Волны Герстнера в Unreal Engine 4

Создание симуляции водной поверхности через редактор шейдеров Unreal Engine 4

Введение

Сразу скажу, что пост рассчитан на людей, которые хоть немного знакомы с шейдерами (Material, Material Instance) и их базовыми функциями в UE4.

В этом посте я не буду подробно описывать создание шейдера воды, а только симуляцию волн.

Ну и вдобавок скажу, что я сам не особо то и профессионал, а просто студент, который в свободное время сидит и изучает движок, так что буду только рад, если кто-то укажет мне на ошибки (если они будут).

Начало

Волны Герстнера активно используют для симуляции воды в компьютерной графике. Их главным преимуществом является смещение вершин не только по оси Z (как у обычных синусоидальных волн), но и по осям X и Y, что собственно и создает достаточно реалистичные волны и не требует особой производительности.

В GPU Gems хорошо описывается вывод уравнений для этих волн, начиная с простой синусоидальной волны. С неё мы и начнем.

Для начала создаем и открываем Material Function, назовем его MF_GerstnerWave. Выделение волны в отдельную функцию позволит создать в материале воды несколько совмещенных волн, не захламляя при этом граф.

Вот такая функция для вычисления высоты нам дана на GPU Gems

Точкой обозначено скалярное произведение векторов, а крестиком - умножение
Точкой обозначено скалярное произведение векторов, а крестиком - умножение

Где

  • A — Амплитуда
  • D — Двумерный вектор направления волны
  • x, y — координаты вершины
  • ω - частота волны
  • t — время
  • φ — фазовый угол

ω будем выводить через длину волны L (ω = 2 / L)

φ через скорость S и длину волны L (φ = S * (2 / L))

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

Волны Герстнера в Unreal Engine 4

Делаем промежуточные вычисления

Волны Герстнера в Unreal Engine 4

Для самой функции вычисления высоты вершины я создам Custom HLSL ноду, в которую запишу формулу, потому что на мой взгляд так будет гораздо читабельнее и легче потом искать ошибки.

Вот что будет в итоге без Custom HLSL ноды, это конечно не уровень <a href="https://blueprintsfromhell.tumblr.com/" rel="nofollow noreferrer noopener" target="_blank">Blueprints from Hell</a>, но все же
Вот что будет в итоге без Custom HLSL ноды, это конечно не уровень Blueprints from Hell, но все же

Вот так все должно в итоге выглядеть, в настройках Custom HLSL выставляем возвращаемый тип CMOT Float 1

Волны Герстнера в Unreal Engine 4

Вот код Custom HLSL ноды, ничего сложного

return A * sin(dot(D, Pos) * W + Time * Phi);

Теперь создадим материал, в котором будем использовать нашу созданную функцию. В настройках материала нужно выставить параметр Flat Tesselation, чтобы мы могли использовать World Displacement

Волны Герстнера в Unreal Engine 4

Вызываем нашу созданную функцию с парой параметров, чтобы их можно было редактировать через Material Instance и вот такой получится материал.

В TexCoord выставлен тайлинг 700, текстура стандартный анриловский grid
В TexCoord выставлен тайлинг 700, текстура стандартный анриловский grid

Натягиваем наш материал на какую-нибудь плоскость (желательно с большим количеством треугольников) и видим такой результат в режиме brush wireframe

10/10 шакалов

У вас результат может быть другим, все зависит от настройки параметров в Material Instance.

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

Второй этап, собственно волны Герстнера

Как я уже говорил ранее, волны Герстнера определяют смещения не только на Z компоненту, но и по X, Y тоже. Это позволяет делать волны более «острыми», прямо как настоящие.

Для этого нам понадобиться ещё один входной параметр в нашу функцию MF_GerstnerWave — Steepness (крутизна).

Вот функция с GPU Gems

Волны Герстнера в Unreal Engine 4

Как можно заметить, Z компонента получаемого вектора вычисляется той же формулой, что мы использовали для синусоидальной волны, поэтому нам остается вычислять только X и Y компоненты.

