Как мы делали красивую двумерную воду в Unity
Cоздание почти трёхмерного окружения без трёхмерной графики.
Мы — это я, Егор, и Юра, наш художник, которому я упорно создаю след в сети, но он не торопится этим пользоваться. И, собственно, мы очень неспешно работаем над нашей игрой и иногда выкладываем всякие заметки о том, что там и как. В этот раз речь пойдёт о том, как мы захотели добавить водички в игру, но не смогли остановиться на слишком простом решении.
Сразу оговорюсь, что работаем мы в Unity и используем Universal Render Pipeline для отрисовки, но общие идеи можно перенести, конечно, и в другие пайплайны отрисовки и даже движки.
Макет
Началось всё с макета и от него пошло. Первоначально он выглядел чуть темнее, но, немного поигравшись, мы получили вот такую картинку.
Сразу выделим, какие детали нужно будет реализовать:
- собственно, вода;
- пенка у колон и камней;
- туман.
План
Так как вся игра в 2D, доступа к высотам в точках у меня нет, и просто перенести 3D подход возможности не было. Поэтому я придумал другую хитрость — сделать отдельную камеру, копирующую основную, подменять в ней спрайты с помощью URP Renderer и дополнительных текстур и рисовать собственную карту высот, используя её. А по ней уже строить очертания моря и тумана.
Тут оговорюсь для людей, незнакомых с Unity, что URP (Universal Render Pipeline) — это один из вариантов пайплайнов отрисовки, который позволяет гибко настраивать, собственно, отрисовку. И идея тут в том, чтобы создать специальный способ отрисовки в специальной камере для некоторых игровых объектов.
Карта высот
Для начала нужно придумать, как её вообще рисовать. В обычном 3D она рисуется автоматически по мере записи в Depth Buffer (иногда и иначе, не будем задерживаться на этом), остаётся только взять её и использовать. Но так как у нас 2D-спрайты, то нам это, конечно же, не подходит.
Самый простой способ, как мне показалось, — это нарисовать специальную текстуру, копирующую текстуру колоны, где в каждом пикселе в B-канале (можно любой другой, это не так важно) будет записано, насколько этот пиксель текстуры ниже, чем точка «нуля».
В редакторе спрайтов Unity потом достаточно просто добавить вторичную текстуру и дать ей название.
Теперь нужно написать шейдер, который будет, собственно, заменять основную текстуру на дополнительную. Помимо этого, он должен немного перекодировать данные, потому что в текстуру влезет всего 256 значений (если она не sRGB, то больше, но это детали), а высота может быть раньше. В шейдер добавляем проперти _Meta и используем те же UV-координаты + вешаем математику сверху.
После я создал URP Renderer и добавил в него фичу для подмены материалов.
Теперь остаётся только создать камеру, поменять ей Renderer и настроить слои, получаем две картинки, одна из которых выводится на экран.
Вуаля, карта высот готова.
Вода
Изначально я хотел сделать поверх всего экрана спрайт, который был бы прозрачным там, где он «ниже», чем объект. И я даже это сделал, но создалось несколько проблем:
- нужно было бы заносить в карты высот вообще всё, а значит рисовать высоты вообще для всего;
- спрайт постоянно мешался, потому что отрисовывался вообще всегда даже на сцене.
Второе решалось простым отключением слоя с ним редакторе, но первое уже было действительно проблемой, поэтому мы решили рисовать море за колонами, а сами колоны отсекать в их же шейдере, делая альфу нулевой, если они ниже уровня воды.
Собираем это всё и смотрим на море.
Оживляем море
Для отрисовки пены изначально я взял карту высот, прошёлся по ней блюром и пастеризовал в цветах воды. Сразу заметил, что пенка появляется и сверху картинки рядом с колонами, а не под ними, потому что не понимает, что колоны над ней.
Чтобы это быстренько поправить, я начал размывать не вокруг точки, а в полукруге под ней. То есть, если обычный блюр — это усреднение цвета вокруг точки, то я сделал усреднение только для нижней половины круга.
На практике это выглядело вот так.
Уже получше. Далее я быстренько накинул шум, чтобы было что-то двигающееся. Выглядело оно так себе.
Но тут пришел Юра и рассказал, как делал волны на макете — просто брал три текстуры с шумом на основе фотки волн и двигал их каждую в свою сторону. После чего он же запрограммировал для этого шейкер и стало выглядеть гораздо лучше.
Но пена нам всё-таки не понравилась, решили переделать.
Пена 2.0
Для этого к каждой колоне я прикрепил спрайт с очертаниями пены, зафиксировал его на уровне воды и настроил камеру, которая рисовала только эти спрайты в отдельную текстуру.
Теперь добавляем новую пену и делаем так, чтобы волны бились по угасающей синусоиде.
Туман
Опять же, сначала я хотел сделать спрайт поверх, с переменной альфой, но появились те же самые проблемы, и туман откатился в тот же шейдер колон. Там просто подмешивается цвет тумана, пропихнутый через всяческую арифметику самым простым способом.
После этого я не выдержал создать очень много нод и переписал весь шейдер клеток на HLSL. Еще была проблема с переиспользованием функций между HLSL и шейдерграфом, потому что я иногда передавал семплер текстур. Проблема заключалась в том, что нельзя просто взять и передать семплер из шейдер графа и в HLSL ноду. Это, конечно, тоже добавило очков текстовым шейдерам.
Переписав всё это добро, мы поигрались с самыми разными параметрами и добавили коня-колосса.
Для коня, кстати, руками рисовалась карта высот и вообще весь процесс довольно весело выглядел.
По итогу получилось собрать псевдо-карту высот и красивую анимацию воды, которая когда-нибудь пойдет в полноценную игру. Геймплейно мы при этом двигаемся не слишком быстро (да и в части визуальной тоже, на самом деле), но мы просто любим делать гиммики, которые радуют глаз.
Водичка чисто партиклами поверх голубой плоскости.
Интересно выглядит) А можно скрин редактора с Gizmos иконками или описание в двух словах как оно работает? Здесь несколько систем частиц, стандартный ли эмитер или вы кастомили создание частиц.
Комментарий недоступен
- Вы в воде купаете?
- Нет, просто показываем, как сделали.
- Красивое.
На asset store продаете?
Комментарий недоступен
Я не умею пользоваться реддитом, а Юра говорит, чтобы я разбирался. Ну а дальше мне лениво и как-то вот!