Создание симуляции водной поверхности через редактор шейдеров 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 ноды, это конечно не уровень Blueprints from Hell, но все жеВот так все должно в итоге выглядеть, в настройках Custom HLSL выставляем возвращаемый тип CMOT Float 1Вот код Custom HLSL ноды, ничего сложногоreturn A * sin(dot(D, Pos) * W + Time * Phi);Теперь создадим материал, в котором будем использовать нашу созданную функцию. В настройках материала нужно выставить параметр Flat Tesselation, чтобы мы могли использовать World DisplacementВызываем нашу созданную функцию с парой параметров, чтобы их можно было редактировать через Material Instance и вот такой получится материал.В TexCoord выставлен тайлинг 700, текстура стандартный анриловский gridНатягиваем наш материал на какую-нибудь плоскость (желательно с большим количеством треугольников) и видим такой результат в режиме brush wireframe10/10 шакаловУ вас результат может быть другим, все зависит от настройки параметров в Material Instance.В целом уже выглядит как достойный представитель водной поверхности, но на этом мы останавливаться не будем.Второй этап, собственно волны ГерстнераКак я уже говорил ранее, волны Герстнера определяют смещения не только на Z компоненту, но и по X, Y тоже. Это позволяет делать волны более «острыми», прямо как настоящие.Для этого нам понадобиться ещё один входной параметр в нашу функцию MF_GerstnerWave — Steepness (крутизна).Вот функция с GPU GemsКак можно заметить, Z компонента получаемого вектора вычисляется той же формулой, что мы использовали для синусоидальной волны, поэтому нам остается вычислять только X и Y компоненты.Добавляем в функцию новый входной параметр Steepness (Q) и ограничиваем его до отрезка [0, 1 / (w * A)], со значениями Q вне этого отрезка волны будут неправильной формы.Вот наш переписанный код 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В материале не забываем добавить новый параметр SteepnessСмотрим на итоговый результат//Как мы видим, с изменением Steepness, меняется и крутизна нашей волныТеперь попробуем совместить несколько волн в одном материалеКопируем функцию четыре раза, меняем названия параметров, складываем выходные данные и подключаем их в World DisplacementНе забываем поменять значения параметров разных волн в Material InstanceВот конечный результатЧаще всего совмещают 4-12 волн (12 на океан, 4-8 на озеро, например)Третий этап, нормалиЕсли нам вдруг понадобятся нормали (например для создания фейкового отражения лучей солнца, если материал воды будет unlit), вместе со смещением вершины нам нужно просчитывать и её новую нормаль.Смотрим на функцию с GPU GemsP нам уже известно - это смещениеРеализовывать все это мы также будем в нашей MF_GerstnerWave и с помощью Custom HLSL ноды.Создаем ноду Custom с такими входными параметрамиКод внутри 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 из единицы, мы будем делать это позже после сложения всех волн.Также к вычисленному значению смещения нужно прибавить текущее положение вершины в миреПодсоединяем значение функции к новому outputВ материале нам нужно банально сложить все нормали всех волн, нормализовать результат и потом не забыть вычесть финальную Z компоненту из единицы.ЗаключениеКак я уже говорил, нормали в случае с волнами Герстнера нужны, чтобы например использовать фейковое отражение на unlit материалах. Вот мой пример:Как видно, отражения от реального источника света нет, есть только фейковое, материал с shading model UnlitНадеюсь я достаточно понятно объяснил принцип создания волн через шейдерный редактор. Буду рад критике по поводу содержания/оформления поста.Подписывайтесь на блог, наверное туда буду выкладывать какие-нибудь посты по Unreal Engine 4.#лонг #опыт #unrealengine #шейдеры
Комментарий недоступен
Комментарий недоступен
Все думаю одному скучно будет, но фиг с ним, по акции как нибудь возьму, заинтриговали вы меня своими волнами,
Ну ты сравнил. Там всё-таки волны нифига себе. А тут по сути база - то, что написано в GPU gems уже 13 лет. Там ещё накручивать и накручивать можно
Согласен, лучший шейдер воды, что я видел
Мне еще понравилась вода в ATLAS