Как создать 2D-воду с динамическими волнами?

Новый Super Mario Bros имеет действительно классную 2D-воду, которую я хотел бы узнать, как ее создать.

Вот видео , показывающее его. Иллюстративная часть:

этот учебник .

Однако в NSMB вода также имеет постоянные волны на поверхности, и брызги выглядят совсем по-другому. Другое отличие состоит в том, что в учебнике, если вы создаете всплеск, он сначала создает глубокую «дыру» в воде в начале всплеска. В новых супер мариорозах эта дыра отсутствует или намного меньше. Я имею в виду всплески, которые создает игрок при прыжке и выходе из воды.

Как создать водную поверхность с постоянными волнами и брызгами?

Я программирую в XNA. Я пробовал это сам, но я не мог получить синусоидальные волны , чтобы хорошо работать вместе с динамическими волнами.

Я не спрашиваю, как именно разработчики New Super Mario Bros сделали это именно так, просто заинтересовались тем, как воссоздать такой эффект.

77 голосов | спросил Berry 27 22012vEurope/Moscow11bEurope/MoscowTue, 27 Nov 2012 19:26:15 +0400 2012, 19:26:15

3 ответа


141

Я попробовал.

Брызги (пружины)

Как этот учебник упоминает, что поверхность воды похожа на провод: если вы потянете за какую-то точку провода, точки рядом с этой точкой тоже будут сбиты. Все точки также возвращаются к базовой линии.

В основном много вертикальных пружин рядом друг с другом, которые тянут друг на друга.

Я набросал это в Lua, используя LÃ-VE и получил следующее:

анимация всплеска

Выглядит правдоподобно. Oh Hooke , вы красивый гений.

Если вы хотите сыграть с ним, вот порт JavaScript , любезно предоставленный Фил ! Мой код находится в конце этого ответа.

Фоновые волны (сложенные синусы)

Естественные фоновые волны смотрят на меня как на пучок синусоидальных волн (с разными амплитудами, фазами и длинами волн), которые суммируются. Вот как это выглядело, когда я написал это:

фоновые волны, создаваемые синусоидальными помехами

Интерференционные картины выглядят довольно правдоподобными.

Теперь все вместе

Итак, довольно просто объединить волны всплеска и фоновые волны:

фоновые волны с брызгами

Когда появляются брызги, вы можете увидеть маленькие серые круги, показывающие, где будет исходная фоновая волна.

Он очень похож на это видео, которое вы связали , поэтому я считаю успешным эксперимент.

Вот мой main.lua (единственный файл). Я думаю, что это вполне читаемо.

 - Разрешение моделирования
NUM_POINTS = 50
- Ширина моделирования
WIDTH = 400
- Постоянная пружины для сил, приложенных соседними точками
SPRING_CONSTANT = 0,005
- Постоянная спринта для силы, применяемой к базовой линии
SPRING_CONSTANT_BASELINE = 0,005
- Вертикальное смещение нити
Y_OFFSET = 300
- Демпфирование для изменения скорости
ДАМПИНГ = 0,98
- Количество итераций точечных воздействий - точка на волне на шаг
- (это заставляет волны оживлять быстрее)
ИТЕРАЦИИ = 5

- Сделать точки, чтобы идти по волне
функция makeWavePoints (numPoints)
    локальный t = {}
    для n = 1, numPoints do
        - Это точка на волне
        local newPoint = {
            x = n /numPoints * WIDTH,
            y = Y_OFFSET,
            spd = {y = 0}, - скорость с вертикальной составляющей нуля
            масса = 1
        }
        t [n] = newPoint
    конец
    return t
конец

- Разность фаз для каждого синуса
offset = 0

NUM_BACKGROUND_WAVES = 7
BACKGROUND_WAVE_MAX_HEIGHT = 5
BACKGROUND_WAVE_COMPRESSION = 1/5
- Суммы, по которым компенсируется конкретный синус
sineOffsets = {}
- Суммы, по которым усиливается конкретный синус
sineAmplitudes = {}
- Суммы, по которым растянут конкретный синус
sineStretches = {}
- Суммы, по которым умножается конкретное смещение синуса
offsetStretches = {}
- Установите значения каждого синуса на разумное случайное значение
для i = 1, NUM_BACKGROUND_WAVES делают
    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)
