[Перевод] Анализ графики Red Dead Redemption 2
Одна из моих любимейших игр, Red Dead Redemption, в 2018 году вернулась с приквелом для консолей. В 2019 году её выпустили для PC, и мне наконец удалось поиграть в неё; меня сразу же поразила её графика. Однако я расстроился: мне едва удавалось играть при средних настройках с 25 FPS на настольном GPU 1050Ti. Понимаю, машина у меня не очень мощная, но 25 FPS на средних настройках?
Сегодня мы рассмотрим несколько примеров кадров из игры и проанализируем использованные в игре графические приёмы.
Предисловие
Это неофициальный анализ игры. Я просто проанализировал захват кадров при помощи RenderDoc. Если вы хотите узнать информацию от самих разработчиков, то можете изучить слайды с доклада на SIGGRAPH Фабиана Байера. Слайды (внизу страницы), видео (начинается с 1:58:00).
Также можно прочитать анализ графики GTA5, выполненный Адрианом Корреже [перевод на Хабре]. Так как и RDR2, и GTA5 созданы одной компанией и используют один движок, часть приёмов из GTA5 присутствует и здесь.
Ещё один важный момент — я не являюсь опытным программистом графики и по-прежнему новичок в этой области. Поэтому многое мне непонятно. Если вы найдёте ошибки или то, что можно улучшить, пишите мне. Ну, поехали!
Разбираем кадр
Вот наш основной кадр для анализа:
Кадр захвачен на PC со средними настройками.
В играх наподобие RDR2 почти невозможно исследовать все приёмы в одном кадре. Их работа переносится между несколькими кадрами. Поэтому я выполнил захват нескольких кадров, но показанный выше будет основным. Он содержит множество свойств, в том числе: прожекторные и точечные источники освещения, направленное освещение (его очень мало, но оно есть), здания, NPC, лошадь, деревья, растительность, облака и т.д. В нём можно показать большинство используемых в игре техник рендеринга.
RDR2 — это игра с открытым миром, поэтому потоковая загрузка данных выполняется постоянно. Следовательно, построение кадра начинается с набора таких задач, как создание и удаление текстур, просмотры ресурсов шейдеров, просмотры с беспорядочным доступом, обновление дескрипторов, буферов и т.д.
Карта земли/грязи
Грязь играет большую роль. Кроме того, что она является игровой механикой, грязь делает окружения более реалистичными. Игра рендерит текстуры следов людей и лошадей в карту смещений (displacement map) вместе с текстурами следов колёс повозок. Эта аккумулированная текстура используется для Parallax Occlusion Mapping при рендеринге рельефа.
Карта грязи: R16_UNORM
размером 2048×2048
Небо и облака
После прохода вычисления грязи игра выполняет большой объём работы с вычислениями GPU. В основном они относятся к небу и облакам. Облака, туман и объёмное освещение — одни из самых выделяющихся эффектов RDR2. Подробнее об этом этапе можно узнать из слайдов Фабиана. Он объясняет всё это гораздо детализированней, чем это смогу сделать я.
Карта окружений
Карты окружений (Environment maps) — основной источник отражений RDR2, а также GTA5.
Как и GTA5, RDR2 генерирует кубическую карту окружений из позиции камеры. Движок игры генерирует тонкий GBuffer для карты окружений, похожий на используемый в Far Cry 4.
Грани кубической карты окружений (альбедо): RGBA8_SRGB
Грани кубической карты окружений (нормали): RGBA8_UNORM
Грани кубической карты окружений (глубина): D32S8
Генерация кубических карт окружений в каждом кадре может оказаться очень тяжеловесной задачей. Для снижения вычислительных затрат RDR2 использует оптимизации. Например, игра отрисовывает только статичные и непрозрачные объекты, выполняет усечение по пирамиде видимости (frustum culling) перед рендерингом каждой грани и отрисовывает версии моделей с пониженным уровнем LOD. Однако я выяснил, что количество полигонов рельефа всё равно очень высок для карт окружений.
После прохода G-Buffer генерируется кубическая карта окружений неба при помощи параболоидной карты неба и текстур облаков. Следующим этапом является свёртка. Для Image Based Lighting движок RDR2 использует split sum approximation. В этом способе используется предварительно отфильтрованная карта окружений вместе с LUT BRDF окружений. Для фильтрации игра сворачивает кубическую карту окружений и хранит свёрнутые версии в уровнях mip-текстур кубической карты.
Перед выполнением прохода освещения для кубической карты освещения RDR2 рендерит в ещё одну кубическую текстуру запечённое крупномасштабное ambient occlusion. Игра использует screen space ambient occlusion, но SSAO помогает только при мелких масштабах. Запечённое ambient occlusion помогает выполнять затемнение в бОльших масштабах, например, затемнение террас и интерьеров.
Грани кубической карты окружений (запечённое AO): R8_UNORM
Для вычисления освещения карт окружений игра использует отложенный тайловый рендеринг. Усечение света (light culling) и освещение вычисляются вместе в одном проходе вычислительного шейдера для каждой грани карты окружений. (Благодарю за эту подсказку @benoitvimont.) Также для запечённого освещения игра использует технику «карты освещения мира с видом сверху» (top-down world lightmap) похожую на использованную в Assassin’s Creed III.
Для каждой грани кубической карты RDR2 рендерит окончательный цвет поверх текстуры окружений неба. Затем игра фильтрует кубическую карту окружений так же, как и кубическую карту окружений неба.
Грани кубической карты окружений (окончательные): R11G11B10_FLOAT
Также когда игрок находится рядом со зданием, RDR2 загружает карты окружений, расположенные в интерьерах зданий. Существуют также G-Buffers кубических карт, потоково загружающиеся с диска.
Запечённые грани кубической карты окружений (альбедо): BC3_SRGB
(запечённое AO хранится в альфа-канале)
Запечённые грани кубической карты окружений (нормали): BC3_UNORM
Запечённые грани кубической карты окружений (глубина): R16_UNORM
Игра вычисляет освещение этих карт и фильтрует их, как и предыдущие. За раз она вычисляет только одну запечённую карту окружений, и заново пересчитывает её только при смене времени суток. Все карту окружений хранятся в массиве текстурных кубических карт. Никакого преобразования кубической карты в двойную параболоидную карту не выполняется.
Проход G-Buffer
Этот этап начинается с предварительного прохода глубин рельефа, а затем игра рендерит сцену в G-Buffers.
RGBA8_SRGB
— этот буфер содержит в каналах RGB альбедо (базовый цвет). Я не совсем понимаю, для чего нужны данные альфа-канала, но они используются на этапе сглаживания.
RGBA8_UNORM
: каналы RGB содержат нормали, а альфа-канал содержит что-то, относящееся к ткани и волосам.
RGBA8_UNORM
: этот target используется для свойств материалов.- R: Reflectance (f0)
- G: Smoothness
- B: Metallic
- A: содержит затенение (этот канал будет использоваться как маска теней на последующих этапах)
RGBA8_UNORM
: красный канал содержит полости. В синем канале снова какие-то загадочные данные. А в альфа-канале находятся данные, относящиеся к волосам. В зелёном канале мне не удалось ничего найти.
RG16_FLOAT
: этот буфер содержит скорость в экранном пространстве для реализации motion blur.
D32S8
: как и GTA5, RDR2 использует для глубины обратную z, а также стенсил-буфер для присвоения значений определённой группе мешей.
Из запечённых данных генерируется ещё один target:
Этот буфер содержит в красном канале такое же запечённое ambient occlusion, что и на этапе карт окружений. Но в этой текстуре есть и другие каналы. Зелёные канал содержит данные, напоминающие данные в синем канале GBuffer 3. Я опять не понимаю, для чего используются эти данные. У захваченных кадров я не смог найти никаких данных в синем и альфа-канале. Я изучу это более подробно.
Генерация карт теней
После этапа G-Buffer игра начинает рендерить карты теней (shadow maps). Она использует 2D-массивы текстур для карт теней точечных источников и кубические массивы текстур для карт теней прожекторных источников.
В некоторых играх для карт теней используется большая текстура атласа теней (например, в DOOM). Одно из преимуществ такого метода заключается в том, что размер карты теней может сильно варьироваться в зависимости от расстояния. При использовании массивов текстур эта гибкость теряется, потому что все текстуры в массиве должны иметь одинаковый размер. В RDR2 есть три разных массива текстур для разного уровня качества. Например, у прожекторных источников есть:
- 512×768 D16 для далёких источников
- 1024×1536 D16 для источников на среднем расстоянии (а при средних настройках графики — и на ближнем расстоянии)
- 2048×3072 D16 для близких источников (при высоких/ультракачественных настройках)
Точечные источники отбрасывают тени во всех направлениях. Чтобы справиться с этой задачей, в играх используется техника под названием Omnidirectional Shadow Mapping, при которой сцена рендерится в кубическую карту глубин из позиции камеры. При помощи этой техники отрендерены тени от костра и тени от фонаря Артура. Как и прожекторные источники, точечные имеют три разных массива для разных настроек качества.
У большинства статичных точечных источников в игре есть запечённые кубические карты теней.
Поэтому игра по возможности использует запечённые тени и генерирует карту теней только тогда, когда игрок находится рядом с объёмом света. Но на самом деле всё ещё интереснее.
Большинство источников света на стенах — прожекторные, но игра не генерирует для них всенаправленную карту теней (omnidirectional shadow map). Вместо неё она генерирует карту теней и копирует память этой карты теней в кубический массив карты теней точечных источников.
Слева — карта теней прожекторных источников размером 1024×1536, справа — те же данные изображения в кубическом формате текстуры 512×512
Обратите внимание, что в локальных картах теней хранятся линейные z.
Это объясняет то, почему для прожекторных источников разработчики не использовали карту теней квадратной формы. Количество пикселей в карте теней прожекторных источников и кубической карте точечных источников должно быть одинаковым. Вы наверняка увидели, что правое изображение нарезано на части. Так получилось, потому что ширина карты теней прожекторного и точечного источников отличаются.
Также обратите внимание на то, что эта текстура не покрывает 360 градусов. Однако, к счастью, у источников на зданиях обычно есть на задней стороне стена, и запечённые карты теней закрывают её.
Ещё один интересный момент заключается в том, что процесс выполняется наоборот. Возьмём для примера Сен-Дени — один из крупнейших городов в игре. Игра генерирует omnidirectional shadow maps для прожекторных источников и копирует эти данные в массив карт теней прожекторных источников. Я не знаю, зачем в RDR2 наложение теней выполняется таким образом. В Интернете мне не удалось найти никаких подобных приёмов.
Наложение теней направленного освещения в RDR2 реализовано почти так же, как в GTA5. Это Cascaded Shadow Mapping с четырьмя каскадами. В качестве каскада используется каждый тайл размером 1024×1024 из текстуры 1024×4096 (при средних настройках графики).
Атлас теней направленного освещения: R16_UNORM
Этап освещения
Наконец настало время для объединения всех этих карт окружений, gbuffers, карт теней и буферов ao.
Этот этап состоит из двух проходов: первый для глобального освещения (солнца/луны), второй — для локальных источников.
Проход глобального освещения
Игра рендерит полноэкранный четырёхугольник для вычисления направленного освещения, которым в нашем кадре является лунный свет. Также используется запечённое освещение из упомянутой выше «карты освещения мира с видом сверху».
Проход локального освещения
В этом проходе игра рендерит низкополигональную сферическую фигуру для объёмов точечных источников и восьмигранник для объёмов прожекторных источников. Освещение рендерится сзади вперёд при помощи аддитивного смешения.
Чтобы избежать необязательных вызовов шейдеров, игра использует тестирование с ограничением по глубине — дополнительную функцию OpenGL/D3D11, которая стала нативной в Vulkan/D3D12. Также она использует стенсил-тестирования для отбрасывания пикселей, поглощённых просвечивающими объектами, например, стёклами окон. Эти объекты будут рендериться во время прямого прохода.
Рендеринг воды и отражения
В этом посте я не буду рассказывать о рендеринге воды, потому что он заслуживает отдельного поста, но немного скажу об отражениях:
- Как говорилось выше, карты окружений — это основной источник отражений. Для стандартных отражений. например, для отражений в окнах, игра использует их.
- Зеркала рендерятся с планарными отражениями, при которых сцена рендерится заново с направления отражения. Этот процесс также выполняется отложенным рендерингом.
- Отражения в воде используют отражения экранного пространства (screen space reflections) в сочетании с картой отражений, сгенерированной в начале кадра.
Этап прямого затенения
Один из недостатков конвейера отложенного рендеринга заключается в том, что невозможно правильно рендерить просвечивающие материалы с GBuffers. Чтобы решить эту проблему, игра рендерит просвечивающие материалы сзади вперёд при помощи прямого затенения, как большинство, использующих отложенный рендеринг.
Но прямой проход может быть затратным:
- В нём используются прямые шейдеры затенения, которые более затратны отложенных.
Количество регистров, используемых в шейдере, имеет отрицательную корреляцию с количеством инстансов шейдеров, которые можно выполнять параллельно. Так как прямое затенение сочетает вычисления материалов и освещения (а также теней), количество регистров в прямых шейдерах может быть высоким.
- Они отрисовывают каждый толстый просвечивающий объект дважды.
Для достижения правильного смешения необходимо сначала отрендерить задние грани объекта, а затем отрендерить передние. Из-за этого большинство просвечивающих объектов в сцене отрисовывается дважды. Двухмерные объекты-четырёхугольники (например, окна) рендерятся один раз.
- Кроме того, между отрисовками происходит изменение состояния конвейера.
Чтобы иметь возможность переключения между отсечением передних и задних граней, необходимо изменять состояния конвейера. А такие изменения могут быть затратными.
На этом этапе есть ещё один render target, генерируемый для эффекта bloom. Этот target хранит яркость bloom. Как видно на изображении, просвечивающие объекты светятся сильнее.
Target яркости bloom: R8_UNORM
Заметьте, что яркость bloom увеличивается на дальних расстояниях, чтобы туманные области больше светились.
Постобработка
На этом этапе выполняются временнОе сглаживание, bloom, motion blur, эффект глубины видимости и другие эффекты. Постобработке я планирую посвятить отдельный пост. Поэтому мы не будем особо её здесь обсуждать, но я бы хотел сказать пару слов о bloom. Благодаря объёмному освещению в основном render target уже есть свечение источников освещения.
Реализация bloom в RDR2 очень похожа на реализацию, описанную в Next Generation Post Processing in Call of Duty: Advanced Warfare.
- В качестве входных данных берётся target без пороговых значений,
- Render target
R11G11B10_FLOAT
уровня 7-mip, - Билинейный фильтр 13-го порядка при даунсэмплинге, tent-фильтр 3×3 при апскейлинге.
Затем игра комбинирует этот отфильтрованный target эффекта bloom с основным target, а также с target яркости bloom.
Выводы
Можно о многом ещё рассказать, но я не хочу, чтобы этот пост был слишком длинным. Я хотел бы поделиться своими выводами обо всём этом, а также некоторыми странностями, которые обнаружил в процессе анализа.
- Первое, что я заметил — игра выполняет множество переключений с вычислений к графике и обратно. Она использует асинхронные вычисления, если их включить. (Их нельзя включить в игровых настройках, придётся изменять конфигурационный файл игры.) Вот пример для эффекта bloom: игра переключается на вычислительные шейдеры и выполняет какую-то работу, затем снова переключается на графику и выполняет даунсэмплинг, затем снова переключается на вычисления для выполнения какой-то другой работы, а потом заново переключается на графику для апсэмплинга. Затратны ли такие переключения для GPU? Оправдывает ли выгода от вычислительных шейдеров затраты на переключения?
- Ещё одна странность заключается в том, что RDR2 очищает большинство текстур. Это странно, потому что обычно игры стараются избегать необязательную очистку текстур (например, очистку GBuffers). Оказывают ли эти очистки текстур реальное влияние на производительность? Обязательно ли очищать эти текстуры?
- Третья странность: в одном кадре есть три (а может и больше) одинаковых прохода даунсэмплинга глубин. Один для SSAO, второй для SSR, и ещё один для этапа генерации объёмного тумана и столбов света. Почему игра не использует подвергнутый даунсэмплингу target глубин из SSAO для других этапов?
Не поймите меня неверно, я никого не обвиняю (хотя… меня немного расстраивает низкая частота кадров), просто пытаюсь понять, как приняты такие решения. В конце концов, над этой игрой в поте лица трудилось множество людей. Вероятно, у них не было достаточно времени для дальнейшей оптимизации игры.
Послесловие
Вот и всё! RDR2 — это превосходно выглядящая игра, не только благодаря использованным графическим приёмам, но и из-за арта и освещения; всё выглядит просто феноменально. Я влюбился в цветовую палитру этой игры. Особенно в ночное время суток, оно напоминает мне «Как трусливый Роберт Форд убил Джесси Джеймса», «Старикам тут не место» и другие вестерны, снимавшиеся 35-миллиметровыми камерами.