Как всем известно, 71% поверхности Земли занимает вода. К сожалению или к счастью, корректно изобразить океан умеют единицы. Иван Айвазовский вошел в учебники живописи благодаря одним только морским пейзажам. В компьютерных играх все еще сложнее. Когда-то море в них обозначали скоплением синих пикселей, раскрашенных белыми квадратами пены. Со временем виртуальные моря стали больше похожи на снимки из отпуска, научились качать волну и покрываться рябью, в которой иногда даже отражались очертания парусников. Но они оставались самостоятельной стихией: натолкнувшись на берег, волна превращалась в незамысловатые угловатые полигоны. Настоящий прибой логично взаимодействует с пляжем, увлажняет песок и с шуршанием откатывается назад. Такого правдоподобия удалось добиться только в современных играх. В том числе в нашем Skyforge. И хоть в основные события будут разворачиваться на суше, игроки попадут и на тропические острова, и в шумные порты. Вода будет постоянно рядом. Ее «правильный» облик будет играть большую роль. И воссоздание морской стихии – серьезная математическая задача. Расскажу об этапах ее реализации.
Особенности движка
В графическом движке [модуль игры или другого графического программного продукта, отвечающий за визуализацию] Skyforge используется технология deferred shading (см. рис 1): это означает, что визуализация сцены происходит в две стадии. Сначала происходит промежуточная запись в так называемый G-buffer: в отдельные текстуры записываются бликовая эмиссионная (a) и альбедо (б) компоненты материала объекта, буфер глубины [буфер, в котором вместо цвета записано значение глубины для каждой точки сцены] (в) и нормали (г). Затем для каждого источника света происходит расчет освещения конечной сцены с использованием информации из G-buffer’а (е).
Рисунок 1.
Deferred shading позволяет освещать сцену гораздо большим количеством источников света, чем при традиционной визуализации в один проход. Однако у подхода есть ограничения: например, он не позволяет корректно визуализировать полупрозрачную геометрию. В таких случаях обычно все непрозрачные объекты визуализируются в первый проход с использованием deferred shading’а (aka deferred pass), а остальные – во второй проход с использованием стандартного алгоритма освещения (aka forward pass).
В Skyforge вода визуализируется между deferred и forward pass’ами (д). С одной стороны, это позволяет получить необходимую информацию о глубине и освещенности, необходимую, например, для расчета преломления и отражения от воды. С другой стороны, эффекты, требующие корректного значения в буфере глубины (например, soft particles) корректно взаимодействуют с водой.
Немного о геометрии воды и модели освещения
В качестве геометрической модели мы используем полигональную сетку [способ представления трехмерных объектов, в котором для аппроксимации поверхности используется сетка, состоящая из многоугольников(треугольников)] с неравномерной детализацией. На рисунке 2 показана полигональная сетка воды (а) и соответствующий ей результат визуализации (б). Детализация сетки возрастает обратно пропорционально расстоянию от точки воды до камеры. Волны (глубоководные и прибой) моделируются смещением вершин сетки по вертикальной оси.
Рисунок 2.
После расчета поверхности воды производится ее освещение. Мы пользуемся моделью освещения, которая учитывает следующие факторы (рис. 3):
• отражение света от поверхности воды (а);
• блик от аналитического источника света (б);
• пена (в);
• подповерхностное рассеяние света (г);
• поглощение света по глубине(д);
• преломление света в толще воды (е);
• каустики [световые пятна, образующиеся на одной поверхности за счет преломления или отражения от другой поверхности] от источника света на дне (ж).
Рисунок 3.
Моделирование прибоя
Упрощенно, прибой – это волны, накатывающие на берег. Для того, чтобы создать волны, движущиеся в определенном направлении, мы воспользовались уравнением бегущей волны:
где A(z,t) – амплитуда волны, k – волновое число, φ – фаза, z – координата, вдоль которой бежит волна. Пример такой волны (с постоянной амплитудой) показан на рисунке 4. Со временем и фазой все более-менее понятно, а как заставить эти волны бежать к берегу в каждой точке?
Рисунок 4.
Для этого мы задаем z значением из поля расстояний. Поле расстояний – это скалярное поле, значение в каждой точке которого равно расстоянию до заданного «препятствия». В нашем случае «препятствие» — это ландшафт, находящийся выше уровня океана. Мы вычисляем поле расстояний заранее и при загрузке карты загружаем эту информацию в текстуру.
Если попробовать наш метод в деле, результат будет совсем не похож на действительность (рис. 5а). Проблема в том, что волны везде идут одинаковым непрерывным фронтом: параметр z меняется только по направлению от берега, поэтому на изолиниях поля расстояния высота волны h(z,t) будет одинаковой. Мы решили эту проблему, создав маску (5б), на которую умножается амплитуда волн:
где x,y – координаты, μ – коэффициент, определяющий длину сегментов волн, ϑ – скорость появления/исчезания волн в данном фронте.
Эта маска плавно меняется во времени, чтобы распределение волн было равномерным. Также генерируется второй фронт волн, отличающийся от первого только выбором фазы φ'. Кроме того, волны плавно появляются на глубине и плавно исчезают, достигая берега – это достигается выбором функции A(z,t).
Таким образом, мы получили формулу, по которой в любой точке океана мы можем узнать амплитуду волны, набегающей на берег (5в). Это позволяет производить весь расчет геометрии волн прибоя в вершинном шейдере [программа, выполняемая на графическом ускорителе для каждой вершины полигональной сетки объекта] GPU.
Рисунок 5.
Форма волны
Настоящие волны не бывают синусоидальными. При приближении к берегу волны становятся все более асимметричными: спереди собирается барашек, а сзади вытягивается пологий шлейф. Наконец, верхняя часть волны начинает нависать над нижней, волна переламывается и выплескивается на берег.
Мы достигаем эффекта асимметричности и нависания верхней части над нижней путем смещения вершин волны по направлению к берегу в горизонтальной плоскости: чем выше вершина над поверхностью воды, тем больше она смещается. Найти вектор направления к берегу можно, взяв градиент от поля расстояний. На рисунке 6 пунктиром показано такое преобразование, примененное к синусоидальной волне.
Рисунок 6.
Градиенты вычисляются заранее и записываются в текстуру поля расстояний, чтобы уменьшить количество выборок из текстуры в вершинном шейдере.
Пена
Еще одна неотъемлемая часть прибоя – пена. Она появляется на гребне волн и при накате волн на берег. После того, как волна разбилась о берег, пена откатывается обратно, навстречу следующей волне.
Для создания этих эффектов нам пришлось использовать различные техники. Имитации движения к берегу и от него симулируется скроллингом текстурных координат [положение в текстуре, откуда нужно взять значение для данной вершины/пиксела] по вектору градиента поля расстояний (и, соответственно, в обратном направлении). Однако если в каждой точке постоянно сдвигать текстурные координаты, образуются потяжки текстуры из-за неравномерности таких сдвигов. Мы решаем эту проблему следующим способом: скроллятся две текстурные в противофазе, при каждой выборке происходит интерполяция. Как только одна из текстурных смещается больше, чем на период, она сбрасывается — при этом ее коэффициент интерполяции равен 0, поэтому это происходит незаметно.
Пена по волне также распределяется асимметрично. Перед волной пены нет, на гребне волны она очень яркая, а позади тянется плавно затухающий шлейф. Для этого мы используем следующую формулу для интенсивности пены (не забудьте привести аргумент в корректный диапазон):
Все параметры, кроме фазы, совпадают с параметрами волны. Фазу можно немного сдвигать для достижения нужного эффекта.
За набегающей волной пена бежит вперед. Симулировать геометрический откат волны в нашем случае довольно сложно: будет нарушаться уравнение бегущей волны и топология сетки. Мы симулируем этот эффект, накладывая вблизи берега перед набегающей волной пену, скроллящуюся навстречу волне.
Мокрые поверхности
Без эффекта намокания песка на берегу вода выглядит неестественно, отдельно от ландшафта. При намокании песок темнеет и начинает блестеть.
Решение в лоб: подкрашивать вручную прибрежные зоны более темным песком. Однако у этого решения есть недостатки: на таком песке не будет видно бликов, разрешение покраски будет низким, потребуется дополнительная работа дизайнеров уровней.
Рисунок 7.
Мы реализуем это следующим образом. Вблизи берега поверхность воды приподнимается немного выше уровня ландшафта, как бы создавая «тонкую пленку» поверх (см. рис 7). Затем при расчете преломления реальный цвет ландшафта немного затеняется, а поверх накладывается блик. На рисунке 8 показано влияние эффекта мокрого песка на финальную картинку.
Рисунок 8.
Благодарю за внимание. Я рассказал о тонкостях создания водной стихии лишь в общих чертах. На самом деле есть еще тысяча подробностей и мелочей. Все вместе они создают превосходную водную стихию. Могу сказать честно, нашим прибоем я горжусь. Это легко проверить. Всмотритесь в скриншот.
Море решений
1. Как расходятся круги от плывущего человека? Бывает ли пена? Чтобы это понять, нужны референсы — примеры из жизни. Мы часами смотрели на YouTube видео, в которых дети брызгаются у берега или какие-то парни забираются друг другу на плечи и бомбочкой прыгают в воду. Каждый месяц просили отправить нас в экспедицию на море — разумеется, только для сбора фактов! Начальство смеялось, но просьбы игнорировало.
2. Мы хотели, чтобы волны натуралистично набегали берег. Но берег может быть любой формы. На севере огромного острова волны придут с севера, на юге – с юга… Как все это учесть? Мы посчитали для каждой точки океана distance field – расстояние и вектор до ближайшей точки берега. Вдоль него и бежит волна.
3. Пена — неотъемлемая часть прибоя. Она появляется на гребне волн и при накате волн на берег. После того, как волна разбилась о берег, пена откатывается обратно, навстречу следующей волне. Чтобы создать убедительную пену, пришлось немало помучаться — использовать техники вроде скроллинга текстурных координат по вектору градиента поля расстояний, решать проблему потяжки текстуры и творить прочую черную магию.
4. При намокании песок темнеет и начинает блестеть. Без этого эффекта прибой выглядит неестественно. Сначала мы хотели подкрашивать вручную прибрежные зоны более темным песком, но, во-первых, это усложняло работу дизайнеров, а во-вторых, на таком песке не видно бликов. Пришли к элегантному решению: у берега вода создает тонкую пленку, затеняя реальный цвет ландшафта, а поверх накладывается солнечный блик.
Литература
[1] E. Darles, B. Crespin, D. Ghazanfarpour, J. C. Gonzato. A Survey of Ocean Simulation and Rendering Techniques in Computer Graphics. Ссылка
[2] Tiago Sousa. Crysis Next Gen Effects. GDC 2008. Ссылка
[3] Carlos Gonzalez-Ochoa. Water Technology of Uncharted. GDC 2012.
[4] Brano Kemen. Ocean Rendering in Outerra. Ссылка
[5] Mike Seymour. Assassin’s Creed III: The ech behind (or beneath) the action. Ссылка
Петр Сикачев,
графический программист Skyforge
Автор: PeterSikachev