[Перевод] Как рендерит кадр движок Metal Gear Solid V: Phantom Pain

mxfxy1jasfw-jnoib8mmsue8ids.png


Серия игр Metal Gear получила мировое признание после того, как почти два десятилетия назад Metal Gear Solid стала бестселлером на первой PlayStation.

Игра познакомила многих игроков с жанром «тактического шпионского экшена» (tactical espionage action), название которого придумал создатель франшизы Хидео Кодзима.

Но лично я впервые играл за Снейка не в этой части, а в Ghost Babel, спин-оффе для консоли GBC, менее известной, и тем не менее превосходной игре с впечатляющей глубиной.

Последняя часть франшизы, Metal Gear Solid V: The Phantom Pain, была выпущена в 2015. Благодаря движку Fox Engine, созданному Kojima Productions, она подняла всю серию на новый уровень визуального качества.

Представленный ниже анализ основан на PC-версии игры с максимальными настройками графики. Часть изложенной здесь информации уже стала достоянием публики после доклада «Photorealism Through the Eyes of a FOX» на GDC 2013.

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

Снейк лежит на полу, стремясь слиться с окружающими его трупами (он находится в нижней части экрана, с голым плечом).

Это не самая красивая сцена, но она неплохо иллюстрирует различные эффекты, которые может создавать движок.

a22a4c3e3069a7ee6b5a02f2e611458d.jpg


Прямо перед Снейком стоят двое солдат. Они смотрят на горящий силуэт в конце коридора.

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

Итак, давайте же посмотрим, как рендерится кадр!

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

Предварительный проход глубин


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

Ниже можно увидеть генерируемый из карты высот меш рельефа: это 16-битная текстура с плавающей точкой, содержащая значения высоты рельефа (из вида сверху). Движок разделяет карту высот на различные тайлы, и для каждого тайла выполняется вызов отрисовки с плоской сеткой 16×16 вершин. Вершинный шейдер считывает карту высот и на лету изменяет положения вершин, чтобы они соответствовали значению высоты. Рельеф растеризируется примерно за 150 вызовов отрисовки.

4b371de27511fc2b69ac227f71c427cf.jpg


Высота рельефа

5d808d89898955e19b1a33a231bd59c5.jpg


Карта глубин: 5%

8e0e8ad931bcfddae6292060ae13a5cc.jpg


Карта глубин: 10%

6bbbd836ace3aef4915fdbbb40705194.jpg


Карта глубин: 40%

d94874750ea3d1c944da32df3b4f2945.jpg


Карта глубин: 100%

Генерирование G-буфера


В MGS V, как и во многих играх этого поколения, используется отложенный рендеринг. Если вы читали мой анализ GTA V (перевод на Хабре), то можете заметить похожие элементы. Итак, вместо непосредственного вычисления конечного значения освещения каждого пикселя в процессе рендеринга сцены, движок сначала сохраняет свойства каждого пикселя (типа цветов albedo, нормалей и т.д.) в нескольких целевых рендерах, называемых G-буфером, и позже комбинирует всю эту информацию.

Все нижеуказанные буферы генерируются одновременно:

Генерирование G-буфера: 25%

9fcd62c7313bb802a772abcfad9ab2a7.jpg


Albedo

54d1531bcf25d76c717755a4597bab93.jpg


Нормали

d48f7740d19ad32e7891b4dcb4a9f5f8.jpg


Зеркальность (specular)

bc58f4f6a0e5357fe933e13a7b46ed84.jpg


Глубины

Генерирование G-буфера: 50%

029cd6c162b7dd160c2ed6a4ed5f35b4.jpg


Albedo

390072e779c65ab2d5a7b7b8b2f45605.jpg


Нормали

3d2f1f97a08d332795cadd37c8778631.jpg


Зеркальность

59b72e8b71348e377ea24806fe773a77.jpg


Глубины

Генерирование G-буфера: 75%

15737e64f22f5b118d8a29c811521ddf.jpg


Albedo

871562fd772f6e2dc92d47bb4d03b403.jpg


