Добавить в корзинуПозвонить
Найти в Дзене
Сделай игру

Симулятор морской волны в 3D

Мы уже делали симуляцию движения кораблика по волнам (вот тут), но это была, так сказать, математическая подготовка к более сложной версии решения. Теперь, давайте сделаем симулятор волн, но в 3D. За основу я взял три технологии: JS, WASM и WebGL. Задумка в том, чтобы генерировать точки внутри wasm-пакета (я выбираю rust), передавать их в js приложение, а уже оттуда передаваться в webgl. Поскольку рисунок морской качки будет повторяться - разобьём пространство на плитки, скажем, MxN и каждую плитку будем отрисовывать единым шейдером, но по разным параметрам. Я уже рассказывал, как подключать wasm-пакеты тут и как использовать webgl в своих веб приложениях здесь. Так что повторяться не стану. Красивые волны сделать непросто, особенно для тех, кто, как и я, не особо силён в вопросах её математического аппарата, поэтому я пойду по пути наименьшего сопротивления: Начал я с того, что нарисовал квадрат на экране. Для этого написал небольшой класс на js, создающий программу отрисовки для web
Оглавление

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

Так может выглядеть код для отрисовки моря
Так может выглядеть код для отрисовки моря

Начало

За основу я взял три технологии: JS, WASM и WebGL. Задумка в том, чтобы генерировать точки внутри wasm-пакета (я выбираю rust), передавать их в js приложение, а уже оттуда передаваться в webgl. Поскольку рисунок морской качки будет повторяться - разобьём пространство на плитки, скажем, MxN и каждую плитку будем отрисовывать единым шейдером, но по разным параметрам.

О технологиях

Я уже рассказывал, как подключать wasm-пакеты тут и как использовать webgl в своих веб приложениях здесь. Так что повторяться не стану.

Математика вычислений

Красивые волны сделать непросто, особенно для тех, кто, как и я, не особо силён в вопросах её математического аппарата, поэтому я пойду по пути наименьшего сопротивления:

  • Волны - будут вычисляться по формуле y=sin(x);
  • Каждая плитка имеет свой набор точек, но он согласован с набором соседней плитки (то есть связь будет бесшовной);
  • Существуют два тип волн - продольные и поперечные, они имеют разную амплитуду и, как следствие, это даёт псевдо-уникальное значение в каждой точке;
  • Точки на плитке рассчитываются относительно её смещения от центра (некой условной точки).

Поехали

Начал я с того, что нарисовал квадрат на экране. Для этого написал небольшой класс на js, создающий программу отрисовки для webgl. Больше про шейдеры можете посмотреть тут: много, но не сложно.

Отличительная особенность программ отрисовки, что их может быть много: первая рисует фон, вторая - водоросли, третья - воду, четвёртая - корабли поверх всего этого. Каждая программа будет обслуживаться самостоятельно и самостоятельно отрисовываться.
Минимальный класс с программы отрисовки
Минимальный класс с программы отрисовки

Да, так много текста и только для того, чтобы подготовиться к рисованию.

Это неполный код отрисовки плитки
Это неполный код отрисовки плитки
Коротко по коду (функция render): используем созданную программу, выбираем все точки, которые надо отобразить, отрисовываем их командой drawArrays. В конечном итоге, получился такой вот рисунок.
Синий прямоугольник
Синий прямоугольник

Хоть планировался синий квадрат, получился синий прямоугольник - издержки использования webgl. Но я это поправлю позже.

Время перспективы

Если коротко, то взгляд сверху/снизу/со стороны - это математическое вычисление. Перемножаешь координаты точек на подготовленную матрицу (подробности тут) - получаешь искажение изображения, которое выглядит как трёхмерная графика. И никакого волшебства.

Пробная плитка: угловатая, зато своя
Пробная плитка: угловатая, зато своя

Обзор с помощью мыши

Самовращающаяся плитка это хорошо, но управляемая мышкой - ещё лучше.

Код управления отображением
Код управления отображением

Данный код отвечает за "взгляд" камеры. Есть точнее, то положения мышки отслеживается и, на его основании, вычисляются углы поворота по осям X и Z, а также смещение.

Вся композиция сцены строится относительно центра; плитки, из которых формируется морская гладь, также, будут выстраиваться хоть и по матрице, но относительно центра. Смещение каждой плитки - это отдельное вычисление смещения (translate), отдельно центра. "Камера" же - это отдельные вычисления, которые будут "наложены" поверх вычислений положений плиток - это даст не только правильное размещение на экране, но и нужный угол обзора.
Управляю вращением фигуры мышкой
Управляю вращением фигуры мышкой

Угловатая плитка есть - сделаем её волнистой

Внешний источник волн

Сделаем так: модуль написанный на rust будет генерировать точки "волнующейся" плитки. Для всех плиток, что будут на экране.

Теперь волна стала более волнистой - благодаря добавлению дополнительных точек
Теперь волна стала более волнистой - благодаря добавлению дополнительных точек

Проблемы для одной плитки нет, а вот когда их будет, скажем, с десяток - они начнутся: всё ведь считается на процессоре. Получается примерно 10 кадров в секунду для 25 плиток. Но можно оптимизировать: перенести вычисления внутрь шейдерной программы. Это позволит производить все вычисления внутри GPU. Да, есть свои издержки такого подхода, но выйдет быстрее.

Вершинный шейдер
Вершинный шейдер

В оптимизации мы перестали передавать все точки плитки, а просто передаём временное смещение, в зависимости от которого считается амплитуда волны. Надо сказать, кода получилось примерно в 5 раз меньше, чем при использовании wasm-модуля. Поэтому я от него откажусь.

Получилось то же самое, но сделано проще и работает быстрее
Получилось то же самое, но сделано проще и работает быстрее

Много-много плиток

А теперь давайте разместим много плиток. Среднее время рассчёта схемы для первого варианта - примерно 170 милисекунд; второго варианта, в среднем, 0.2 милисекунды.

Много плиток
Много плиток

Ну вот и готова водная гладь. Хотел код приложить, но там прямо очень много получилось.

Заключение

Простые выводы, которые можно сделать из всего этого безобразия:

  • Даже такую простую вещь, как колышущееся море можно делать долго и трудно, потому что математики там много, а для изменения поведения даже в пустяках требует, порой, правку и программу-исполнение, и шейдеры.
  • Все основные вычисления лучше перекладывать на GPU и не испытывать надежды, мол, посчитаю на процессоре и всё будет хорошо. Не будет. Даже вычисление изменения поведения 25 точек для каждой из 25 плиток на экране может просадить производительность отрисовки до 10 кадров в секунду, зато на видеокарте всё считается просто и быстро. Да и код проще.
  • Надежды на то, что wasm будет работать быстрее - необоснованны. Наверное есть какие-то задачи, где прироста производительности добиться удастся, но для большинства задач - это пустая трата времени.
  • Трёхмерная графика - это не про милых анимированных котиков, это про математику.

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