Хабрахабр

[Перевод] Особенности рендеринга в игре Metro: Exodus c raytracing

image

Предисловие

После выхода последней игры из серии «Метро» я потратил несколько часов на изучение её внутренней работы и решил поделиться тем, что может показаться интересным с технологической точки зрения. Я не буду проводить подробный анализ или изучать дизассемблированный код шейдеров, а покажу высокоуровневые решения, принятые разработчиками в процессе создания игры.

Единственным официальным источником информации является доклад с GDC, который нельзя больше найти нигде в Интернете. На данный момент разработчики ещё не рассказывали об использованных в игре техниках рендеринга. Это одна из первых игр, в которых используется DXR. И это досадно, ведь игра работает на очень интересном собственном движке, эволюционировавшем из предыдущих игр серии «Метро».

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

Первые шаги

На поиск среды, способной работать с этой игрой, у меня ушло несколько дней. Протестировав несколько версий RenderDoc и PIX, я остановился на изучении результатов трассировки лучей с помощью Nvidia NSight. Я хотел изучать рендеринг без функций raytracing, но NSight позволял исследовать подробности и этой функции, поэтому я решил оставить её включённой. Для всего остального рендеринга вполне подошёл PIX. Скриншоты сделаны с помощью обоих приложений.
У NSight есть один недостаток — он не поддерживает сохранение захвата в файл, поэтому я не мог возвращаться к кадрам, которые изучал.

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

Ради полноты исследования я делал захваты из игры, запущенной с максимально возможными параметрами, но без DLSS.

Анализ кадра

Готовый кадр

Краткий анализ рендеринга демонстрирует довольно стандартный набор функций, за исключением глобального освещения, выполняемого трассировкой лучей (raytraced GI).

Перед рендерингом картинки масштаб предыдущего кадра уменьшается в очереди compute и вычисляется средняя яркость.

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

Проход GBuffer заполняет 4 render target по показанной ниже схеме, а также завершает заполнение буфера глубин.

1. Target в формате RGBA8 с albedo и, возможно, Ambient Occlusion в альфа-канале; на некоторых поверхностях выглядит очень тёмным.

2. Target в формате RGB10A2 с нормалями и, возможно, маской подповерхностного рассеяния (subsurface scattering) в альфа-канале.

3. Target в формате RGBA8 с другими параметрами материалов, вероятно, metalness и roughness в альфа-канале. Любопытно, что каналы RGB в этом случае содержат точно такие же данные.

4. Target в формате RG16F с 2D-векторами движения.

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

В графической очереди GPU занимается трассировкой лучей глобального освещения, но подробнее я расскажу об этом ниже.

В очереди compute вычисляются ambient occlusion, отражения и что-то, похожее на распознавание рёбер.

Подробнее об этом ниже. В графической очереди четырёхкаскадная карта теней рендерится в 32-битную карту глубин размером 6k * 6k. После завершения карты направленных теней разрешение третьего каскада по непонятным причинам снижается до 768 * 768.

Посередине процесса рендеринга теней есть любопытный момент: impostor atlas дополняется какими-то объектам до рендеринга локальных теней от освещения (о том, что такое impostors, можно прочитать здесь). И буферы impostor-ов, и буферы теней локального освещения тоже являются текстурами размером 6k * 6k.

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

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

Окончательная картинка достигается тональной коррекцией и вычислением bloom (уменьшением, а затем увеличением разрешения кадра с тональной коррекцией). Наконец, в отдельный буфер рендерится UI и вместе с bloom композитингом накладывается поверх сцены.

Я не нашёл части, в которой выполняется сглаживание, поэтому оставлю это на потом.

Трассировка лучей глобального освещения

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

Вид сверху

Также очевидно отсутствие геометрии, тестируемой по альфа-каналу. Здесь мы можем видеть четыре тайла, окружающие позицию игрока. У деревьев есть стволы, но нет листвы, нет ни травы, ни кустов.

Вид вблизи

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

Предметы игрока под ногами

Интересно, что предметы игрока тоже являются частью ускоряющей структуры, но по какой-то причине расположены у него под ногами.

Поломанный скиннинг?

Снова поломанный скиннинг?

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

Огромное количество объектов

Большинство из них на самом деле не внесёт никакого вклада в результаты вычислений глобального освщенеия. На более общем плане видно, как много разных объектов в ускоряющей структуре. Все объекты добавляются с полной детализацией. Также здесь видно, что нет никакой схемы LOD. Было бы интересно узнать, оказывает ли это какое-то влияние на трассировку лучей (я бы предположил, что да).

Сверхвысокий LOD, полностью смоделирована каждая шкала и переключатель

Каждый переключатель и каждая шкала на этом снимке чётко читаются даже без текстур. Ещё один скриншот показывает огромную детализацию объектов даже вдалеке от игрока. Возможно обновление ускоряющей структуры при использовании LOD было бы слишком затратным, но существует большая вероятность того, что это обновление можно выполнять асинхронно. Место, в которое я переместил камеру, чтобы сделать этот скриншот, находится в десятках метров от игрока и устранение этих деталей никак бы не ухудшило качество картинки. Этот момент определённо стоит исследовать подробнее.