Нормали

1350a0a0c3d13650d88c4c111b79ab1d.jpg


Зеркальность

47322977bd44eee50f83025ccf3a8304.jpg


Глубины

Генерирование G-буфера: 100%

36d49910ef49351cd26bef6674c7aa90.jpg


Albedo

b897b382c1145db7e3335aede590e9df.jpg


Нормали

4d46e735c159a1711432f129206986f9.jpg


Зеркальность

a13b237e34e598370856331530f51965.jpg


Глубины

Здесь у нас получается относительно лёгкий G-буфер с тремя целевыми рендерами в формате B8G8R8A8:

  • Карта Albedo: каналы RGB содержат диффузный цвет albedo мешей, то есть собственный цвет, к которому не применено никакое освещение. Альфа-канал содержит значение непрозрачности/«светопроницаемости» материала (обычно 1 для полностью непрозрачных объектов и 0 для травы или листвы).
  • Карта нормалей: вектор нормали (x, y, z) пикселя хранится в каналах RGB. В альфа-канале содержится коэффициент шероховатости, зависящей от угла обзора, для некоторых материалов.
  • Карта зеркальности (specular map):
    • Red: шероховатость (roughness)
    • Green: зеркальные отражения (specular)
    • Blue: идентификатор материала
    • Alpha: просвечиваемость для подповерхностного рассеивания (похоже, это относится только к материалам человеческой кожи и волос)
  • Карта глубин: 32-битное значение float, обозначающее глубину пикселя. Глубина перевёрнута (значение 1 имеют меши рядом с камерой), чтобы сохранить высокую точность чисел с плавающей точкой для далёких объектов и избежать Z-конфликтов. Это важно для игр с открытым миром, в которых расстояние прорисовки может быть очень большим.
b3e8806d25efcf9527c2246196615ef7.png


Рендеринг G-буфера выполняется в следующем порядке: сначала все непрозрачные меши основной сцены (персонажи, здание больницы и т.д.), затем весь рельеф (снова) и, наконец, декали.

Именно здесь пригождается предварительный проход глубин: он делает второй этап (рендеринг рельефа) очень быстрым. Каждый пиксель рельефа, перекрываемый другим мешем, не будет иметь ожидаемую глубину, предсказанную предварительным проходом глубин, и в таком случае он мгновенно отбрасывается без необходимости получения относящихся к рельефу текстур и записи этих данных обратно в G-буфер.

Карта скоростей


Для применения на этапе постобработки эффекта размытия в движении необходимо знать скорость каждого пикселя на экране.

Если сцена полностью статична, то узнать скорость каждой точки достаточно просто: она выводится из глубины и разности матрицы проекции между предыдущим и текущим кадрами. Но всё становится сложнее, когда в кадре присутствуют динамические объекты, например бегающие персонажи, потому что они могут двигаться независимо от камеры.

Здесь в дело вступает карта скоростей: она хранит векторы движения (скорости) каждого пикселя текущего кадра.

790375e164696e51ab307de239b6ee12.png


Карта скоростей (динамические меши)

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

Можно заметить, что в нашей сцене динамическим мешем считается только человек в огне. Даже если Снейк и солдаты технически не являются статичными мешами, движок обрабатывает их таким образом, и в нашем случае это вполне приемлемо, потому что они едва подвижны. Благодаря этому движок может избежать части вычислений: для вычисления скоростей анимированных персонажей вершинный скиннинг нужно выполнять дважды (для предыдущей и текущей позы), что может быть довольно затратно.

Красный канал используется в качестве маски (имеет значение 1 там, где отрисовывается персонаж), а сам вектор скорости записывается в синий и альфа-канал. Человек в огне не движется, поэтому его динамическая скорость равна (0, 0).

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

637a696351b58f0668cacf0b762632a6.jpg


Карта скоростей (статичная + динамическая)

