Реализация Физически Правильных Объемных Облаков Как В Игре Horizon Zero Dawn.

Раньше облака в играх рисовались обычными 2D-спрайтами, которые всегда были повёрнуты в сторону камеры, но в последние годы новые модели видеокарт позволяют рисовать физически правильные облака без заметных потерь в производительности.

Считается, что студия Guerrilla Games привнесла в игры объёмные облака вместе с игрой Horizon Zero Dawn. Конечно, такие облака умели рендерить и раньше, но студия сформировала что-то вроде индустриального стандарта исходных ресурсов и используемых алгоритмов, и на данный момент любая реализация объёмных облаков так или иначе соответствует этому стандарту.

УПД.

Изображение обновлено.

Изменения описаны в конце статьи.



Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Весь процесс рендеринга облаков очень четко разделен на этапы и важно отметить, что неаккуратная реализация даже в одном из них может привести к таким последствиям, что будет непонятно, где ошибка и как ее исправить, поэтому желательно каждый раз делать контрольный вывод результата.



Отображение тонов, sRGB

Прежде чем приступить к работе с освещением, важно сделать две вещи:
  1. Прежде чем вывести на экран итоговое изображение, примените хотя бы простейшую тональную компрессию:
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
      
       

    tunedColor=color/(1+color)

    Это необходимо, поскольку рассчитанные значения цвета будут намного больше единицы.

  2. Убедитесь, что окончательный буфер кадров, который вы рисуете и который будет представлен на экране, имеет формат sRGB. Если есть проблемы с активацией режима sRGB, преобразование можно выполнить вручную в шейдере:

    finalColor=pow(color, vec3(1.0/2.2))

    Формула будет работать в большинстве случаев, но не на 100% в зависимости от монитора.

    Важно, чтобы преобразование sRGB всегда выполнялось последним.



Модель освещения

Рассмотрим пространство, заполненное частично прозрачным веществом разной плотности.

Когда луч света проходит через такое вещество, он подвергается четырем эффектам: поглощению, рассеянию, усилению рассеяния и самоизлучению.

Последняя возникает при химических процессах, происходящих в веществе, и здесь не затрагивается.

Допустим, у нас есть луч света, который проходит через вещество из точки А в точку Б:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Поглощение Свет, проходящий через вещество, поглощается этим самым веществом.

Непоглощенную долю света можно найти по формуле:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Где

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— свет, оставшийся после поглощения в точке

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

.



Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— точка на отрезке АВ на расстоянии

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

из.

Диффузия Часть света меняет свое направление под воздействием частиц вещества.

Долю света, не изменившую своего направления, можно найти по формуле:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Где

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

- доля света, не изменившая своего направления после рассеяния в точке

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

.

Поглощение и диссипация должны сочетаться:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Функция

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

называется ослаблением или угасанием.

Функция

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

- функция передачи.

Он показывает, сколько света останется при переходе из точки А в точку Б.

Касательно

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

И

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

, где C — некая константа, которая может иметь разное значение для каждого канала в RGB,

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— плотность среды в точке

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

.

Теперь усложним задачу.

Свет движется из точки А в точку Б, по мере движения он тускнеет. В точке Х часть света рассеивается в разные стороны, одно из направлений соответствует наблюдателю в точке О.

Далее часть рассеянного света перемещается из точки Х в точку О и снова затухает. Интересующий нас путь света — AXO.

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Мы знаем потерю света при движении от А к Х:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

, так же, как мы знаем потерю света от Х до О - это

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

.

А как насчет доли света, которая рассеется в направлении наблюдателя? Улучшенное рассеяние Если при обычном рассеянии интенсивность света уменьшается, то при усиленном рассеянии она увеличивается за счет рассеяния света, происходящего на соседних участках.

Общее количество света, поступающего из соседних регионов, можно найти по формуле:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Где

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

означает взятие интеграла по сфере,

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— фазовая функция,

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

- свет, идущий с определенного направления

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

.

Сосчитать свет со всех сторон довольно сложно, однако мы знаем, что основную часть света несет наш первоначальный луч АВ.

Формулу можно существенно упростить:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Где

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— угол между лучом света и лучом наблюдателя (т. е.

угол AXO),

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— начальное значение силы света.

