Internals of "Clive Barkers Jericho" game rendering engine (ru)
После того, как демка игры Clive Barker’s Jericho появилась для свободного скачивания, многие люди оценили графику в ней как «лучшую которую они видели на сегодняшний момент». Как вы понимаете, я не мог пропустить такую игру.
Конфигурация компьютера: A64 X2 4200+, GeForce 8800 GTX, 2GB Ram. Все настройки игры на максимум.
После запуска, невооруженным глазом сразу видно, что игра сильно нагружена спецеффектами, поэтому, как я и ожидал, первым проходом идёт заполнение scene depth буфера. Это не чисто only-z проход. На нем в R32F рендер таргет выводится camera space z. Полученная текстура далее используется во множестве случаев. Все последующие проходы геометрии используют полученный z-буфер устанавливая DepthWrite в false.
Движок игры многопроходный. Для каждого источника света существует отдельный проход рендера геометрии. Если источник должен отбрасывать тень, то первым идет рендер в R32F текстуру тени (omni shadows замечены небыли). Затем отрисовывается вся геометрия, на которую влияет данный источник. Во время рендера используются: Диффузная текстура (сделайте монитор поярче, картиночки темноваты):
Нормал мапа:
Примняется DXT5 сжатие. X и Y компоненты вектора лежат в G и A каналах. Данный способ сжатия нормал мап широко известен. Z вычисляется как sqrt(1 – x^2 – y^2), так как нормали у нас единичной длины и мы знаем, что Z координата в tangent space normal maps всегда положительное число. Более подробно про сжатие карт номалей можно почитать тут и тут Так как R компонента пустует, то её можно использовать для хранения чего-то полезного (но не критичного к качеству). В Jericho данный канал текстуры хранит низкочастотное значение высоты, или, если говорить иностранным языком - low frequency height map. Используется данный канал для самого простого параллакс маппинга. Ни Parallax Occlusion Mapping (URL), ни Relief Mapping не реализованы. Канал B текстуры не используется. Height map:
Также присутствует specular map:
Кубемапа с бликами приассоциированная к объекту (для оружия например):
Также используется кубемапа привязанная к висящим на потолке фонарикам. Для правильной интерпретации цвета, которая должна давать последняя кубемапа, в шейдер передается также матрица положения соответствующего физического объекта. Таким образом трансформируя вектор направления «рисуемый объект» -> «фонарь» с помощью этой матрицы мы получаем красивенькие динамические блики на объектах вокруг данного светоизлучателя:
Сцена с одним источником:
с двумя:
с тремя:
После всех проходов геометрии, рисуется вода. Вернее, это та субстанция похожая на кровь, которая в изобилии встречается на демоуровне. Тут применяются 3 скроллящиеся с разными скоростями и в разные стороны карты нормалей. В общем ничего примечательного.
Далее идут частицы, которые забатчены довольно большими группами. К слову много частиц рисуется с аддитивным блендингом, создавая таким образом эффект volumetric light and fog. До партиклов:
После партиклов:
Depth of Field. Для достижения данного эффекта сцена даунсемплится в РТ размером ¼ от оригинального, а затем 2 раза размывается. Полученная текстура комбинируется с исходным изображением, используя метод описанный ребятами из АТИ в ShaderX3. Ближняя плоскость, дальняя плоскость, фокусное расстояние, leak reduction используя scene depth texture – всё присутствует. Хочу отметить, что фокусное расстояние определяется динамически, в зависимости от объекта на который мы сейчас смотрим (простое пересечения луча с ближайшим объектом). Этим достигается плавное изменение степени заблуривания объектов. Как я и предполагал, качество видео в Youtube не позволит проиллюстрировать DoF с переменным фокусным расстоянием, поэтому будет 2 картиночки (обратите внимание на дальнюю освещенную колонну):
После DoF идут т.н. “image distortion” пост-процесс эффекты. Подход достаточно оригинален и необычен. В отдельную ARGB8 текстуру рисуются все смещения текстурных координат (используются нормал мапы), которые должны искажать картинку. Это и эффект мокрого экрана (капли на стекле), и heat-haze от огня и эффект телекинеза. Затем используя полученную динамическую карту смещений и изображение сцены полученной на предыдущих шагах делаем distortion эффекты. Динамическая карта смещений:
Полученная картинка:
Следующим идёт генерация velocity буфера для motion blur эффекта. Подход такой же как и во многих других играх. В шейдер передаются предыдущая и текущая матрицы трансформации. После их применения вычисляется вектор смещения в XY плоскости, который записывается в ARGB8 текстуру. Дабы увеличить точность значений и зря не расходовать память а R канале содержится +Х, в G –X. В каналах B и A содержится +Y и –Y соответственно. Визуализация velocity буфера в момент включения зума на снайперке у Black (она один из 3-х играбельных персонажей в демке).
На следующем этапе используя полученный буфер размываем изображение:
и видео:
На следующем шаге идет очередной пост-процесс эффект. Сцена даунсемплится и размывается в несколько шагов до 1х1 изображения в котором хранится средняя яркость. Используя эту мини-текстуру выполняется bright pass, или говоря нашим, родным языком, выделяются яркие области. Текстура с яркими областями подвергается «диагональному» размыванию в виде буквы Х. Вверх-вправо, вверх-влево, вниз-вправо, вниз-влево. Полученные лучи крестообразных бликов собираются в одно изображение и блендится с оригинальной картинкой.
Обратите внимание на glow в районе руки:
Последни пунктом идет UI, которого совсем немного.
С 5-ю активными источниками получается где-то 1800 dip’ов и до 400К полигонов в кадре. Средние значения: 1200 draw calls и 200K полигонов. Вся геометрия в невыровненных вертекс-форматах, хотя данные немного сжаты: позиция во float3 и нормали в short4n. В движке хорошая сортировка как по шейдерам, так и по текстурам. ЦПУ в драйвере не сидит, что тоже хорошо. Смущает только объем загруженных в видеопамять текстур. Аж 550 мегабайт. Вот вам и хай-энд :)
В послесловии я хочу подлиться своими соображениями по поводу многопроходности. Раньше я думал, что с длинными шейдерами данный подход себя изжил. Но данный реверс отчетливо показал, что мои выводы были преждевременными. Главное это бороться за минимизацию pixel-processing overhead и количество батчей. Рендер Clive Barker’s Jericho демонстрирует отличные показатели по обоим пунктам.