Не обращайте особого внимания на шум, в этой сцене почти нет движения: камера медленно выполняет наезд на человека в огне, все пиксели имеют почти нулевую скорость, а то, что вы видите, является ошибками точности округления при записи компонентов в 8-битные каналы. Я усилил цвета, чтобы сделать изображение более отчётливым. Также я поменял местами зелёный и альфа-канал, настоящий буфер хранит скорость в синем и альфа-канале.

Преграждение окружающего света в экранном пространстве (Screen Space Ambient Occlusion)


Эффект SSAO должен добавлять немного затемнения в областях, в которых меньше окружающего освещения, например, в узких отверстиях или в складках. Интересно, что Fox Engine выполняет два отдельных прохода SSAO, используя разные алгоритмы и комбинируя результаты в последнем проходе.

SSAO на основе линейных интегралов


SSAO на основе линейных интегралов (Line Integral SSAO) — это техника вычисления ambient occlusion, которую Avalanche Software использовала в диснеевской игре Toy Story 3.

Несмотря на устрашающее название, этот алгоритм достаточно понятен и хорошо объяснён в этом докладе на Siggraph 2010: для каждого пикселя сцены берётся сфера с центром в этом пикселе; этот сферический объём затем подразделяется на несколько линейных подобъёмов. Коэффициент окклюзии каждого подобъёма вычисляется получением единственного значения из карты глубин, а общий коэффициент окклюзии сферы является просто взвешенной суммой коэффициентов каждого из подобъёмов.

Здесь Fox Engine использует две пары симметричных сэмплов, то есть всего в оригинальный сэмпл включается пять значений буфера глубин на пиксель.

e77f7241cb228de66d22709729b2d3d6.jpg


RGB: линейная глубина

85b019538b4b491d3ff3bd32daa08e1c.jpg


Альфа: LISSAO

Вычисление выполняется при половинном разрешении, а данные сохраняются в текстуре RGBA8, где альфа-канал содержит действительный результат ambient occlusion, а в RGB хранится значение линейной глубины (используется кодирование Float-to-RGB, похожее на эту технику).

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

Масштабируемое затемнение окружающего освещения (Scalable Ambient Obscurance)


a1edb89fbc70b6d650a91d23592588ed.jpg


SAO

Во втором проходе вычисления SSAO используется вариация техники Scalable Ambient Obscurance.

Она отличается от «официального» SAO в том, что не использует никакие mip-уровни глубин и не реконструирует нормали; она считывает непосредственно саму карту высот и работает на половинном разрешении, выполняя 11 считываний на пиксель (однако применяя другую стратегию выбора мест сэмплов).

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

Заметьте, что параметры SAO изменены таким образом, чтобы высокочастотные вариации (например, на ногах солдата) сильно выделялись по сравнению с версией для LISSAO.

Так же, как и в LISSAO, карта SAO размывается двумя побочными проходами с учётом глубин.

После этого compute shader комбинирует изображения LISSAO и SAO, получая конечный результат SSAO:

2ced4a35d2ea57f0f7b8fff9dfd182e3.jpg


Готовое SSAO

Сферические карты освещённости


Для работы с глобальным освещением движок Fox Engine применяет локальные сферические карты освещённости: на игровом уровне задаются различные области, и для каждой из этих областей создаётся сферическая карта, аппроксимирующая освещённость, поступающую с различных направлений.

4939a6402ce7b14eb1d94e020a3a4bbf.png


Сферические карты освещённости

На этом этапе одна за другой генерируются все сферические карты, используемые в нашей сцене, после чего каждая из них сохраняется в тайл 16×16 атласа HDR-текстур. Этот атлас текстур показан выше: диск посередине каждого тайла является приблизительным представлением того, что отражала бы металлическая сфера, расположенная посередине соответствующей ей зоны освещённости.

Как же генерируются эти сферические карты? Они вычисляются из сферических гармоник. Лежащая в основе вычислений математика довольно пугающа, но, в сущности, сферические гармоники являются способом кодирования значения 360-градусного сигнала во множество коэффициентов (обычно их девять), создающих достаточно хорошую точность (SH второго порядка).

И всего из девяти этих чисел можно приблизительно воссоздать значение сигнала в любом направлении.

