Итак, Genshin Impact... В нем используется кастомный Scriptable Render Pipeline, причем и в мобильной и в десктопной версиях рендеринг видимо устроен похоже (в мобильной отключены некоторые эффекты, ограничено разрешение). Точно сказать не могу, так как десктопная версия игры защищена от анализа рендердоком и другими профайлерами – я так и не смог к ней подключится. Однако Ninja Ripper позволяет вытащить все текстуры, включая буферы кадра, и меши. Мобильная версия тоже не поддавалась анализу, однако поиском в китайском сегменте интернета удалось обнаружить небольшой трик: игра должна быть запущена в эмуляторе MuMu player (не проверял, работает ли это с другими эмуляторами), в корневую директорию которого добавлена d3dd11.dll, модифицированная для загрузки renderDoc. Такой MuMu player позволяет подключать к себе рендердок и профайлить запущенные на нем игры. Да, появляются некоторые артефакты, но сравнением с другими Unity проектами можно их исключить из анализа. Я тестировал игру только на самом максимальном качестве.
Рендеринг – Deferred, что довольно необычно как для мобильных игр, так и с точки зрения стилизации. Все же типичный случай Deferred рендеринга – когда все материалы PBR (с физически корректной моделью освещения).
Первый DrawCall – рендер квада в текстуру размером 128х128: из текущих параметров освещения и LUT текстуры рассчитывается что-то вроде цвета неба. Эта текстура позже используется при рендеринге отражений на воде и тумана.
Далее, также в текстуру 128х128, смешиванием текстур шума и маски подготавливается текстура, используемая при рендеринге неба для едва заметных облаков самого верхнего слоя.

Третьим идет основной проход рендеринга обьектов в G-буфер. Это три RGBA текстуры (8 бит на канал) и Depth(24bit)Stencil буфер, причем для разных типов объектов назначения текстур несколько отличаются (достаточно нетипично для Deferred рендеринга). Для хранения информации о типе обьекта служит стенсил-буфер. Всего типов три:
-
трава, кроны деревьев и кусты;
-
анимированные персонажи;
-
остальная геометрия – скалы, здания, стволы деревьев, игровые предметы и т.д.
Небо, вода, облака и партиклы не пишутся в G-буфер и рендерятся позднее, о них - потом.
Первая RGBA текстура G-буфера – это нормали (каналы RGB) и glossiness для основной геометрии. Вторая текстура – диффузный цвет (каналы RGB), в альфа канал записывается интенсивность самосвечения (Emissive). RGB каналы третьей текстуры используются для хранения дополнительных нормалей травы или амбиентного освещения крон деревьев или Specular color основной геометрии. Breakdown заполнения G-буфера я сохранил в gif, для иллюстрации:



Сначала рендерится трава, группы травинок (по 2 - 4 треугольника на травинку) по 48 - 114 треугольников (LODы, в зависимости от расстояния) инстансиируются до 32 групп на 1 DrawCall. Травинки, соответственно, не прозрачные (кроме цветочков), это хорошо – филлрейт от объектов с альфа-тестом может просто убивать производительность мобильных игр. Практически все вычисления выполняются в вертексном шейдере. Используются псевдорандомные значения для положения и изгиба – синус или косинус от позиции, текстура шума для дополнительной рандомизации изгиба, текстура положения персонажей (из предыдущего кадра) для еще большего изгиба для примятия травы. В текстуру нормалей G-буфера записывается нормаль террейна под травой, а в RGB каналы третей текстуры G-буфера – дополнительные нормали, рассчитываемые из полученного рандомного положения травинки.
Далее кусты и кроны деревьев. По 1300 - 3500 треугольников на объект. Активно используются LODы. Крупные лиственные деревья сделаны хитро – часть геометрии статичная, часть – спрайтами, нескольких типов. В итоге эти спрайты не бросаются в глаза, нет ощущения, что дерево следит за тобой. Дальние деревья – спрайтами, инстансиируются в 1 батч.
Скалы, здания, игровые обьекты (сундуки например) и террейн – это все объекты с не-стилизованным шейдингом, с картами нормалей и интенсивности спекуляра/glossiness. Везде LODы. Одинаковые объекты тоже инстансиируются.

Анимированные персонажи рендерятся в 2 прохода – сначала основной цвет (и интенсивность Emissive в альфу буфера цвета) и нормали (третья карта G-буфера не используется). Потом – рендерится слегка смещенная по нормали ("раздутая") геометрия для создания ink-обводки. В основных персонажах по 16 – 20 тысяч треугольников. В драконе – 42 тысячи. Текстуры человеческих персонажей весьма необычно объединены в атласы (и, соответственно, DrawCalls): 1) все тело и почти вся одежда , 2) оставшаяся часть одежды 3) нижняя часть лица и лоб, 3) верхняя часть лица, 4) волосы и глаза(!). Маленькая деталь: блики в глазах сделаны геометрией по 18 треугольников.

После того, как все объекты отрендерены в G-буфер, происходит копирование буфера глубины в текстуру R32 разрешением в 2 раза меньшим разрешения экрана (во всяком случае так в мобильной версии), последовательное копирование ее в текстуры 512х256, 256х128 и тд, и их обьединение в одну текстуру 512х512. Потом из этой текстуры сложным шейдером создается некая индексная текстура, которая в дальнейшем нигде не используется.
Далее следует проход рендеринга карты теней – похоже что обычные тени Unity, четыре каскада (в десктопной версии на макс качестве – 6). Во внутренних помещениях вместо теней от солнца рендерятся карты теней от точечных (Omni) источников света. Это кубмапы, и вид у них странноватый.

