Волны Герстнера в 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))
Для всего остального, кроме времени и координат вершины просто создадим входные параметры для функции.
Делаем промежуточные вычисления
Для самой функции вычисления высоты вершины я создам Custom HLSL ноду, в которую запишу формулу, потому что на мой взгляд так будет гораздо читабельнее и легче потом искать ошибки.
Вот так все должно в итоге выглядеть, в настройках Custom HLSL выставляем возвращаемый тип CMOT Float 1
Вот код Custom HLSL ноды, ничего сложного
Теперь создадим материал, в котором будем использовать нашу созданную функцию. В настройках материала нужно выставить параметр Flat Tesselation, чтобы мы могли использовать World Displacement
Вызываем нашу созданную функцию с парой параметров, чтобы их можно было редактировать через Material Instance и вот такой получится материал.
Натягиваем наш материал на какую-нибудь плоскость (желательно с большим количеством треугольников) и видим такой результат в режиме brush wireframe
У вас результат может быть другим, все зависит от настройки параметров в Material Instance.
В целом уже выглядит как достойный представитель водной поверхности, но на этом мы останавливаться не будем.
Второй этап, собственно волны Герстнера
Как я уже говорил ранее, волны Герстнера определяют смещения не только на Z компоненту, но и по X, Y тоже. Это позволяет делать волны более «острыми», прямо как настоящие.
Для этого нам понадобиться ещё один входной параметр в нашу функцию MF_GerstnerWave — Steepness (крутизна).
Вот функция с GPU Gems
Как можно заметить, Z компонента получаемого вектора вычисляется той же формулой, что мы использовали для синусоидальной волны, поэтому нам остается вычислять только X и Y компоненты.
Добавляем в функцию новый входной параметр Steepness (Q) и ограничиваем его до отрезка [0, 1 / (w * A)], со значениями Q вне этого отрезка волны будут неправильной формы.
Вот наш переписанный код Custom HLSL ноды
Не забудьте поменять возвращаемый тип ноды на CMOT Float 3!
Ну и вот наша вся измененная функция
В материале не забываем добавить новый параметр Steepness
Смотрим на итоговый результат
Теперь попробуем совместить несколько волн в одном материале
Копируем функцию четыре раза, меняем названия параметров, складываем выходные данные и подключаем их в World Displacement
Вот конечный результат
Чаще всего совмещают 4-12 волн (12 на океан, 4-8 на озеро, например)
Третий этап, нормали
Если нам вдруг понадобятся нормали (например для создания фейкового отражения лучей солнца, если материал воды будет unlit), вместе со смещением вершины нам нужно просчитывать и её новую нормаль.
Смотрим на функцию с GPU Gems
Реализовывать все это мы также будем в нашей MF_GerstnerWave и с помощью Custom HLSL ноды.
Создаем ноду Custom с такими входными параметрами
Код внутри Custom:
Заметьте, что мы пока не вычитаем Z из единицы, мы будем делать это позже после сложения всех волн.
Также к вычисленному значению смещения нужно прибавить текущее положение вершины в мире
Подсоединяем значение функции к новому output
В материале нам нужно банально сложить все нормали всех волн, нормализовать результат и потом не забыть вычесть финальную Z компоненту из единицы.
Заключение
Как я уже говорил, нормали в случае с волнами Герстнера нужны, чтобы например использовать фейковое отражение на unlit материалах. Вот мой пример:
Надеюсь я достаточно понятно объяснил принцип создания волн через шейдерный редактор. Буду рад критике по поводу содержания/оформления поста.
Подписывайтесь на блог, наверное туда буду выкладывать какие-нибудь посты по Unreal Engine 4.
Комментарий недоступен
Комментарий недоступен
Все думаю одному скучно будет, но фиг с ним, по акции как нибудь возьму, заинтриговали вы меня своими волнами,
Ну ты сравнил. Там всё-таки волны нифига себе. А тут по сути база - то, что написано в GPU gems уже 13 лет. Там ещё накручивать и накручивать можно
Согласен, лучший шейдер воды, что я видел
Мне еще понравилась вода в ATLAS