Если вам знакома концепция того, как преобразование Фурье может разбивать сигнал на синусоидные компоненты, то здесь ситуация довольно похожая, за исключением того, что мы разлагаем сигнал на функции на поверхности сферы.

Откуда же берутся эти коэффициенты? Они вычисляются предварительно — я предполагаю, что окружение каждой области, размеченное дизайнерами уровней, записывается в кубическую карту. Затем она преобразуется в кубическую карту освещённости и кодируется в коэффициенты сферических гармоник, которые считывает движок во время выполнения игры.

Можно задаться вопросом: почему бы не использовать для определения освещённости сами кубические карты? Это может сработать, кубические карты освещённости применять возможно, но у них есть свои недостатки. Самый главный — лишняя трата памяти на хранение шести граней кубической карты, в то время как сферические гармоники снижают затраты всего до девяти значений RGB на карту. Этим экономится много места в памяти и пропускной способности GPU, что очень важно, когда приходится иметь дело с десятками карт в сцене.

Все эти сферические карты генерируется в каждом кадре из предварительно запечённых коэффициентов сферических гармоник и текущего положения и направления камеры игрока.

Диффузное освещение (Global Illumination)


Настало время применить все эти сгенерированные карты освещённости! Каждая зона, на которую влияет карта освещённости, передаётся для растеризации видеопроцессору. Обычно каждый вызов отрисовки (по одному на карту освещённости) отправляет меш в форме параллелепипеда, представляющий собой объём влияния карты в мире. Идея заключается в том, что он должен касаться всех пикселей, на которые может повлиять конкретная карта освещённости.

Диффузная карта вычисляется в HDR-текстуре половинного разрешения считыванием из карт нормалей, глубин и освещённости.

b897b382c1145db7e3335aede590e9df.jpg


Нормали

a13b237e34e598370856331530f51965.jpg


Глубины

4939a6402ce7b14eb1d94e020a3a4bbf.png


Освещённость

9daae424a37c337fd2c2815b538ffac0.jpg


Диффузное освещение (GI): 15%

fd269272c01cb35d7d94ceadccbd440f.jpg


Диффузное освещение (GI): 30%

eec52ba7d4aaa426b9fd7f814aae073a.jpg


Диффузное освещение (GI): 80%

09d20805590d09b67b5a7744616c65bb.jpg


Диффузное освещение (GI): 100%

Процесс повторяется для каждой карты освещённости с аддитивным смешением новых фрагментов поверх старых.

После накопления в диффузном буфере всего освещения, внесённого глобальным освещением, он масштабируется с половинного на полное разрешение. Стоит заметить, что повышение масштаба не является наивной билинейной фильтрацией, это двунаправленный фильтр, который считывает буфер половинного разрешения, а также, что более важно, исходную карту глубин полного разрешения (чтобы привязать веса к соседним цветовым пикселям), поэтому конечный результат всё равно содержит ломаные рёбра вокруг границ мешей. Визуально это выглядит так, как будто мы всё это время работали в полном разрешении!

3a575f8634fbd0ca369a44ba7c7c9129.png


Увеличение масштаба 2x (без фильтрации)

5e56d0e14ec419599b21f81e399461bb.png


Билинейное увеличение масштаба 2x

392904606c0f9c98860ce6950e4bf873.png


Двунаправленное увеличение масштаба 2x

Источники освещения, не отбрасывающие тень


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

4b901b6ebaf79b1a74c7bc2c97596f31.jpg


Диффузное освещение: 5%

6411c6b2c1e09c8941772af9a1f4889c.jpg


Диффузное освещение: 30%

6ac5773f1678c02607c57f09573dde75.jpg


Диффузное освещение: 60%

881f6bdfc67f388cee39548749020b10.jpg


Диффузное освещение: 100%

На самом деле одновременно с обновлением буфера диффузного освещения рендерится ещё один целевой HDR-рендер полного разрешения: буфер отражённого освещения. Каждый показанный выше вызов отрисовки источника освещения на самом деле одновременно выполняет запись в буфер диффузного и отражённого освещения.

