Как сделать дождь в unity
Математика в Gamedev по-простому. Кривые и рябь для эффекта дождя в Unity
Всем привет! Меня зовут Гриша, и я основатель CGDevs. Продолжим говорить про математику что ли. Пожалуй, основное применение математики в геймдеве и компьютерной графики в целом – это VFX. Вот и поговорим про один такой эффект – дождь, а точнее про его основную часть, требующую математики – рябь на поверхности. Последовательно напишем шейдер для ряби на поверхности, и разберём его математику. Если интересно – добро пожаловать под кат. Гитхаб проект прилагается.
Иногда наступает такой момент в жизни, когда программист должен взяться за бубен и призвать дождь. В целом сама по себе тема моделирования дождя очень глубокая. Существует множество математических работ по разным частям этого процесса от падения капли и эффектов связанных с этим до распределения капель в объёме. Разберём только один аспект – шейдер, который позволит нам создавать эффект похожий на волну от упавшей капли. Пора браться за бубен!
При поиске в интернете находишь очень много забавных математических выражений для генерации ряби. Часто они состоят каких-то «магических» чисел и периодической функции без обоснований. Но вообще математика подобного эффекта довольно простая.
Нам понадобится всего лишь уравнение плоской волны в одномерном случае. Почему плоской и одномерной разберём чуть позже.
Уравнение плоской волны в нашем случае может быть записано как:
Aresult = A * cos(2 * PI *(x / waveLength – t * frequency));
Где:
Aresult – амплитуда в точке x, в момент времени t
А – максимальная амплитуда
wavelength – длина волны
frequency – частота волны
PI – число ПИ = 3.14159 (float)
Первое, что нам понадобится – это уравнение окружности. Волна нашего шейдера будет симметрична относительно центра. Уравнение окружности в 2д случае описывается, как:
нам понадобится радиус, так что уравнение приобретёт форму:
и это даст нам симметрию относительно точки (0, 0) в меше, что сведёт всё к одномерному случаю плоской волны.
Теперь напишем шейдер. Я не буду разбирать каждый шаг написания шейдера, так как это не цель статьи, но за основу берётся Standard Surface Shader из Unity, шаблон которого можно получить через Create->Shader->StandardSurfaceShader.
Кроме этого, добавляются проперти необходимые для волнового уравнения: _Frequency, _WaveLength и _WaveHeight. Проперти _Timer (можно было бы использовать время с гпу, но при разработке и последующем анимировании удобнее его контролировать вручную.
Напишем функцию getHeight получения высоты (сейчас это координата Z) подставив уравнение окружности в волновое уравнение
Написав шейдер с нашим волновым уравнением и уравнением окружности — получим такой эффект.
Волны есть. Но хочется, чтобы анимация начиналась и заканчивалась плоскостью. В этом нам поможет функция синуса. Домножив амплитуду на sin(_Timer * PI) получим плавное появление и исчезновение волн. Так как _Timer принимает значения от 0 до 1, а синус в нуле и в PI равен нулю, это как раз то, что нужно.
Пока совсем не похоже на падение капли. Проблема в том, что энергия волной теряется равномерно. Добавим проперти _Radius, которая будет отвечать за радиус действия эффекта. И домножим на амплитуду clamp(_Radius — rad, 0, 1) и получим уже эффект больше похожий на правду.
Ну и заключительный шаг. То, что амплитуда в каждой отдельной точке достигает своего максимума в момент времени равный 0.5 не совсем верно, эту функцию лучше заменить.
Тут мне стало немного лень считать, и я просто домножил синус на (1 — _Timer) и получил такую кривую.
Но в общем с точки зрения математики тут так же можно подобрать нужную кривую исходя из логики в какой момент времени вы хотите пик и примерную форму, а дальше построить интерполяцию по этим точкам.
В итоге получился такой шейдер и эффект.
Сетка меша – это важно
Возвращаясь немного к теме предыдущей статьи. Волны реализованы вертексным шейдером, поэтому сетка меша играет достаточно большую роль. Так как известна природа движения задача упрощается, но в целом от формы сетки зависит финальный визуал. Разница становится не существенной при высокой полигональности, но для производительности чем меньше полигонов, тем лучше. Ниже картинки, иллюстрирующие разницу между сетками и визуалом.
Даже при вдвое большем числе полигонов второй меш даёт неправильный визуал (оба меша сгенерированы с помощь Triangle.Net, просто по разным алгоритмам).
В другую версию шейдера добавлена специальная часть для создания волн не строго в центре, а в нескольких точках. То, как это реализовано и каким образом можно передавать подобные параметры я может расскажу в следующих статьях, если тема интересна.
С проектом в целом и тем, как это работает можно ознакомиться тут. Правда часть ресурсов пришлось убрать из-за ограничений по весу гитхаба (hdr skybox и машина).
Спасибо за внимание! Надеюсь, статья будет кому-то полезна, и стало чуть понятнее зачем может понадобится тригонометрия, аналитическая геометрия (всё что связано с кривыми) и другие математические дисциплины.
Читают сейчас
Редакторский дайджест
Присылаем лучшие статьи раз в месяц
Скоро на этот адрес придет письмо. Подтвердите подписку, если всё в силе.
Похожие публикации
Расширение редактора Unity через Editor Window, Scriptable Object и Custom Editor
Математика в Gamedev по-простому. Векторы и интегралы
Работа с EventSystem в Unity. Базовые вещи в работе с UI
Средняя зарплата в IT
AdBlock похитил этот баннер, но баннеры не зубы — отрастут
Минуточку внимания
Комментарии 22
судя по всему, когда на видео странно шевелится асфальт — это оно.
В шейдере с несколькими полюсами сделано вообще неоптимально, лучше параметры полюса передавать в этом случае через vertexColor, а не через текстуру. В данном случае текстура не имеет особого смысла
А если ввести ещё одну текстуру и накладывать её на блеск, где есть эффект ряби, то пропадёт эффект «вся дорога под водой»?
Если делать эффект правильно, чтобы получилось красиво, можно пойти несколькими путями. Если дальний ракурс, то как говорили выше лучше не заморачиваться и пользоваться партиклами. Если же вблизи и нужно на какой-то меш наложить лужи с рябью, какие тут возникнут проблемы по ходу.
Разберём немного способы:
Отдельный меш поверх — плохая затея. С рефракцией и альфа блендингом самой волны можно будет чокнуться. Ну точнее ничего сверх естественного нет, но получится просто лишняя сложность.
О чём я думал, не назову это способом, просто что мне приходило в голову. Тут суть не в блеске. Если думать над тем, чтобы делать «быстро» без сложной фрагментной части в целом. У нас есть 8 параметров доступных для записи (4 координаты тангентов и вертекс колоров) Если делать процедурненький алгоритм, то мы просто записываем что-то вроде нормал мапа в тангенты, а данные о полюсах в вертекс колоры меша. Для того чтобы разбросать лужи — генерируем шум (поэтому в проекте можно найти простой генератор текстуры шума перлина, так как сама генерация интегрирована в юнити) Дальше, делаем лужи хайполи, а остальную геометрию лоуполи (процедурно — это не так сложно на самом деле, просто по шуму генерируем сетку меша специальным образом) По идее в данном случае нормали в вертексе в соответствии с «кривизной лужи» дадут правильный эффект. Шум станет картой отражений (так как лужа должна отражать сильнее, чем меш под ней) и должно получиться красиво и +- универсальненько. Но условно этот проект я собирал дня 4 где-то (так как надо ещё же найти всю информацию, которую я постарался структурировано описать), а вот всё что я описал писать ещё недельку, и мне пока не до того.