Добавляем в функцию новый входной параметр Steepness (Q) и ограничиваем его до отрезка [0, 1 / (w * A)], со значениями Q вне этого отрезка волны будут неправильной формы.

Волны Герстнера в Unreal Engine 4

Вот наш переписанный код Custom HLSL ноды

float x = Q * A * D.x * cos(W * dot(D, Pos) + Phi * Time); float y = Q * A * D.y * cos(W * dot(D, Pos) + Phi * Time); float z = A * sin(dot(D, Pos) * W + Time * Phi); return float3(x, y, z);

Не забудьте поменять возвращаемый тип ноды на CMOT Float 3!

Ну и вот наша вся измененная функция

Заметьте, что MakeFloat больше не нужен, результат функции сразу выводим в output
Заметьте, что MakeFloat больше не нужен, результат функции сразу выводим в output

В материале не забываем добавить новый параметр Steepness

Волны Герстнера в Unreal Engine 4

Смотрим на итоговый результат

//Как мы видим, с изменением Steepness, меняется и крутизна нашей волны

Теперь попробуем совместить несколько волн в одном материале

Копируем функцию четыре раза, меняем названия параметров, складываем выходные данные и подключаем их в World Displacement

Не забываем поменять значения параметров разных волн в <i>Material Instance</i>
Не забываем поменять значения параметров разных волн в Material Instance

Вот конечный результат

Чаще всего совмещают 4-12 волн (12 на океан, 4-8 на озеро, например)

Третий этап, нормали

Если нам вдруг понадобятся нормали (например для создания фейкового отражения лучей солнца, если материал воды будет unlit), вместе со смещением вершины нам нужно просчитывать и её новую нормаль.

Смотрим на функцию с GPU Gems

<b>P</b> нам уже известно - это смещение
P нам уже известно - это смещение

Реализовывать все это мы также будем в нашей MF_GerstnerWave и с помощью Custom HLSL ноды.

Создаем ноду Custom с такими входными параметрами

Волны Герстнера в Unreal Engine 4

Код внутри Custom:

float inner = W * dot(D, P) + Phi * Time; float x = -D.x * W * A * cos(inner); float y = -D.y * W * A * cos(inner); float z = Q * W * A * sin(inner); return float3(x, y, z);

Заметьте, что мы пока не вычитаем Z из единицы, мы будем делать это позже после сложения всех волн.

Также к вычисленному значению смещения нужно прибавить текущее положение вершины в мире

Волны Герстнера в Unreal Engine 4

Подсоединяем значение функции к новому output

Волны Герстнера в Unreal Engine 4

В материале нам нужно банально сложить все нормали всех волн, нормализовать результат и потом не забыть вычесть финальную Z компоненту из единицы.

Волны Герстнера в Unreal Engine 4

Заключение

Как я уже говорил, нормали в случае с волнами Герстнера нужны, чтобы например использовать фейковое отражение на unlit материалах. Вот мой пример:

Как видно, отражения от реального источника света нет, есть только фейковое, материал с shading model Unlit

Надеюсь я достаточно понятно объяснил принцип создания волн через шейдерный редактор. Буду рад критике по поводу содержания/оформления поста.

Подписывайтесь на блог, наверное туда буду выкладывать какие-нибудь посты по Unreal Engine 4.

5555 показов
7.6K7.6K открытий
33 репоста
24 комментария

Комментарий недоступен

Ответить

Комментарий недоступен

Ответить

Все думаю одному скучно будет, но фиг с ним, по акции как нибудь возьму, заинтриговали вы меня своими волнами, 

Ответить

Ну ты сравнил. Там всё-таки волны нифига себе. А тут по сути база - то, что написано в GPU gems уже 13 лет. Там ещё накручивать и накручивать можно

Ответить

Согласен, лучший шейдер воды, что я видел

Ответить

Мне еще понравилась вода в ATLAS

Ответить