46dfa18ba6dac49810466f44e5d474a7.jpg


Отражённое освещение: 5%

4fdb6abd34e6c9450bb2aa39f2ddaf67.jpg


Отражённое освещение: 30%

a35c9dcba8b64c0624066712eb517f36.jpg


Отражённое освещение: 60%

559d11b2c0119195dcc08a53746880ac.jpg


Отражённое освещение: 100%

Карты теней


Можно догадаться, чем мы займёмся после источников без теней: источниками освещения, отбрасывающими тени!

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

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

249d2c9068768d3cb1694d97cbd9b079.jpg


Две карты теней

Источники освещения, отбрасывающие тень


После завершения генерирования карт теней вычисляется освещение от двух направленных источников на потолке. Буферы диффузного и отражённого освещения обновляются одновременно. Наконец, применяется освещение от солнца (из ранее сгенерированной сферической карты сферических гармоник).

b47c3e055a20fdf4318d197dc3ce98d9.jpg


Диффузное освещение 0%

5fc99514d0e2289761a8c7e2a69fc4d9.jpg


Отражённое освещение 0%

c500e15a89040eb61a0510aa83d869f0.jpg


Диффузное освещение 30%

4b81004d6e91ef58c1a562639c5a8da3.jpg


Отражённое освещение 30%

959cab54999c227928c532ab6c386ee6.jpg


Диффузное освещение 70%

2947cd18cd70e7f757782f397edb2232.jpg


Отражённое освещение 70%

155f2cf06a44ebc2ce188f18c7e1dddb.jpg


Диффузное освещение 100%

61b858e0ee9a80e64eaa5969c547fe2b.jpg


Отражённое освещение 100%

Комбинирование освещения и карты тональной компрессии


На этом этапе комбинируются все сгенерированные ранее буферы: цвет albedo умножается на диффузное освещение, а затем к результату прибавляется отражённое освещение. Потом цвет умножается на значение SSAO и результат интерполируется с цветом тумана (который извлекается из текстуры поиска тумана и глубины текущего пикселя). Наконец, применяется тональная компрессия для преобразования из HDR-пространства в LDR. В альфа-канале хранится дополнительная информация: исходная HDR-яркость каждого пикселя.

a13b237e34e598370856331530f51965.jpg

Глубина
36d49910ef49351cd26bef6674c7aa90.jpg

Albedo
155f2cf06a44ebc2ce188f18c7e1dddb.jpg

Диффузное освещение
61b858e0ee9a80e64eaa5969c547fe2b.jpg

Отражённое освещение
2ced4a35d2ea57f0f7b8fff9dfd182e3.jpg

SSAO


b8ec9cd44f96fe2f6315b8e7c0cf7fac.png
97c16dd893c9f2c81111d2281ecc4459.jpg


Комбинирование освещения

Кстати, какая же карта тональной компрессии используется в MGS V? В интервале от 0 до определённого порога (0.6) она совершенно линейна и возвращает исходное значение канала, а выше порога значения медленно стремятся к горизонтальной асимптоте.

Вот функция, применяемая к каждому каналу RGB, где $A = 0.6$, а $B = 0.45333$:

$$display$$ToneMap (x) = \begin{cases} x & \text{if $x \le A$} \\[2ex] min\left (\text{1, } A + B — \large{\frac{\text{$B$ ²}}{x — A + B}}\right) & \text{if $x \gt A$} \end{cases}$$display$$


e1ca9fa84c3db9e137e1030bbfd2d0c1.png


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

Но действительно ли это так? Ни в коем случае, мы только начинаем! Интересно, что Fox Engine выполняет тональную компрессию довольно рано и продолжает работу в LDR-пространтсве, в том числе выполняет проходы для прозрачных объектов, отражений, глубины резкости и т.д.

Излучающие и прозрачные объекты


В этом проходе движок отрисовывает все объекты со свойством излучаемости, например, зелёный знак «Exit» или раскалённые пятна пламени на человеке в огне. Также движок отрисовывает прозрачные объекты, например, стекло.