Суммируя все вышесказанное, получаем формулу:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Где

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

- входящий свет,

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

- свет, достигающий наблюдателя.

Еще немного усложним задачу.

Допустим, свет излучается источником света направленного типа, то есть солнцем:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Происходит то же самое, что и в предыдущем случае, но много раз.

Свет из точки А1 рассеивается в точке Х1 в сторону наблюдателя в точке О, свет из точки А2 рассеивается в точке Х2 в сторону наблюдателя в точке О и т. д. Мы видим, что свет, достигающий наблюдателя, равен сумме:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Или более точное интегральное выражение:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Важно понимать, что здесь

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

, т.е.

отрезок разбит на бесконечное число участков нулевой длины.



Небо

При небольшом упрощении солнечный луч, проходя через атмосферу, испытывает лишь рассеяние, т.е.



Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

.



Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

И даже не один тип рассеяния, а целых два типа: рэлеевское рассеяние и рассеяние Ми.

Первый вызван молекулами воздуха, а второй водным аэрозолем.

Общая плотность воздуха (или аэрозоля), через который пройдет луч света, двигаясь из точки А в точку Б:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Где

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— высота масштабирования, h — текущая высота.

Простое решение интеграла будет:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

где dh — размер шага, с которым производится выборка высоты.

Теперь посмотрим на рисунок и воспользуемся формулой, полученной в предыдущей части «модель освещения»:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Наблюдатель смотрит от О к О'.

Мы хотим собрать весь свет, который дойдет до точек X1, X2,.

, Xn, рассеется в них, а затем достигнет наблюдателя:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Где

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

интенсивность света, излучаемого солнцем,

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

— высота в точке

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

; в случае неба константа C, находящаяся в функции

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

обозначается как

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

.

Решением интеграла может быть:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Эта формула справедлива как для рассеяния Рэлея, так и для рассеяния Ми.

В результате значения освещенности для каждого из рассеяний просто складываются:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Рэлеевское рассеяние

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.



Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

(содержит значения для каждого канала RGB)

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Результат:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Рассеяние Ми

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.



Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

(значения для всех каналов RGB одинаковы)

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Результат:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Количество образцов на сегмент

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

и на сегменте

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

можно брать 32 и выше.

Радиус Земли 6 371 000 м, толщина атмосферы 100 000 м.

Что со всем этим делать:

  1. В каждом пикселе экрана вычисляем направление наблюдателя V
  2. Позицию наблюдателя O примем равной {0,6371000,0}.

  3. Мы нашли

    Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

    в результате пересечения луча, исходящего из точки О, и направления V и сферы с центром в точке {0,0,0} и радиусом 6471000
  4. Отрезок

    Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

    разделен на 32 секции одинаковой длины
  5. Для каждого раздела мы вычисляем рассеяние Рэлея и рассеяние Ми и все суммируем.

    Более того, для расчета

    Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

    нам также нужно разделить сегмент

    Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

    в каждом случае на 32 равные секции.



    Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

    можно прочитать через переменную, значение которой увеличивается на каждом шаге цикла.

Конечный результат:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.



Облачная модель

Нам понадобится несколько типов шума в 3D. Первый — это мозаичный фрактальный броуновский шум Перлина (fBm): Результат для 2D-среза:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Второй — скрытый шум Вороного fBm. Результат для 2D-среза:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Чтобы получить мозаичный шум Вороного fBm, необходимо инвертировать мозаичный шум Вороного fBm. Однако я немного изменил диапазоны значений на свое усмотрение:

float fbmTiledWorley3(.

) { return clamp((1.0-fbmTiledVoronoi3(.

))*1.5-0.25, 0.0, 1.0); }

Результат сразу напоминает облачные структуры:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Для облаков вам нужно получить две специальные текстуры.

Первый имеет размер 128х128х128 и отвечает за низкочастотный шум, второй имеет размер 32х32х32 и отвечает за высокочастотный шум.

Каждая текстура использует только один канал в формате R8. В некоторых примерах используются 4 канала R8G8B8A8 для первой текстуры и три канала R8G8B8 для второй, а затем смешиваются каналы в шейдере.

Я не вижу в этом смысла, ведь смешивание можно сделать заранее, тем самым получив большую когерентность кэша.

