Как устроены графические движки популярных игр с мировым именем? Какие технологии применяют разработчики в самых крупных игровых компаниях? Действительно ли, чтобы сделать красивую игровую графику необходимо применять самые передовые технологии современной 3D графики? На эти вопросы мы попробуем ответить на примере рендер части игры Diablo3, от компании Blizzard Entertainment.
Я давно занимаюсь в сфере игровой разработки, и мое хобби реверс-инжиниринг графических движков популярных игровых продуктов. Когда вышел долгожданный сиквел серии Diablo, я сразу захотел узнать, какие технологии использовали разработчики в своем детище.
Рендер игры построен на базе технологии Direct3D 9. Это позволяет покрыть более широкую аппаратную базу видеокарт, а те расширенные возможности, которые предлагает D3D 10 и 11 зачастую либо вовсе не нужны, либо реализуемы теми или иными способами в девятой версии.
Тени
Для всей статической геометрии уровня используются предрасчитанные лайтмапы. Да-да, старый добрый способ, который применяется со времен, когда 3D-акселераторы стали поддерживать мультитекстурирование.
Лайтмапа просчитывается заранее в пакете 3Д моделирования (3ds max, Maya), либо собственным рейтрейсером в редакторе уровней. Одна такая текстура используется для нескольких игровых объектов, либо их частей, если объект крупный (например, террейн).
Для динамических объектов (монстры, фигурки персонажа) используются динамические тени выполненные по технологии “shadow map” (stencil shadows в наше время уже практически не используется). Разработчики решили отступить от классических канонов в этой сфере, и не использовали hardware shadows (текстуры которые могут быть использованы как буфера глубины, и поддерживающие аппаратный Percentage Closer Filtering – PCF), которые предлагают все популярные производителями видеокарт. Вместо этого, была применена технология Variance Shadow Maps. Она позволяет получить мягкие края путем стандартного размытия текстуры тени (для классических карт теней этот метод неприменим, так как усреднение значений глубины пикселя не имеет смысла). Подробности VSM я расписывать не буду (см. полезные ссылки в конце статьи), скажу лишь только, что для ее реализации необходимо хранить 2 значения: глубину пикселя и глубину пикселя в квадрате. Именно второе значение диктует довольно жесткие условия к точности хранения этой информации, поэтому была выбрана текстура формата A32B32G32R32 float. Размер ее при максимальных настройках качества теней 2048х2048.
Процесс создания карты теней стандартный. Рисуем все объекты отбрасывающие тень (окклюдеры) в карту тени с позиции источника освещения. Размываем карту тени сначала по горизонтали, а затем по вертикали. При рендере объектов, которые должны получать тень (ресиверы), семплим шадоу мапу, определяем степень освещенности пикселя и соответственным образом затемняем финальный цвет. Семплинг карты теней должен происходить с билинейной фильтрацией. Аппаратная фильтрация формата A32B32G32R32F поддерживается далеко не всей линейкой shader model 3.0 capable видеокарт, поэтому она реализуется программно, в шейдере (хотя на моей видеокарте она поддерживается, но это не учли).
Рендеринг теней происходит с ортографической проекцией, если тень от направленного (directional) источника (солнце), либо с перспективной для конусовидных (omni). Техники перспективного искажения карты теней (например, Perspective Shadow Maps,Trapezoidal Shadow Maps и др.) для положения камеры используемой в игре (направление взгляда сверху вниз, под небольшим углом к направлению главного источнику освещения) не нужны и не используются. Каскадное разделение теней на сектора (Cascaded Shadow Maps, Parallel Split Shadow Maps) не реализованы по тем же причинам.
Карта тени в разрешении 512х512 со сглаживанием и без:
Шейдер для патча террейна в варианте со сглаживанием состоит из 12 текстурных и 59 арифметических инструкций. Без сглаживания 10 и 29 соответственно. Разница в арифметических инструкциях есть реализация билинейной фильтрации и VSM.
Динамическое освещение
Удивительно, но все динамическое освещение повертексное. Как в старые добрые времена. Никаких карт нормалей в игре нет. Смелое решение, но учитывая финальный результат, очевидно, что он себя оправдал на 100%. Недостатка в детализации геометрии совершенно не ощущается. В вертексном шейдере реализованы один точечный источник света с квадратичной аттенюацией (как в классических FFP формулах), один цилиндрический источник (он используется как подсветка персонажа, для освещения близкого окружения героя) и до 16 точечных источников с простейшей линейной аттенюацией по расстоянию.
В игре реализованы также объемные источники освещения. Сделаны они следующим образом. Рисуем сферу, или другую выпуклую фигуру, в месте источника освещения. В вершинном шейдере высчитываем альфу вертексов на основе нормали и вектора направления камеры. Чем больше угол между ними, тем большая прозрачность должна быть. Получаем полупрозрачную сферу с увеличением прозрачности от центра к краям. Так как эта сфера будет пересекать геометрию уровня, мы получим визуальный артефакт изображения в месте пересечения объектов уровня и самой сферы. Данный недостаток исправляется абсолютно таким же методом, как и делаются так называемые soft particles. Берется выборка из буфера глубины и сравнивается с глубиной отрисовывамого пикселя. Если значения близки, то модифицируя альфу (уменьшая ее до нуля), мы делаем место пересечения геометрий невидимым.
Специальные эффекты
Из интересных эффектов можно выделить проективное текстурирование. Для наложения на поверхность земли текстур различных игровых заклинаний (например, крики варвара, лужи яда, огненные дорожки монстров и т.д.) все эти эффекты рендерятся в отдельную текстуру:
Затем выполняется повторный рендеринг всей геометрии, на которую должно быть выполнено проективное текстурирование с использованием построенного кумулятивного изображения с проецируемыми графическими эффектами. Смешивание изображений выполняется по альфа каналу.
Для некоторых эффектов (пост-процесс, в частности) необходима информация о глубине сцены в данной точке. Стандартные средства Direct3D 9 не позволяют получить буфер глубины как текстуру для последующего чтения. Очевидным вариантом будет рендеринг всей сцены еще раз, с выводом глубины пикселей в текстуру формата R32F. Этот метод в большинстве случаев неприемлем, так как удвоение отрисовываемой геометрии сильно скажется на общей производительности игры. Производители графических адаптеров давно знают об этой проблеме, и предлагают специальные форматы текстур, которые могут использоваться и как текстура в шейдере, и как буфер глубины при рендеринге. Одним из таких форматов является так называемый INTZ формат. Он то и используется в Diablo III. Текстура такого типа используется при рендеринге сцены как depth buffer, а затем значения из нее можно получить в шейдерах, где необходима информация о глубине. Я не знаю, как выполняется рендеринг на аппаратуре, которая не поддерживает INTZ текстуры (не все shader model 3 видеокарты поддерживают данный «хак»), у меня нет видеокарты без такой поддержки. Возможно, выполняется дополнительный проход, либо эффекты, зависящие от глубины, реализуются по-другому, либо совсем выключаются.
Подсветка объектов под курсором реализуется путем рендеринга выделяемого объекта в отдельную текстуру. Шейдер при этом используется простейший – вывод единицы в альфа канал рендер таргета, и цвета выделения в rgb каналы. Затем полученная текстура размывается по горизонтали и вертикали. Для корректного наложения эффекта и получения финального изображения необходимо оставить только ореол объекта, но не его основной силуэт. Имея оригинальное (не размытое) изображение, финальный шейдер наложения проверяет значение альфа канала в этой текстуре. Если он равен 1 (объект в этом пикселе есть), то выводимый альфа канал устанавливается в ноль. Если значение 0 (объекта в этом пикселе нет), то используется альфа канал размытой текстуры.
Пост-процесс эффекты
Количеством пост-процесс эффектов игра похвастаться не может. Среди всего арсенала были замечены bloom, full screen distortion и полноэкранное сглаживание, выполненное по технологии FXAA. Дисторшн реализуется по классической схеме. Отрисовываем партиклы, которые должны привносить искажения в финальную картинку (горячий воздух, например) в специальную текстуру. Записываемые данные являются u и v смещениями текстурных координат. В следующем полноэкранном проходе используем эту текстуру и смещаем текстурные координаты для семплинга основного изображения сцены.
Полноэкранное сглаживание выполнено также как пост-процесс эффект. Причин этому несколько. Использование INTZ буфера глубины становится невозможным (нельзя создать чистый multisampled INTZ depth buffer для последующего копирования его в non-multisampled INTZ текстуру), и shadow map будет занимать очень много памяти (напомню, что ее формат A32R32G32B32F, т.е. 16 байт на пиксель). Полноэкранное сглаживание в игре выполнено по технологии Fast Approximate Anti-Aliasing (FXAA).
Геометрия и материалы
Все данные вершин игровых моделей упакованы в cache-friendly 32 байт формат. Исключение составляют анимированные модели, там 48. Дополнительные данные это веса костей их индексы. В игре используется скелетная анимация, которая выполняется в шейдере. По этой причине для анимированных моделей количество точечных источников ограничено семью, из-за нехватки константных регистров для хранения параметров источников освещения и матриц костей.
Общее количество draw call’s невелико. Значение колеблется в пределах 300-800 DIP, что является хорошим показателем.
Шейдера выполнены по технологии uber-shader, т.е. компиляция множества вариантов одного эффекта с помощью перебора набора дефайнов препроцессора. Например эффект может быть с туманом и без, с тенью и без, с лайтмапой и без. За туман, тень и лайтмапу отвечает определенный define вида: #define USE_FOG 1. В теле шейдера блок кода, отвечающий за наложение тумана выполнен внутри блока #if USE_FOG … #endif. Таким образом переключая значение USE_FOG 1/0, мы получаем шейдер с туманом и без. Схожим образом делаются и все эффекты. Система сборки всех вариантов шейдеров, автоматически перебирает весь набор значений дефайнов, и для каждого набора компилирует шейдер.
Пользовательский интерфейс
Внутриигровой интерфейс отрисовывается на экран довольно стандартно. Особой группировки элементов с целью уменьшения вызовов на отрисовку (DIP calls) не наблюдается. Хочется отметить рендеринг текста. Подготовка символов для рендера очень похожа на метод используемый в Scaleform GFX. Все уникальные символы отрисовываются в отдельную текстуру, и уже эта текстура используется для рендеринга текста. Не смотря на схожесть текстового рендера, сам Scaleform не используется.
Послесловие
Сам рендер оставляет приятные впечатления. Такой себе микс олдскула и некоторых современных веяний. Производительность на высоте при красивой картинке (как всегда у игр от Blizzard, собственно). Большую роль в этой всей красоте отыгрывает работа художников и дизайнеров. Diablo III еще раз доказывает, что очень красивую графику можно сделать и на не самом технологичном рендере.
Полезные ссылки
— Variance Shadow Maps. www.punkuser.net/vsm/
— FXAA. developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf
— Список известных GPU «хаков». aras-p.info/texts/D3D9GPUHacks.html
Автор: UrsusDominatus