97c16dd893c9f2c81111d2281ecc4459.jpg


Излучающие и прозрачные объекты: до прохода

a8dfb64c421fc37356713427c69b9ef8.jpg


Излучающие и прозрачные объекты: после прохода

На скриншоте выше не очень заметно, но в случае стекла также применяются отражения от окружения.

Все данные окружения получаются из кубической HDR-карты размером 256×256, которая показана ниже (также называемой зондом отражения).

7da08d4d068ed46e310e0fe03b514563.png


Зонд отражения (Reflection Probe)

Кубическая карта не является динамической, она запекается предварительно и используется как есть в процессе выполнения игры, поэтому внутри мы не увидим динамических мешей. Её задача — создание «достаточно хороших» отражений от данных статичного окружения. В разных местах уровня расположено несколько зондов. Общее количество кубических карт всей игры огромно — требуются не только зонды для множества локаций, но и различные версии одного зонда, зависящие от времени дня/ночи. Плюс необходимо учитывать различные погодные условия, поэтому для каждого времени суток и каждой точки движок генерирует четыре кубические карты (для солнечной, облачной, дождливой и грозовой погоды). Игре приходится иметь дело с впечатляющим количеством сочетаний.

На GDC 2013 показывали короткий клип о том, как движок генерирует такие зонды освещения.

Отражения в экранном пространстве (Screen Space Reflections)


На этом этапе создаётся изображение отражений в сцене исключительно на основе информации от пикселей, отрендеренных в предыдущем проходе. Здесь выполняется трассировка лучей в экранном пространстве при половинном разрешении: в каждый пиксель экрана «бросается» несколько лучей, направление этих лучей вычисляется из буфера глубин (что даёт нам положение пикселя) и из нормали. Каждый луч проверяется на коллизию сэмплированием буфера глубин в четырёх равноудалённых точках, расположенных вдоль луча. Если обнаружена коллизия, то цвет пикселя в точке её возникновения используется как цвет отражения, модулированный шероховатостью исходного пикселя.

d3531a96f5d1798c77fced81f26ba364.jpg


Цвет SSR

86b943cb1cb370cfcd0202b7309b348c.jpg


Альфа SSR

Очевидно, что нам не хватает «глобальной» информации: никакой объект за пределами экрана не может внести свой вклад в отражения. Чтобы сделать артефакты менее заметными, карта отражений использует альфа-маску для плавного затемнения непрозрачности при приближении к краям экрана.

Ценность SSR заключается в том, что они могут обеспечивать динамические отражения в реальном времени при достаточно низких затратах.

Шум карты SSR позже снижается с помощью размытия по Гауссу и смешивается поверх сцены.

Тепловые искажения, декали и частицы


Температура горящей области, в которой стоит человек в огне, настолько велика, что она создаёт искажения света. Этот эффект достигается с помощью нескольких вызовов отрисовки, каждый из которых создаёт копию всего целевого рендера и применяет искажение, локально растягивая пиксели в каком-то направлении.

Это особенно заметно на первой арке, соединяющей левую стену с потолком.

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

215016fcb87a3340763802a0d6894bce.jpg


Основа

189d675f340c3fb51ecab684cafdc9c7.jpg


Искажение

2b16768ea110eba2c086793d73cacc67.jpg


Декали

b0d8d1b4caf380059c110f22c86d969d.jpg


Частицы 30%

6750477bfa3b5fd9a290aa3fad0ace09.jpg


Частицы 60%

1e307061d800820907a03f4b352c79f3.jpg


Частицы 100%

Bloom


917e7f3afb250b8f8797eddc8d95e344.jpg


Проход светлоты

На этом этапе создаётся текстура bloom из исходной сцены. Он работает с очень низким разрешением: сначала масштаб сцены уменьшается в четыре раза, затем применяется фильтр прохода светлоты для выделения только самых светлых пикселей, как показано на изображении выше.