Для смешивания, а также в некоторых других местах будет использоваться функция remap(), которая масштабирует значения из одного диапазона в другой:

float remap(float value, float minValue, float maxValue, float newMinValue, float newMaxValue) { return newMinValue+(value-minValue)/(maxValue-minValue)*(newMaxValue-newMinValue); }

Начнем подготовку текстуры с низкочастотным шумом: R-канал — скрытый шум Перлина fBm G-канал – скрытый шум ФБМ Ворли B-канал — мозаичный шум Уорли fBm с меньшим масштабом A-канал — тайловый fBm шум Ворли с еще меньшим масштабом

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Смешивание производится следующим образом:

finalValue=remap(noise.x, (noise.y * 0.625 + noise.z*0.25 + noise.w * 0.125)-1, 1, 0, 1)

Результат для 2D-среза:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Теперь готовим текстуру с высокочастотным шумом: R-канал – скрытый шум ФБМ Ворли G-канал — мозаичный шум Уорли fBm с меньшим масштабом B-канал — тайловый fBm шум Ворли с еще меньшим масштабом

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.



finalValue=noise.x * 0.625 + noise.y*0.25 + noise.z * 0.125;

Результат для 2D-среза:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

Также нам понадобится 2D-текстура-карта погоды, которая будет определять наличие, плотность и форму облаков в зависимости от пространственных координат. Художники рисуют его, чтобы точнее откорректировать облачность.

Трактовка цветовых каналов карты погоды может быть разной, в том варианте, который я позаимствовал, она следующая:

Реализация физически правильных объемных облаков как в игре Horizon Zero Dawn.

R-канал – облачность на малой высоте G-канал – облачность на большой высоте B-канал – максимальная высота облаков А-канал – плотность облаков Теперь мы готовы написать функцию, которая будет возвращать плотность облаков в зависимости от координат 3D-пространства.

На входе точка пространства с координатами в км.



vec3 position

Сразу добавим смещение ветром

position.xz+=vec2(0.2f)*ufmParams.time;

Получение значений карты погоды

vec4 weather=textureLod(ufmWeatherMap, position.xz/4096.0f, 0);

Получаем процент высоты (от 0 до 1)

float height=cloudGetHeight(position);

Добавьте небольшое закругление облаков ниже:

float SRb=clamp(remap(height, 0, 0.07, 0, 1), 0, 1);

Делаем линейное уменьшение плотности до 0 с увеличением высоты по B-каналу карты погоды:

float SRt=clamp(remap(height, weather.b*0.2, weather.b, 1, 0), 0, 1);

Объединяем результат:

float SA=SRb*SRt;

Еще раз добавьте закругление облаков ниже:

float DRb=height*clamp(remap(height, 0, 0.15, 0, 1), 0, 1);

Также добавляем закругление облаков вверху:

float DRt=height*clamp(remap(height, 0.9, 1, 1, 0), 0, 1);

Совмещаем результат, и сюда добавляем влияние плотности с карты погоды и влияние плотности, которая задается через gui:

float DA=DRb*DRt*weather.a*2*ufmProperties.density;

Соединяем низкочастотный и высокочастотный шум из наших текстур:

float SNsample=textureLod(ufmLowFreqNoiseTexture, position/48.0f, 0).

x*0.85f+textureLod(ufmHighFreqNoiseTexture, position/4.8f, 0).

x*0.15f;

Все документы, которые я читал, имеют разные способы слияния, но этот мне понравился.

Определяем степень покрытия (% неба, занятого облаками), которая задается через gui; Также используются R-, G- каналы карты погоды:

float WMc=max(weather.r, clamp(ufmProperties.coverage-0.5, 0, 1)*weather.g*2);

Рассчитаем конечную плотность:

float d=clamp(remap(SNsample*SA, 1-ufmProperties.coverage*WMc, 1, 0, 1), 0, 1)*DA;

Вся функция:

float cloudSampleDensity(vec3 position) {

Теги: #Разработка игр #математика #Алгоритмы #программирование #Работа с 3D-графикой #объемный рендеринг #шум Перлина #физически обоснованный рендеринг #реалистичные облака #реалистичное небо #Horizon Zero Dawn

Вместе с данным постом часто просматривают: