Главная » Статьи » Painkiller » Прочее |
PainKiller: Технологии Рендера Графики | |
Итак, реверсил я на максимальных настройках графики ("insane" в параметрах конфиграции) , на видеокарте GF6600. Рендер игрового кадра состоит из следующих этапов: * Если на уровне есть вода, то рисуется рефлекшин мапа. Никаких упрощений геометрии и шейдеров, по сравнению с основным проходом (их детальное описание см. дальше), я не заметил. Рисуется все, что видно в главную камеру. Т.е. теоретически можем поиметь эффект внезапного исчезновения/появления отражений невидимых в камеру объектов. Практически же, я пытался поймать эту ситуацию (не долго правдаJ). Не поймал. * Тени для монстров. Для каждого используется RTT (render target texture) A8R8G8B8 128х128. * Блурим эти текстуры каждую по 2 раза простым 4 tap фильтром, используя смещения координат. Получаем: * Монстрики. Используется хардварный скининг. Точно количество костей не знаю, но учитывая что vs.1.1, смею предположить что около 20. Модель содержит, естественно, больше, поэтому она разбивается на несколько сабмешей, по костям. Количество влияний на вершину – 4. Скиненные персонажи рисуются в 1 проход, поэтому освещаются сразу. Текстура монстрика (оригинальный размер 1024x1024): Шейдер для монстров * Первый проход на объекты. FFP + простые шейдеры (1.1). При отрисовке земли используется 2 диффузные текстуры с рисунком камешков и травички, которые блендятся по заданной маске. Также используется лайтмапа. Шейдеры для земли Пример маски блендинга для земли: Пример лайтмапы для земли: При отрисовке всех остальных объектов используется лайтмапа и 1 диффузная текстура. Для ближней геометрии используется также текстура с деталями. Шейдеры для объектов Лайтмапа для объектов (оригинальный размер 1024x1024): * Вода. Первый проход. Сложные шейдеры, до конца не осилил Приводить их не буду. Большое количество входных параметров. Vs – 56 инструкций, ps – 31 инструкция. В этом проходе накладываем reflections, анимируя её используя 2 dudv карты. Волнение геометрии создаем анимацией вершин в vs. Я вообще обнаружил повертексную анимацию, только после того, как шейдер проанализировал, и увидел, что там меняется позиция вершины. Так думал, что вода плоская. * Второй проход для воды. Освещаем её 4-мя направленными (directional) источниками повертексно. Шейдеры для второго прохода для воды В арсенале PainKiller’а есть еще один тип воды. Без честных отражений, с использованием лайтмап. Такой тип используется на уровнях, где воды много. Например в локации C5L1_City_On_Water. Первый же тип используется на первом уровне аддона Battle Out Of Hell: C6L1_Orphanage. Думаю, что мощный редактор позволяет по-разному варьировать параметры воды, чтобы получить нужный визуальный стиль. * Небо. До 4-х слоев. Компоненты трехслойного неба: полусфера, внутри чашечка в зените, потом опять полусфера. Эти слои блендятся стандартным альфаблендом (srcalpha, invsrcalpha), и используют шейдеры точно такие же как при рендеринге земли. Неиспользованные слои (вспомним, что при рендеринге земли используется 2 текстуры для комбинирования) заменяются dummy текстурой. Пример текстур неба: Оригинальный размер 2048х512 Оригинальный размер 1024x1024 Оригинальный размер 1024x1024 * Второй проход на сцену – освещение. Тут все что земля, что остальные статические объекты рисуются одинаково. Освещение осуществляется от одного точечного источника, попиксельно. Весь процесс достаточно хитрый. Для учета расстояния используется текстура аттенюации такого содержания: В вертексном шейдере мы вычисляем расстояние от источника света до вершины. Поделив полученное расстояние на дистанцию влияния источника, мы пакуем вектор так, что все расстояния на которые влияет источник попадают в диапазон [-1,1]. Скейлим дальше в [0,1]. Получили текстурные координаты для лукапа в текстуру аттенюации (так как address mode установлен в clamp, то все лукапы за пределами текстуры будут происходить по ее границе). В нашем примере используется простой кружочек, что означает простое постепенное ослабление освещения с расстоянием. Но задав, например тот же кружочек, но инвертировав его цвет, мы получим освещёнными дальние объекты, но неосвещённые ближние. В общем вариантов масса. Шейдеры для освещения статики Основные стетйты тендера установлены в: Depth write = false Srcblend = one Dstblend = one Т.е. получаем блендинг pixel color + incoming color * Третий проход на статику (только если включены тени). Рисуем геометрию, проективно накладывая текстуры с тенюшками от монстриков. Ничего особо интересного, разве что, тени еще больше разблуриваются (2 лукапа на текстуру). * Партиклы. Используются динамические VB/IB. FFP. * Оружие. Sm30. Normal mapping от нескольких (я видел 2) directional источников освещения. Тут все стандартно, за исключением того, что normal map’ы у них не в tangent, a в model space. Просто источники они тоже поворачивают в model space. Анимации оружия скинтся хардварно (на вертекс влияет 2 кости) Normal map оружия (оригинальный размер 1024х1024): Specular mask хранится в альфа канале normal map’ы (оригинальный размер 1024х1024) Диффузная карта (оригинальный размер 1024х1024): Шейдеры для оружия * Downsample картики, с небольшим color remap’ом, для выделения ярких областей. * Separable Gaussian blur. Горизонтальный, вертикальный, 13х13. Получили bloom текстуру. Шейдер horizontal pass’a Блум текстура: * Блендинг с блум тектурой. FFP. Блендим по формуле: blurred текстура + текстура сцены. Полученный результат отлично выглядит на динамическом небе. * UI. Ингейм интерфейса в painkiller почти нет. Думаю именно по этому он сделан так ужасно J Рисуется поэлементно, группировок никаких нет. Зато в startup menu разработчики постарались. Особенно убило окно конфигурации кнопок. Несколько скринов из под NVPerfHUD’а: 3 раза перетиреть почти весь экран, причем такими маленькими квадратиками, это надо уметь 2000 дипов. * Demon mode. Кто не играл, это такой режим, когда собираешь 66 душ, и превращаешься в бессмертного психа-убийцу. :) Процесс рендеринга таков: Рисуем сцену нормально в RTT. Затем из нее делаем ч.б. картинку (способ стандартный: dp3 pixel, float3(0.3, 0.59, 0.11). Потом рисуем монстров специальным шейдером, который используя нормали и градиентную текстуру выделяет границу объекта красным цветом. И, напоследок, используя dudv bump map, вносим в картинку искажения. Для получения motion blur эффекта блендим предыдущий и текущий кадр используя весовые коэффициенты. Demon mode shader Градиентная текстура: Получаем вот такую картинку: Вот собственно и всё. Теперь общие сведения по рендеру. 1. Дипов мало. В аутдорах до 1000 (редко больше, но бывает). В индорах обычно меньше. Иногда сильно. При таком количестве проходов, это очень хороший показатель. 2. Сортировка по материалам и текстурам хорошая. D3DXEffect не используется. 3. Арт просто супер. Эффекты там где надо. Все хорошо настроены и сбалансированы. Дизайн уровней великолепен. Текстуры сделаны очень качественно. Полетайте в редакторе по C6L2_LoonyPark, например. Просто загляденье. 4. Загрузка видеопамяти 100 – 120 MB (у видеокарты на борту 128). AGP – 6 MB. Чувствуется умелая рука настройщика. 5. LOD’ов геометрии нет. Есть удаление glow партиклов по расстоянию. Мои мини выводы (большинство перекликается с выводами по FlatOut2): 1. Первое, что надо продумать в рендере, это минимизация DP calls. PainKiller наглядно показал, что с малым количеством DP больше простора для применения интересных и разнообразных алгоритмов (которые могут потребовать несколько проходов рендера). Если для одного прохода требуется больше 1000 DP, то многопроходным алгоритмам, скорее всего, дорога закрыта. 2. Не надо брезговать предрасчетом освещения. Не подходят лайтмапы? Рассмотрите использование ambient occlusion. Никакие реалтайм алгоритмы не дадут вам того-же качества при таком малом потреблении ресурсов. 3. Арт и хорошая дизайнерская работа решает с огромной силой. Эти компоненты не просто делают игру красивее, но и позволяют применить более простые алгоритмы визуализации без ущерба для картинки, что разгружает рендер. Получается, что хороший арт ускоряет игру! Во как! J 4. Чтобы был хороший дизайн, надо давать хорошие рычаги управления этим дизайном (это не из исследования рендера, это после ознакомления с редактором и структурирования своих мыслей, после этого исследования). Методы управления по типу: «сказать программисту, чтобы исправил параметры материала там и там», не работают. Знаю по собственному опыту. Признаю, важность этого момента я недооценивал. Раздел с комментированными шейдерами. Шейдер для монстров vs_1_1 dcl_position v0 dcl_normal v1 dcl_texcoord v2 dcl_blendweight v3 dcl_blendindices v4 dp4 oT0.x, v2, c24 dp4 oT0.y, v2, c25 dp4 oT1.x, v2, c24 dp4 oT1.y, v2, c25 dp3 r0.w, v3, c8.z add r0.w, c8.z, -r0.w mad r1, v4.zyxw, c8.y, c8.x mov a0.x, r1.x m3x3 r5.xyz, v1, c0[a0.x] // Скининг позиции по первой кости m4x3 r6.xyz, v0, c0[a0.x] // Скининг нормали mul r3, r5.xyzz, v3.x // Блендим по весу кости mul r4, r6.xyzz, v3.x mov a0.x, r1.y m3x3 r5.xyz, v1, c0[a0.x] // тут и дальше аналогично по 2-4 костям. m4x3 r6.xyz, v0, c0[a0.x] mad r3, r5.xyzz, v3.y, r3 mad r4, r6.xyzz, v3.y, r4 mov a0.x, r1.z m3x3 r5.xyz, v1, c0[a0.x] m4x3 r6.xyz, v0, c0[a0.x] mad r3, r5.xyzz, v3.z, r3 mad r4, r6.xyzz, v3.z, r4 mov r4.w, c8.z m4x4 r0, r4, c0 mov oPos, r0 mad oFog, r0.z, c9.y, c9.x mov r4, c11.xyzz mov r0.w, c10.w dp3 r0.x, r3, c13 // n dot l dp3 r0.y, r3, c14 // n dot h lit r1, r0 // посчитали освещение от directional источника. Тут всего 1 источник, но может быть больше. Считается аналогично. mad oD0, r1.y, c12, r4 mul r5, r1.z, c12 mul oD1, r5, c10 ps – ffp. Шейдеры для земли: vs_1_1 dcl_position v0 dcl_texcoord v1 dcl_texcoord1 v2 m4x4 r0, v0, c0 mov oPos, r0 mad oFog, r0.w, c9.y, c9.x dp4 oT0.x, v1, c24 dp4 oT0.y, v1, c25 dp4 oT1.x, v1, c27 dp4 oT1.y, v1, c28 mov oT2.xy, v2.xyyy mov oT3.xy, v2.xyyy // approximately 12 instruction slots used ps_1_1 tex t0 // ground texture 1 tex t1 // ground texture 2 tex t2 // blend map tex t3 // light map mul r0.xyz, t0, 1-t2 // Темные регионы на маске блендинга соответсвуют t0 + mul r0.w, t0.w, 1-t2.w // Зачем тут параллелить альфа и вектор пайпы? Не пойму. mad r0.xyz, t1, t2, r0 // Светлые регионы - t1. Результат сложили + mad r0.w, t1.w, t2.w, r0.w // Опять… mul_x2 r0.xyz, r0, t3 // * на лайтмапу и * 2 + mul r0.w, r0.w, t3.w // альфу на 2 не умножаем. // approximately 7 instruction slots used (4 texture, 3 arithmetic) Шейдеры для объектов: vs_1_1 dcl_position v0 dcl_texcoord v1 dcl_texcoord1 v2 m4x4 r0, v0, c0 mov oPos, r0 mad oFog, r0.z, c9.y, c9.x // считаем туманчик dp4 oT0.x, v1, c24 // тут какой-то рескейлинг. Присутствует во всех шейдерах. Глубоко не вникал. dp4 oT0.y, v1, c25 dp4 oT1.x, v1, c27 dp4 oT1.y, v1, c28 mov oT2, v2 // approximately 11 instruction slots used На момент теста в константах было вот что. // c24 – 20, 0, 0, 0 // c25 – 0, 20, 0, 0 // c27 – 20, 0, 0, 0 // c28 – 0, 20, 0, 0 ps – FFP t0 – base t1 – detail – для ближних объектов этого нет. t2 – lightmap формула блендинга текстур: (base + detail – 0.5) * lightmap * 2 Шейдеры для второго прохода для воды: vs_1_1 dcl_position v0 dcl_normal v1 dcl_texcoord v2 m4x4 r0, v0, c0 mov oPos, r0 mad oFog, r0.z, c9.y, c9.x dp4 oT0.x, v2, c24 dp4 oT0.y, v2, c25 dp4 oT1.x, v2, c27 dp4 oT1.y, v2, c28 mov r4, c11.xyzz // r4.xyzw = 0.125 mov r0.w, c10.w m3x3 r3.xyz, v1, c4 // во view space dp3 r0.x, r3, c13 // n dot l dp3 r0.y, r3, c14 // n dot h lit r1, r0 // осветили. После операции r1.y = diffuse, r1.z = specular mad r4, r1.y, c12, r4 // в c12 хранится цвет источника света mul r5, r1.z, c12 // для спекуляра тоже самое. dp3 r0.x, r3, c16 // дальше аналогично накапливаем diffuse и specular contribution для еще 3-х источников. dp3 r0.y, r3, c17 lit r1, r0 mad r4, r1.y, c15, r4 mad r5, r1.z, c15, r5 dp3 r0.x, r3, c19 dp3 r0.y, r3, c20 lit r1, r0 mad r4, r1.y, c18, r4 mad r5, r1.z, c18, r5 dp3 r0.x, r3, c22 dp3 r0.y, r3, c23 lit r1, r0 mad oD0, r1.y, c21, r4 mad r5, r1.z, c21, r5 mul oD1, r5, c10 // approximately 36 instruction slots used ps – ffp Шейдеры для освещения статики vs_1_1 dcl_position v0 dcl_texcoord v1 dcl_normal v3 m4x4 r0, v0, c0 mov oPos, r0 mad oFog, r0.w, c9.y, c9.x add r0, v0, -c13 // world pos – light pos mul r1, r0, c12.w // В с12.w коэффициент дальности освещения источником. После умножения все расстояния на которые влияет источник, попадут в диапазон [-1, 1] mad r1, r1, c8.w, c8.w // Пакуем вектор [-1, 1]->[0, 1] mov oT0.xy, r1.xyyy // xy записали в tc0 mov oT1.x, r1.z // z – в tc1 mov oT1.y, c8.w // c8.w = 0.5, получаем эмуляцию 1D лукапа по горизонтальной линии в центре текстуры. mov oT2, -r0 // записали vertex to light vector dp4 oT3.x, v1, c33 // учитывая, что c33 1000, а с34 0100, эти 2 строки очень хитрый метод записать mov oT3.xy, v1. Возможно тут сделано с прицелом на анимацию текстурных координат. dp4 oT3.y, v1, c34 mov oD0, c12 mad oD1, v3, c8.w, c8.w // запаковали нормаль в цвет // approximately 17 instruction slots used ps_1_1 tex t0 // Кружочек, снаружи непрозрачный, внутри прозрачный. А так белый. tex t1 // Тот же кружочек tex t2 // normalized vertex to light vector from normalization cubemap tex t3 // diffuse texture mul r0.xyz, t3.w, v0 // diffuse color * base texture alpha dp3_sat r1, v1_bx2, t2_bx2 // n dot l mul r0.xyz, r0, r1 + add r0.w, 1-t0.w, -t1.w // Тут получаем аттенюацию по расстоянию, используя наш кружочек. t0.w – аттенюация по xy координатам, t1.w по z. mul r0.xyz, r0, r0.w // Применили вычисленную аттенюацию к цвету. + mul r0.w, r1.w, r0.w mul_x2 r0.xyz, r0, t3 // добавили diffuse texture // approximately 9 instruction slots used (4 texture, 5 arithmetic) Шейдеры для оружия // Registers: // // Name Reg Size // ------------ ----- ---- // GClipMat c0 4 // GFogParams c9 1 // GLightDir0 c13 1 // GHalfDir0 c14 1 // GLightDir1 c16 1 // GHalfDir1 c17 1 // GSkinBones c27 69 vs_3_0 def c4, 765.005859, 1, 0, 0 dcl_position v0 dcl_texcoord v1 dcl_blendweight v2 dcl_blendindices v3 dcl_position o0 dcl_fog o1.x dcl_texcoord o2.xy dcl_texcoord1 o3.xy dcl_texcoord2 o4.xyz dcl_texcoord3 o5.xyz dcl_texcoord4 o6.xyz dcl_texcoord5 o7.xyz mul r0.xy, c4.x, v3.zyzw mova a0.xy, r0 dp4 r1.x, v0, c27[a0.x] // Скининг dp4 r1.y, v0, c28[a0.x] dp4 r1.z, v0, c29[a0.x] dp4 r2.x, v0, c27[a0.y] dp4 r2.y, v0, c28[a0.y] dp4 r2.z, v0, c29[a0.y] lrp r0.xyz, v2.x, r1, r2 // 2 кости, блендим по весам. mov r0.w, c4.y dp4 o0.x, r0, c0 // x * wvp dp4 o0.y, r0, c1 // y * wvp mov r4.xyz, c28[a0.x] // Тут немножко хитро. Так как используется не tangent space normal map, а model space, то параметры источников света (dir, half) перед установкой в константы переводится в это пространство. Затем поворачиваем вектора в соответствии с матрицами костей. mul r3.xyz, r4, c13.y // y в animated model space mul r1.xyz, r4, c14.y mov r2.xyz, c27[a0.x] mad r5.xyz, r2, c13.x, r3 // x mad r3.xyz, r2, c14.x, r1 mov r1.xyz, c29[a0.x] mad o4.xyz, r1, c13.z, r5 // o4 - light vector 0 в animated model space mad o5.xyz, r1, c14.z, r3 // o5 – half vector 0 в animated model space mul r3.xyz, r4, c16.y mul r4.xyz, r4, c17.y mad r3.xyz, r2, c16.x, r3 mad r2.xyz, r2, c17.x, r4 mad o6.xyz, r1, c16.z, r3 // o6 – light vector 1 в animated model space mad o7.xyz, r1, c17.z, r2 // o7 – half vector 1 в animated model space dp4 r1.z, r0, c2 // z * wvp dp4 r1.w, r0, c3 // w * wvp mad o1.x, r1.z, c9.y, c9.x // fog mov o0.zw, r1 mov o2.xy, v1 // tc0 pass through mov o3.xy, v1 // approximately 33 instruction slots used // Name Reg Size // ------------- ----- ---- // GLightEnable0 b0 1 // GLightEnable1 b1 1 // GAmbientColor c0 1 // GLightColor0 c1 1 // GLightColor1 c2 1 // ColorSampler s0 1 // NormalSampler s1 1 ps_3_0 def c3, 2, -1, 10, 0 dcl_texcoord v2.xy dcl_texcoord1 v3.xy dcl_texcoord2 v4.xyz dcl_texcoord3 v5.xyz dcl_texcoord4 v6.xyz dcl_texcoord5 v7.xyz dcl_2d s0 dcl_2d s1 texld_pp r0, v2, s0 texld r1, v3, s1 if b0 mad_pp r3.xyz, c3.x, r1, c3.y // распаковали нормаль из NM nrm_pp r2.xyz, v4 // нормализировали light vector 0 nrm_pp r1.xyz, v5 // нормализировали half vector 0 dp3_sat_pp r2.w, r3, r2 // n dot l dp3_sat_pp r2.z, r3, r1 // n dot h pow_pp r1.z, r2.z, c3.z // посчитали спекуляр mul_pp r2.xyz, r2.w, c1 // учли цвет источника mul_pp r1.xyz, r1.z, r2 // учли спекуляр if b1 // для второго тоже самое nrm_pp r4.xyz, v7 nrm_pp r2.xyz, v6 dp3_sat_pp r4.w, r3, r4 dp3_sat_pp r2.z, r3, r2 pow_pp r3.w, r4.w, c3.z mul_pp r2.xyz, r2.z, c2 mad_pp r1.xyz, r3.w, r2, r1 mad_pp r2.xyz, r2.w, c1, r2 endif add r2.xyz, r2, c0 // добавили амбиент mul r0.xyz, r0, r2 // добавили диффузную текстуру mad_pp oC0.xyz, r1.w, r1, r0 // в альфа канале normal map’ы- specular mask mov_pp oC0.w, r0.w else mul_pp oC0.xyz, r0, c0 mov_pp oC0.w, r0.w endif // approximately 45 instruction slots used (2 texture, 43 arithmetic) Шейдер horizontal pass’a vs_1_1 dcl_position v0 dcl_texcoord v1 add oT1.xy, v1, c20 // Посмещали в стороны текстурные координаты add oT2.xy, v1, c21 add oT3.xy, v1, c22 add oT4.xy, v1, -c20 add oT5.xy, v1, -c21 add oT6.xy, v1, -c22 mov oPos, v0 mov oT0.xy, v1 ps_2_0 dcl t0.xy dcl t1.xy dcl t2.xy dcl t3.xy dcl t4.xy dcl t5.xy dcl t6.xy dcl_2d s0 texld_pp r6, t1, s0 // 7 семплов используя TC посчитанные в VS texld_pp r5, t0, s0 texld_pp r4, t2, s0 texld_pp r3, t3, s0 texld_pp r2, t4, s0 texld_pp r1, t5, s0 texld_pp r0, t6, s0 mul r6, r6, c1 mad_pp r5, r5, c0, r6 // Аккумулируем с учетом весов. mad_pp r4, r4, c2, r5 mad_pp r3, r3, c3, r4 mad_pp r2, r2, c1, r3 mad_pp r1, r1, c2, r2 mad_pp r6, r0, c3, r1 add r5.xy, t0, c7 // Посчитали координаты еще 6 семплов. add r4.xy, t0, c8 // Можно было заюзать все 4 компонента у oTx регистров в VS. add r3.xy, t0, c9 add r2.xy, t0, -c7 add r1.xy, t0, -c8 add r0.xy, t0, -c9 texld_pp r5, r5, s0 texld_pp r4, r4, s0 texld_pp r3, r3, s0 texld_pp r2, r2, s0 texld_pp r1, r1, s0 texld_pp r0, r0, s0 mad_pp r5, r5, c4, r6 mad_pp r4, r4, c5, r5 mad_pp r3, r3, c6, r4 mad_pp r2, r2, c4, r3 mad_pp r1, r1, c5, r2 mad_pp r0, r0, c6, r1 mov_pp oC0, r0 // approximately 33 instruction slots used (13 texture, 20 arithmetic) Demon mode shader ps_1_1 tex t0 // Семпл из оригинальной текстуры texbem t1, t0 // Эффект мембраны, который возникает когда стреляешь в demon mode, реализуется твиками bumpenvmat текстурного стейджа. tex t2 // Используем текстуру с предыдущего кадра, чтобы получить плавающий эффект. mul r0, t1, c0 mad r0, t2, c1, r0 // Блендим текущий и предыдущий кадр по весам. Получаем motion blur like эффект. | |
Просмотров: 2977 | Комментарии: 3 | Теги: |
Всего комментариев: 3 | |
| |