Как фильтр прохода светлоты разделяет «тёмные» и «светлые» пиксели? Мы уже находимся не в HDR-пространстве, тональная компрессия перенесла нас в LDR-пространство, в котором сложнее определить, какой цвет изначально был светлым.

Вспомните, что альфа-канал буфера сцены содержит исходную HDR-яркость каждого пикселя до тональной компрессии — эту информацию фильтр использует для определения «светлоты» пикселей.

d9d868dcc981d8874a45bb9e7f2bbf37.png


Блики в объективе

В движке Fox Engine эффект bloom относится не только к светлым пикселям, распространяющим вокруг свой цвет: он также учитывает блики объектива и хроматическую аберрацию, которые процедурно генерируются из буфера прохода светлоты. В нашей тёмной сцене нет сильных источников освещения, благодаря которым могли бы выделиться блики объектива, они едва видимы, но можно понять, как они выглядят, по представленному выше изображению, где я искуственно подчеркнул цвета.

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

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

917e7f3afb250b8f8797eddc8d95e344.jpg
2d266e114ea437291de3090b8c67db99.png
41113b84b6e6c5ac62d12d6beace1900.jpg

Bloom


Глубина резкости


Игры серии Metal Gear известны своими длинными кинематографическими роликами, поэтому естественно, что движок стремится как можно точнее воссоздать поведение реальных камер с помощью эффекта глубины резкости (Depth of Field, DoF): резкой выглядит только определённая область, а другие области вне фокуса выглядят размытыми.

Масштаб сцены уменьшается до половинного разрешения и преобразуется обратно из пространства sRGB в линейное пространство.

Затем движок генерирует два изображения, соответствующих «ближнему полю» (области между камерой и фокусным расстоянием) и «дальнему полю» (области за пределами фокусного расстояния). Разделение выполняется только на основе глубин (расстояния от камеры) — все пиксели ближе, чем солдаты, будут копироваться в буфер ближнего поля, а все остальные — в буфер дальнего поля.

Каждое поле обрабатывается отдельно, к нему применяется размытие. Кружок рассеяния каждого пикселя вычисляется только на основании глубины и конфигурации камеры (диафрагмы, фокусного расстояния…). Значение кружка рассеяния сообщает, насколько «вне фокуса» находится пиксель — чем больше кружок рассеяния, тем больше пиксель распространяется вокруг.

После выполнения размытия создаётся два поля:

8ccbb5d7070b09c823eb289743e59197.png

DoF — ближнее поле
9d3a5c8fb88361c994bda0332c25194e.png

DoF — дальнее поле


d171223d14d535cc831f5c09f2095267.png


Скажу пару слов об этой операции «размытия»: на самом деле это относительно затратная операция, при которой один спрайт создаётся и рендерится для каждого пикселя в сцене.

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

Спрайт центрируется на создавшем его пикселе, он имеет тот же цвет, что и пиксель, а его размер масштабируется вместе с кружком рассеяния пикселя. Идея заключается в том, чтобы пиксель «распространял свой цвет вокруг» с помощью диска — чем больше вне фокуса пиксель, тем больше должен быть создаваемый им спрайт.

abf8df2aa73eaaa1c824d5c4c82b4d87.png


Все спрайты отрисовываются поверх друг друга с аддитивным смешением.

Эта техника называется «рассеиванием спрайтов» (sprite scattering); она используется во многих играх, например, в сериях Lost Planet, The Witcher и в постобработке Bokeh-DoF UE4.

После генерирования размытых дальнего и ближнего полей мы просто смешиваем их поверх исходной сцены:

1e307061d800820907a03f4b352c79f3.jpg


DoF: до

66f52ab299b25d2b2afa4a6220ebfc1c.jpg


DoF: после

Эта техника работает и создаёт красивые результаты, однако при высоком разрешении в почти полностью расфокусированных сценах она может стать очень тормозной: перекрывающие друг друга огромные спрайты могут привести к дикому количеству перерисовок.

Как же движку Fox Engine удаётся ослабить влияние этого эффекта?

Ну, на самом деле я чрезмерно

© Habrahabr.ru