Хабрахабр

[Перевод] Как происходит рендеринг кадра A Plague Tale: Innocence

Предисловие

Как и в других моих исследованиях, давайте начнём с введения. Сегодня мы рассмотрим последнюю игру французского разработчика Asobo Studio. Впервые я увидел видео этой игры в прошлом году, когда коллега поделился со мной 16-минутным геймплейным трейлером. Моё внимание привлекла механика «крысы против света», но мне не особо захотелось играть в эту игру. Однако после её выхода многие стали говорить, что она выглядит так, как будто сделана на движке Unreal, но это не так. Мне стало любопытно увидеть, как работает рендеринг и насколько вообще разработчики вдохновлялись Unreal. Ещё меня заинтересовал процесс рендеринга стаи крыс, потому то в игре она выглядела очень убедительно и к тому же является одним из ключевых элементов геймплея.

Хотя игра использует DX11, который сейчас поддерживают практически все инструменты анализа, мне не удалось заставить работать ни один из них. Когда я начал попытки выполнить захват игры, то подумал, что придётся сдаться, потому что ничего не срабатывало. Я по-прежнему не знаю, почему так происходит, но к счастью, мне удалось выполнить несколько захватов с помощью NSight Graphics. Когда я пытался использовать RenderDoc, игра вылетала при запуске, и то же самое происходило с PIX. Как обычно, я поднял все параметры до максимальных и начал искать подходящие для анализа кадры.

Разбивка кадра

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

Как обычно, давайте начнём с окончательного кадра:

Первым, что я заметил, был совершенно иной баланс в этой игре событий рендеринга по сравнению с тем, что я видел в других играх ранее. Здесь множество вызовов отрисовки, что нормально, но на удивление лишь немногие из них используются для постобработки. В других играх после рендеринга цветов для получения окончательного результата кадр проходит ещё много этапов, но в A Plague Tale: Innocence стек постобработки очень мал и оптимизирован всего до нескольких событий отрисовки/вычислений.

Интересно, что это все render targets имеют 32-битный беззнаковый целочисленный формат (за исключением одного) вместо цветов RGBA8 или других специфичных для таких данных форматов. Игра начинает построение кадра с рендеринга GBuffer с шестью render targets. Я потратил много времени на выяснение того, какие значения закодированы в 32-битные targets, но не исключено, что всё равно что-то упустил. Это представляло сложность, потому что мне приходилось декодировать каждый канал вручную с помощью функции Custom Shader из NSight.

GBuffer 0

Первый target содержит в 24 битах некие значения шейдинга, а в 8 битах — какие-то другие значения для волос.

GBuffer 1

Насколько я понимаю, красный канал — это metalness (не совсем понятно, почему ею помечены некоторые листья), зелёный канал выглядит как значение roughness, а синий канал — это маска главного персонажа. Второй target выглядит как традиционный RGBA8-target с разными значениями управления материалом в каждом канале. Ни в одном из сделанных мной захватов альфа-канал не использовался.

GBuffer 2

Третий target тоже выглядит как RGBA8 с albedo в каналах RGB, а альфа-канал в каждом сделанном мной захвате был полностью белым, так что я не совсем понимаю, что эти данные должны делать.

GBuffer3

Значения выглядят как маска части растительности и всех волос/меха. Четвёртый target любопытен, потому что на всех моих захватах почти полностью чёрный. Возможно, это как-то связано с просвечиванием (translucency).

GBuffer 4

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

Глубина из GBuffer 5

Маска из GBuffer 5

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

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

Сумеречные лучи

После завершения карт теней можно вычислить освещение, но прежде в отдельный target рендерятся сумеречные лучи (god rays).

SSAO

На этапе вычисления освещения выполняется вычислительный шейдер для расчёта SSAO.

Освещённая непрозрачная геометрия

Все эти разные источники освещения в сочетании с отрендеренными выше targets в результате формируют освещённое HDR-изображение. Освещение добавляется из кубических карт и локальных источников освещения.

Элементы, отрисовываемые упреждающим рендерингом

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

После накопления всего цвета мы почти закончили, осталось только несколько операций постобработки и UI.

Разрешение цвета снижается в вычислительном шейдере, а затем увеличивается для создания очень красивого и мягкого эффекта bloom.

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

Стоит упомянуть пару интересных вещей, касающихся рендеринга:

  • Instancing (дублирование геометрии) используется только для отдельных мешей (похоже, что только для растительности). Все другие объекты рендерятся в отдельных вызовах отрисовки.
  • Похоже, объекты приблизительно сортируются спереди назад, за некоторыми исключениями.
  • Кажется, разработчики не прикладывали никаких усилий для группирования вызовов отрисовки с точки зрения параметров материалов.

Крысы

Как я говорил в начале статьи, одной из причин, по которым я хотел исследовать эту игру, был способ рендеринга стаи крыс. Решение меня в чём-то разочаровало: похоже, оно сделано методом грубой силы. Здесь я использую скриншоты из другой сцены игры, но, надеюсь, в ней нет никаких спойлеров.

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

LOD0

LOD1

LOD2

LOD3

Интересно, что на третьем уровне хвост загнут к телу, а у четвёртого хвоста вовсе нет. У крыс есть 4 уровня LOD. К сожалению, у NSight Graphics, похоже, не хватает инструментов, чтобы это проверить. Вероятно это означает, что анимации активны только для первых двух уровней.

Без дублирования (instancing) крыс.

С дублированием.

В сцене, захват которой показан выше, отрендерено следующее количество крыс:

  • LOD0 – 200
  • LOD1 – 200
  • LOD2 – 1258
  • LOD3 – 3500 (с дублированием геометрии)

Это даёт нам понять, что существует жёсткое ограничение на количество крыс, которых можно отрендерить на первых двух LOD.

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

В заключение

Plague Tale: Innocence очень интересна с точки зрения рендеринга. Его результаты без сомнений меня впечатлили, они очень хорошо служат геймплею. Как и в случае с любым проприетарным движком, было бы здорово услышать более подробный анализ из уст самих разработчиков, особенно потому, что мне не удалось подтвердить некоторые из моих теорий. Надеюсь, моя статья когда-нибудь доберётся до кого-нибудь из Asobo Studio и они увидят, что у людей есть к этому интерес.

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

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

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

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

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