Рендеринг направленных теней

Основная часть рендеринга теней проста и не требует особого упоминания, но и здесь есть интересные моменты.

Меши, для которых мала вероятность отбрасывания теней

Огромная детализация в картах теней

Меши, для которых, похоже, используется неправильный буфер индексов

Есть объекты, которые почти не вносят вклада в карту теней, но они всё равно рендерятся. Похоже, что аналогично ускоряющим структурам, рендеринг теней включает в себя абсолютно всё. Интересно, так происходит из-за разрешения, или в движке нет простого способа исключить их?

На их ренедринг уходит не так много времени, но интересно было бы посмотреть, можно ли их удалить, чтобы сэкономить немного времени. Есть и объекты, которые трудно заметить даже с тенями в экранном пространстве.

Это самый лучший пример, который мне удалось найти, но он далеко не единственный. При изучении меша кажется, что некоторые из мешей, отрендеренные в карте теней, имеют поломанные буферы индексов, но после вершинного шейдера они выглядят правильными (результаты одинаковы и в PIX, и в NSight). Может быть, это какая-то специальная упаковка позиции?

Похоже, что у мешей плохой скиннинг

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

Часть 2

image

Небольшая поправка

В предыдущей части я писал, что в третьем render target буфера GBuffer скорее всего содержится metalness, но кажется, что на самом деле там содержится specular color. Сначала я не видел никаких цветов и не понимал, почему во всех трёх каналах RGB содержатся одинаковые данные, но наверно так было потому, что в сцене не было цветных отражений. Для этого оружия в буфере содержится гораздо больше разных цветов.

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

«Улучши меня!»

Композитинг прозрачности и сглаживание

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

До рендеринга прозрачности

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

После композитинга прозрачности текущего кадра

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

После добавления непрозрачности текущего кадра

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

После TAA

Меня уже интересовало это раньше, потому что я не видел, где происходит сглаживание. После завершения полного кадра проход TAA (временнОго сглаживания) сглаживает рёбра. Но я пропустил это потому, что сразу после этого вызова отрисовки начинается снижение разрешения (downsampling) для прохода bloom и я упустил этот один вызов отрисовки.

Блик объектива (Lens flare)

Обычно я не стремлюсь анализировать отдельные эффекты, но существует множество способов реализации lens flare, поэтому мне стало любопытно, какой выбрали разработчики.

Lens flare в готовом композитинге

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

Lens flare в буфере bloom

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

Геометрия Lens flare

В создании готового результата на экране участвуют не менее 6 четырёхугольников, но здесь нет серии более мелких четырёхугольников, становящихся всё ближе к позиции солнца. Если посмотреть на геометрию, то lens flare довольно прост. Можно прийти к выводу, что это довольно стандартное решение, хоть некоторые разработчики и рендерят lens flare непосредственно в rendertarget сцены, а другие вычисляют эффект как постобработку.

Рендеринг рельефа

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

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

Возможно, у меня были заданы неверные параметры, но игра рендерит все фрагменты рельефа без тесселяции. Для каждого фрагмента рельефа она использует эту равномерную сетку 32*32. Также нет никакого LOD.

Взглянув на фрагмент рельефа после вершинного шейдера, можно увидеть, что большинство пар вершин слились, образовав практически идеальную сетку 16*16, за исключением некоторых мест, в которых требуется бОльшая точность (вероятно, из-за кривизны рельефа). Упомянутая выше деформация вероятно возникает благодаря чтению mip-текстур карты высот рельефа, когда рельеф находится далеко от камеры.

Хитрости трассировки лучей

А теперь о том, чего все ждали.

Потоковая передача данных

Один из самых интересных аспектов любой реализации DXR на текущий момент — это способ работы с данными. Самое важное — это как данные загружаются в ускоряющие структуры и как они обновляются. Чтобы проверить это, я сделал два захвата и сравнил ускоряющие структуры в NSight.

Игрок находится внутри судна

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

Игрок переместился в левый верхний угол этого изображения

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

Рельеф и то, что под ним

Можно упомянуть несколько аспектов реализации DXR в Metro: Exodus, касающихся рельефа.

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

Следующего момента я бы ни за что не заметил, если бы рельеф был на месте. Взглянув в начале уровня на ускоряющую структуру в NSight, я заметил под рельефом какие-то меши.

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

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

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

Безголовость со скиннингом

Я уже говорил о проблемах мешей со скиннингом, но на этом уровне я заметил ещё кое-что.

Во-первых, этот монстр демонстрирует в одном изображении обе ошибки, которые я заметил выше. Мне всё ещё интересно, чем они вызваны.

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

Ещё один пример. Заметьте отверстие в том месте, где должна быть голова. Я не видел ни одного случая, когда бы голова была видна.

Тот же вид существ в режиме растеризации. Заметьте, что голова чётко видна.

А вот каркасное отображение головы.

В заключение

На сегодня это всё. Надеюсь, вам понравился этот взгляд на внутренности Metro:Exodus.

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»