Я даже сначала подумал что это какой-то хитрый способ расчёта Global Illumination. Но все оказалось проще – карта теней вместо 32-битного формата пакуется в четыре 8-битных канала (R8G8B8A8_UNORM). Зачем оно так сделано – не знаю. Лет 20 назад, когда поддержка HDR текстур еще не была общепринятой, такой способ упаковки теней использовался некоторыми движками. Какой сейчас от него выигрыш, сказать не могу. Это особенность именно Genshin, в URP и в Built-in рендерерах Unity кубмапы теней от Omni источников сохраняются в обычный R16 формат.
Следующим проходом рендерим вид сверху на вот такие расплющенные конусы, в тех местах, где находятся персонажи. Получаемая текстура применяется для приминания травы (в направлении от центра конуса) во время рендеринга травы в следующем кадре.

Следующий проход – рендеринг собственно теней, по карте глубины и карте теней. Используется эффект Sun Shafts – получается такое “затекание” света на объекты.

Потом рендеринг screen-space reflections (вроде бы, применяются только на водных поверхностях), в отдельный буфер. Используются копия буфера предыдущего кадра и карта глубины.
Затем следует рендеринг полупрозрачных объектов в отдельный буфер – в основном это эффекты. Часть из них сделана партиклами, часть – модели.

Наконец, собственно deferred рендеринг основной картинки. Он происходит в три прохода, в соответствии со значениями в стенсил-буфере. Используются текстуры G-буфера и готовая текстура теней. Результат записывается в 2 буфера - один из них основной буфер кадра, другой - будет использован в следующем кадре для генерации Screen-Space Reflections. Первый проход - основная геометрия, там сложный длинный шейдер, используется по сути PBR рендеринг.
Второй проход - персонажи, тут шейдер Cell-шейдинга, он сильно проще. К персонажу тени не применяются – он целиком уменьшает свою яркость при попадании в тень, видимо значение карты теней проверяется в контроллере персонажа после чего значение яркости выставляется в рендер.
Третий проход - растительность - снова большой шейдер, подробно не смотрел, что там и как.

Следующий этап - применение spot и omni источников света, если где они есть. Эти источники рендерятся как геометрия - сферы для omni и пирамидки для spot, что типично для deferred рендеринга. В одной из сцен подземелий я насчитал 72 источника света (кроме основного directional света), по 3 вызова для каждого – также, с тремя стенсил-масками: для применения на основной геометрии, растительности и персонажах, но для персонажей используется “пустой” шейдер, который ничего не пишет в буфер, зачем он вообще вызывается – не знаю.

Далее рендерится небо (сфера с процедурной моделью освещения неба, ~3k треугольников), солнце (спрайт с процедурно нарисованным кружочком), облака (несколько слоев с разными параметрами, сами облака сделаны спрайтами). Следующие шаги: применяется туман, по карте глубины с использованием рендертекстуры, отрендеренной самой первой; рендерятся водные поверхности (используется уже отрендеренный буфер SSR); применяется тоже уже отрендеренный буфер с полупрозрачными объектами; рендерится Lens Flare (спрайтами, стандартно).

Теперь рендерим анимированных персонажей и движущиеся объекты в буфер для создания motion blur эффекта. На движущейся растительности motion blur нет.

Потом идут 3 прохода применения motion blur эффекта. Далее – эффект Bloom. Тут вроде бы все как обычно: текстура уменьшенного разрешения размывается горизонтально, вертикально, потом еще уменьшается и так 3 раза. Запись уменьшенных текстур последовательно происходит в один и тот же рендертаргет – таким образом количество переключений рендертаргетов уменьшается, что хорошо, особенно для мобильных (в Bloom'е, встроенном в Unity, сделано не так, там много рендертаргетов). К финальной картинке в один пасс применяются Bloom и Color Correction.
Ну и наконец рендеринг UI: батчами, но все равно получается около 50 DrawCalls.

В типичном кадре всего, в сумме всех проходов (включая тени), рендерится 500 – 850 тысяч треугольников, что немало для мобильной игры. Персонажи рендерятся 3 раза (основной G-буфер, обводка, motion blur), плюс рендеринг в карту теней. Вызовов DrawCalls немного, все что можно инстансиируется, все красиво и чисто, но и особой заботы об отключении невидимых в данный момент стадий нет – например в подземельях и интерьерах рендерится текстура для приминания травы. В интерьерах, вместо динамических теней (те самые, запакованные в RGBA кубмапы) можно было бы использовать лайтмапы – но лайтмапы в игре вообще не применяются. Шейдеры не производят впечатление оптимизированных, написанных руками. В мобильной версии для оптимизации отключены некоторые эффекты и ограничено разрешение рендеринга – перед рендерингом GUI фреймбуфер апскейлится до разрешения экрана (это часто применяется в мобильной графике – из-за высокой плотности точек на экранах телефонов такой апскейл не сильно заметно портит качество).
Но в целом Genshin Impact производит позитивное впечатление “как надо делать” О том, как делать не надо, мы поговорим в одной из следующих статей, благо примеры мобильных игр, поражающих воображение скрытыми фейлами рендеринга, тоже имеются в наличии
Автор: Anfin3D