Я попробовал это.
Брызги (пружины)
Как упоминается в этом уроке , поверхность воды похожа на проволоку: если вы потянете за какую-то точку проволоки, точки рядом с этой точкой также будут смещены вниз. Все точки также возвращаются к базовой линии.
Это в основном много вертикальных пружин рядом друг с другом, которые также тянут друг друга.
Я набросал это в Lua, используя LÖVE, и получил это:
Выглядит правдоподобно О, Хук , ты красивый гений.
Если вы хотите поиграть с ним, вот вам JavaScript от Фила ! Мой код находится в конце этого ответа.
Фоновые волны (сложенные синусы)
Естественные фоновые волны выглядят для меня как синусоидальные волны (с разными амплитудами, фазами и длинами волн), которые суммируются вместе. Вот как это выглядело, когда я писал это:
Интерференционные картины выглядят довольно правдоподобно.
Теперь все вместе
Итак, довольно просто суммировать всплески и фоновые волны:
Когда происходят всплески, вы можете видеть маленькие серые круги, показывающие, где будет исходная фоновая волна.
Это похоже на то видео, которое вы связали , поэтому я считаю это успешным экспериментом.
Вот мой main.lua
(единственный файл). Я думаю, что это вполне читабельно.
-- Resolution of simulation
NUM_POINTS = 50
-- Width of simulation
WIDTH = 400
-- Spring constant for forces applied by adjacent points
SPRING_CONSTANT = 0.005
-- Sprint constant for force applied to baseline
SPRING_CONSTANT_BASELINE = 0.005
-- Vertical draw offset of simulation
Y_OFFSET = 300
-- Damping to apply to speed changes
DAMPING = 0.98
-- Number of iterations of point-influences-point to do on wave per step
-- (this makes the waves animate faster)
ITERATIONS = 5
-- Make points to go on the wave
function makeWavePoints(numPoints)
local t = {}
for n = 1,numPoints do
-- This represents a point on the wave
local newPoint = {
x = n / numPoints * WIDTH,
y = Y_OFFSET,
spd = {y=0}, -- speed with vertical component zero
mass = 1
}
t[n] = newPoint
end
return t
end
-- A phase difference to apply to each sine
offset = 0
NUM_BACKGROUND_WAVES = 7
BACKGROUND_WAVE_MAX_HEIGHT = 5
BACKGROUND_WAVE_COMPRESSION = 1/5
-- Amounts by which a particular sine is offset
sineOffsets = {}
-- Amounts by which a particular sine is amplified
sineAmplitudes = {}
-- Amounts by which a particular sine is stretched
sineStretches = {}
-- Amounts by which a particular sine's offset is multiplied
offsetStretches = {}
-- Set each sine's values to a reasonable random value
for i=1,NUM_BACKGROUND_WAVES do
table.insert(sineOffsets, -1 + 2*math.random())
table.insert(sineAmplitudes, math.random()*BACKGROUND_WAVE_MAX_HEIGHT)
table.insert(sineStretches, math.random()*BACKGROUND_WAVE_COMPRESSION)
table.insert(offsetStretches, math.random()*BACKGROUND_WAVE_COMPRESSION)
end
-- This function sums together the sines generated above,
-- given an input value x
function overlapSines(x)
local result = 0
for i=1,NUM_BACKGROUND_WAVES do
result = result
+ sineOffsets[i]
+ sineAmplitudes[i] * math.sin(
x * sineStretches[i] + offset * offsetStretches[i])
end
return result
end
wavePoints = makeWavePoints(NUM_POINTS)
-- Update the positions of each wave point
function updateWavePoints(points, dt)
for i=1,ITERATIONS do
for n,p in ipairs(points) do
-- force to apply to this point
local force = 0
-- forces caused by the point immediately to the left or the right
local forceFromLeft, forceFromRight
if n == 1 then -- wrap to left-to-right
local dy = points[# points].y - p.y
forceFromLeft = SPRING_CONSTANT * dy
else -- normally
local dy = points[n-1].y - p.y
forceFromLeft = SPRING_CONSTANT * dy
end
if n == # points then -- wrap to right-to-left
local dy = points[1].y - p.y
forceFromRight = SPRING_CONSTANT * dy
else -- normally
local dy = points[n+1].y - p.y
forceFromRight = SPRING_CONSTANT * dy
end
-- Also apply force toward the baseline
local dy = Y_OFFSET - p.y
forceToBaseline = SPRING_CONSTANT_BASELINE * dy
-- Sum up forces
force = force + forceFromLeft
force = force + forceFromRight
force = force + forceToBaseline
-- Calculate acceleration
local acceleration = force / p.mass
-- Apply acceleration (with damping)
p.spd.y = DAMPING * p.spd.y + acceleration
-- Apply speed
p.y = p.y + p.spd.y
end
end
end
-- Callback when updating
function love.update(dt)
if love.keyboard.isDown"k" then
offset = offset + 1
end
-- On click: Pick nearest point to mouse position
if love.mouse.isDown("l") then
local mouseX, mouseY = love.mouse.getPosition()
local closestPoint = nil
local closestDistance = nil
for _,p in ipairs(wavePoints) do
local distance = math.abs(mouseX-p.x)
if closestDistance == nil then
closestPoint = p
closestDistance = distance
else
if distance <= closestDistance then
closestPoint = p
closestDistance = distance
end
end
end
closestPoint.y = love.mouse.getY()
end
-- Update positions of points
updateWavePoints(wavePoints, dt)
end
local circle = love.graphics.circle
local line = love.graphics.line
local color = love.graphics.setColor
love.graphics.setBackgroundColor(0xff,0xff,0xff)
-- Callback for drawing
function love.draw(dt)
-- Draw baseline
color(0xff,0x33,0x33)
line(0, Y_OFFSET, WIDTH, Y_OFFSET)
-- Draw "drop line" from cursor
local mouseX, mouseY = love.mouse.getPosition()
line(mouseX, 0, mouseX, Y_OFFSET)
-- Draw click indicator
if love.mouse.isDown"l" then
love.graphics.circle("line", mouseX, mouseY, 20)
end
-- Draw overlap wave animation indicator
if love.keyboard.isDown "k" then
love.graphics.print("Overlap waves PLAY", 10, Y_OFFSET+50)
else
love.graphics.print("Overlap waves PAUSED", 10, Y_OFFSET+50)
end
-- Draw points and line
for n,p in ipairs(wavePoints) do
-- Draw little grey circles for overlap waves
color(0xaa,0xaa,0xbb)
circle("line", p.x, Y_OFFSET + overlapSines(p.x), 2)
-- Draw blue circles for final wave
color(0x00,0x33,0xbb)
circle("line", p.x, p.y + overlapSines(p.x), 4)
-- Draw lines between circles
if n == 1 then
else
local leftPoint = wavePoints[n-1]
line(leftPoint.x, leftPoint.y + overlapSines(leftPoint.x), p.x, p.y + overlapSines(p.x))
end
end
end
Для решения (математически говоря, вы можете решить проблему с помощью решения дифференциальных уравнений, но я уверен, что они этого не делают) для создания волн у вас есть 3 возможности (в зависимости от того, насколько подробным он должен быть):
Решение 1
Действительно просто, для каждой волны мы вычисляем (абсолютное) расстояние от каждой точки поверхности до источника и вычисляем «высоту» по формуле
1.0f/(dist*dist) * sin(dist*FactorA + Phase)
где
Обратите внимание, что мы можем добавить столько терминов, сколько захотим (принцип суперпозиции).
профессионал
против
Решение 2
профессионал
против
Решение 3
Теперь я ударился о жесткую стену, это самое сложное решение.
Я не реализовал это, но возможно решить этих монстров.
Здесь вы можете найти представление о его математике, это не просто, и существуют также дифференциальные уравнения для различных видов волн.
Вот неполный список с некоторыми дифференциальными уравнениями для решения более частных случаев (Solitons, Peakons, ...)
профессионал
против
Решение 4
Немного сложнее, чем решение 1, но не так сложно решение 3.
Мы используем предварительно рассчитанные текстуры и смешиваем их вместе, после этого мы используем отображение смещения (фактически метод для 2d волн, но принцип может также работать для 1d волн)
В игре sturmovik использовался этот подход, но я не нахожу ссылку на статью об этом.
профессионал
против
источник
Чтобы добавить постоянные волны, добавьте пару синусоид после расчета динамики. Для простоты я бы сделал это смещение только графическим эффектом и не позволил бы ему влиять на саму динамику, но вы могли бы попробовать обе альтернативы и посмотреть, какая из них работает лучше всего.
Чтобы уменьшить "всплеск", я бы предложил изменить метод Splash (int index, float speed) так, чтобы он напрямую воздействовал не только на индекс, но и на некоторые близкие вершины, чтобы распространить эффект, но при этом иметь тот же " энергия». Количество затронутых вершин может зависеть от ширины вашего объекта. Вам, вероятно, нужно будет настроить эффект, прежде чем вы получите идеальный результат.
Чтобы текстурировать более глубокие части воды, вы можете либо сделать, как описано в статье, и просто сделать более глубокую часть «более синим», либо вы можете интерполировать между двумя текстурами в зависимости от глубины воды.
источник