конец
- Эта функция суммирует синусы, сгенерированные выше,
- с учетом входного значения x
функции overlapSines (x)
    локальный результат = 0
    для i = 1, NUM_BACKGROUND_WAVES делают
        result = result
            + sineOffsets [i]
            + sineAmplitudes [i] * math.sin (
                x * sineStretches [i] + смещение * offsetStretches [i])
    конец
    результат возврата
конец

wavePoints = makeWavePoints (NUM_POINTS)

- Обновить положение каждой точки волны
функция updateWavePoints (points, dt)
    для i = 1, ИТЕРАЦИИ
    для n, p в ipairs (points) do
        - заставить применить к этому моменту
        локальная сила = 0

        - силы, вызванные точкой сразу слева или справа
        локальная силаFromLeft, forceFromRight

        если n == 1, то - оберните влево-вправо
            local dy = points [# points] .y - p.y
            forceFromLeft = SPRING_CONSTANT * dy
        else - обычно
            локальный dy = точки [n-1] .y - p.y
            forceFromLeft = SPRING_CONSTANT * dy
        конец
        если n == # указывает, то - оберните влево-влево
            локальные dy = точки [1] .y - p.yforceFromRight = SPRING_CONSTANT * dy
        else - обычно
            локальные dy = точки [n + 1] .y - p.y
            forceFromRight = SPRING_CONSTANT * dy
        конец

        - Также примените силу к базовой линии
        локальный dy = Y_OFFSET - p.y
        forceToBaseline = SPRING_CONSTANT_BASELINE * dy

        - Суммировать силы
        force = force + forceFromLeft
        force = force + forceFromRight
        force = force + forceToBaseline

        - Рассчитать ускорение
        локальное ускорение = сила /p.mass

        - Применить ускорение (с затуханием)
        p.spd.y = DAMPING * p.spd.y + ускорение

        - Применить скорость
        p.y = p.y + p.spd.y
    конец
    конец
конец

- Обратный вызов при обновлении
function love.update (dt)
    если love.keyboard.isDown "k" then
        смещение = смещение + 1
    конец

    - На клик: выберите ближайшую точку в позицию мыши
    если love.mouse.isDown ("l"), то
        локальная mouseX, mouseY = love.mouse.getPosition ()
        local closeestPoint = nil
        местное ближайшее Сопротивление = nil
        для _, p в ipairs (wavePoints) делают
            локальное расстояние = math.abs (mouseX-p.x)
            если ближайшее Сопротивление == nil тогда
                closeestPoint = p
                ближайший Расстояние = расстояние
            еще
                если расстояние <= ближайшее
                    closeestPoint = p
                    ближайший Расстояние = расстояние
                конец
            конец
        конец

        closeestPoint.y = love.mouse.getY ()
    конец

    - Обновление позиций очков
    updateWavePoints (wavePoints, dt)
конец

местный круг = love.graphics.circle
локальная линия = love.graphics.line
local color = love.graphics.setColor
love.graphics.setBackgroundColor (0xff, 0xff, 0xff)

- Обратный звонок для рисования
Функция love.draw (dt)

    - Нарисуйте базовую линию
    Цвет (0xff, 0x33,0x33)
    line (0, Y_OFFSET, WIDTH, Y_OFFSET)

    - Нарисуйте «drop line» из курсора

    локальная mouseX, mouseY = love.mouse.getPosition ()
    line (mouseX, 0, mouseX, Y_OFFSET)
    - Нарисовать индикатор щелчка
    если love.mouse.isDown "l" тогда
        love.graphics.circle ("линия", mouseX, mouseY, 20)
    конец

    - Нарисуйте индикатор анимации перекрывающихся волн
    если love.keyboard.isDown "k" then
        love.graphics.print («Перекрытие волн PLAY», 10, Y_OFFSET + 50)
    еще
        love.graphics.print («Перекрывающиеся волны PAUSED», 10, Y_OFFSET + 50)
    конец


    - Нарисуйте точки и линию
    для n, p в ipairs (wavePoints) делают
        - Нарисуйте маленькие серые круги для волн перекрытия
        Цвет (0xAA, 0xAA, 0xBB)
        circle ("line", p.x, Y_OFFSET + overlapSines (p.x), 2)
        - Нарисуйте синие круги для финальной волны
        цвет (0x00,0x33,0xbb)
        circle ("line", p.x, p.y + overlapSines (p.x), 4)
        - Нарисовать линии между кругами
        если n == 1, то
        еще
            local leftPoint = wavePoints [n-1]
            line (leftPoint.x, leftPoint.y + overlapSines (leftPoint.x), p.x, p.y + overlapSines (p.x))
        конец
    конец
конец
ответил Anko 9 SunEurope/Moscow2012-12-09T17:16:38+04:00Europe/Moscow12bEurope/MoscowSun, 09 Dec 2012 17:16:38 +0400 2012, 17:16:38
10

Для решения (математически говоря, вы можете решить проблему с решением дифференциальных уравнений, но я уверен, что они этого не делают) создавая волны, у вас есть 3 возможности (в зависимости от того, насколько детально это должно получиться):

  1. Рассчитайте волны с помощью тригонометрических функций (наиболее простых и быстрых)
  2. Сделайте это, как предложил Анко
  3. Решите дифференциальные уравнения
  4. Использовать поиск текстур

Решение 1

Действительно, для каждой волны мы вычисляем (абсолютное) расстояние от каждой точки поверхности до источника и вычисляем «высоту» с помощью формулы

1.0f /(dist * dist) * sin (dist * FactorA + Phase)

, где

  • dist - наше расстояние
  • FactorA - это значение, которое означает, насколько быстрые /плотные волны должны быть
  • Фаза - это фаза волны, нам нужно постепенно увеличивать ее, чтобы получить анимированную волну.

Обратите внимание, что мы можем добавить столько членов вместе, сколько нам нравится (принцип суперпозиции).

Pro

  • Его очень быстро вычислить
  • Легко реализовать

Contra

  • Для (простых) отражений на 1-й поверхности нам нужно создать «призрачные» источники волн для имитации отражений, это сложнее на 2d-поверхностях и является одним из ограничений этого простого подхода.

Решение 2

Pro

  • Простое и простое
  • Это позволяет легко рассчитать отражения.
  • Он может быть легко перенесен на 2d или 3d пространство

Contra

  • Может получить численное неустойчивость, если значение сброса слишком велико.
  • требуется больше вычислительной мощности, чем Solution 1 (но не так, как Solution 3 )

Решение 3

Теперь я ударил жесткую стену, это самое сложное решение.

Я не реализовал этот, но можно решить эти монстры.

Здесь вы можете найти презентацию о математике, ее не просто и существуют также дифференциальные уравнения для разных видов волн.

Здесь - неполный список с некоторыми дифференциальными уравнениями для решения более частных случаев (Solitons, Peakons, ... )

Pro

  • Реалистичные волны

Contra

  • Для большинства игр не стоит усилий
  • Требуется наибольшее время вычисления

Решение 4

Немного сложнее, чем решение 1 , но не так сложно решить 3.

Мы используем предварительно просчитанные текстуры и объединяем их вместе, после чего мы используем отображение смещения (на самом деле метод для 2d-волн, но этот принцип также может работать для 1d-волн)

Игра sturmovik использовала этот подход, но я не нашел ссылку на статью об этом.

Pro

  • это проще, чем 3
  • он получает хорошие результаты (для 2d)
  • он может выглядеть реалистично, если художники отлично справятся с работой.

Contra

  • трудно оживить
  • повторяющиеся шаблоны могут отображаться на горизонте
ответил Quonux 7 PMpSun, 07 Apr 2013 18:44:54 +040044Sunday 2013, 18:44:54
5

Чтобы добавить постоянные волны, добавьте пару синусоидальных волн после того, как вы рассчитали динамику. Для простоты я сделал бы это смещение только графическим эффектом и не позволял ему влиять на динамику, но вы могли бы попробовать обе альтернативы и посмотреть, какая из них лучшая.

Чтобы сделать «всплеск» меньше, я бы предложил изменить метод Splash (int index, float speed), чтобы он напрямую влиял не только на индекс, но и на некоторые из близких вершин, чтобы разложить эффект, но все же та же «энергия». Количество затронутых вершин может зависеть от того, насколько широка ваша цель. Вероятно, вам нужно будет усилить эффект, прежде чем у вас получится отличный результат.

Чтобы текстурировать более глубокие части воды, вы могли либо сделать, как описано в статье, и просто сделать более глубокую часть «более синей», или вы могли бы интерполировать между двумя текстурами в зависимости от глубины воды.

ответил Mikael Högström 28 32012vEurope/Moscow11bEurope/MoscowWed, 28 Nov 2012 01:03:41 +0400 2012, 01:03:41

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132