Arseny Kapoulkine:
Традиционно сходил на доклад от Крайтека про графику в Крайзисе.

Moving to the Next Generation: The Rendering Technology of Ryse

Speaker: Nicolas Schulz (Crytek)
Вдруг оказалось что Ryse это не крайзис! Тут много информации, я постараюсь конспективно но может не получиться.
Значит есть игра Ryse, launch title на Xbox1. По сложившейся традиции (см. http://blog.gamedeff.com/?p=642), на проекте опять мало рендер программистов.
Но есть новый набор challenges.
С точки зрения мощности GPU по сравнению с top PC на момент выхода таргет железка уже слабая,
так что сделать еще эффектов по сравнению с Crysis 3 уже не выйдет.
Плюс на Crysis 3 и так много эффектов и неплохая картинка, так что надо брать другим.
Но при этом графика очень важна т.к. ничего другого нет.
Т.е. я хотел сказать, т.к. всем нужна графика.
Соответственно будем поднимать уровень ключевых элементов - шейдинг, лайтинг, материалы, GI,
и делать чтобы они были во-первых consistent, а во-вторых чтобы они выглядели меньше как игровой рендер и больше как кинематографический.
Т.е. упор на уменьшение альясинга, уменьшение контраста, менее агрессивные пост эффекты.

Всем будет править physically based shading

Как уже модно последние несколько лет - energy conserving итп.
Говорят, что очень важно если начал делать physically based то уже идти all the way - т.е. например если у вас energy conserving модель то в нее снаружи добавить левый диффузный фактор нельзя, это убирает преимущества от physically based (которые в целом сводятся к тому что тюнить материалы можно отдельно от лайтинга - материалы должны выглядеть "естественно" при любом освещении - ну и тюнить материалы проще т.к. меньше странных параметров)
Материал задается рядом текстур - albedo (хотели калибровать по референсам но не успели), normal, specular reflectance (артисты рисуют ее опираясь на index of refraction реальных материалов) и surface roughness.
Вместо roughness в которой 0 означает гладкий и 1 означает бугристый артисты рисуют inverse roughness т.к. им проще понять что белое означает гладкое чем наоборот.
Дальше выберем shading model которой будем пользоваться почти для всех материалов.
Это будет Cook-Torrance, в качестве NDF выберем GGX.
В качестве Fresnel term выберем приближенную формулу Schlick (которая со степенью 5, проверенную годами)
В качестве Visibility term выберем Schlick-Smith approximation, но roughness в ней слегка изменен (не записано как)
Вообще по-моему теперь модно обсуждать у кого какие функции вставлены в cook-torrance,
и кто их как подправил.
Там есть куча трейдофов, люди из крайтека попробовали всякого разного и вот остановились на таком.
Кстати о roughness - хочется чтобы изменение в текстуре приводило к визуально линейному изменению спекулярного пятна.
Эмпирически была выведена формула alpha = (1 - smoothness * 0.7)^6.
Говорят что результат с т.з. распределения похож на 2^(smoothness*16) для blinn-phong; в эпоху physically-based blinn-phong возводить 2 в roughness вроде было нормой, так что логично сравнивать с этим.
В качестве Diffuse BRDF вместо константного диффуза (традиционная модель Ламберта) используют Oren-Nayar.
Используют т.к. модель имеет эффективные приближенные формулы и сходится к Ламберту.

Как считать поняли, теперь давайте поймем как шейдить

Начинали с кодобазы Crysis 2, там был deferred lighting (два прохода)
В прошлом году (см. ссылку выше) было рассказано как для Crysis 3 перешли на тонкий gbuffer с полностью deferred shading.
В Ryse тоже перешли на deferred shading.
Основной бонус deferred lighting - очень тонкий gbuffer чтобы экономить bandwidth.
Проблема в том, что в physically based shading спекуляр есть везде, с Френелем - а для френеля нужен specular reflectance (F0); это цвет так что тонкого gbuffer уже как-то не выходит.
Лейаут gbuffer традиционно хитрый чтобы экономить bandwidth, сейчас попробую воспроизвести и не налажать.
  • RT0: RGB содержит нормаль (т.к. 8 бит не очень хватает для отражений, то используют best fit normals технику запаковки, см. какую-то из предыдущих публикаций crytek), A содержит что-нибудь еще, например фактор translucency (используется для skin shading и foliage)
  • RT1: RGB содержит albedo, A содержит коэффициент управляющий subsurface scattering.
  • RT2: R содержит roughness/smoothness, GBA каналы содержат цвет спекуляра для металла либо transmittance color для диэлектриков.
Там хитрая запаковка потому что есть фундаментальное ограничение - хочется вбленживать например декали в эти буферы, но при блендинге нельзя управлять output alpha channel как хочется.
Цвет спекуляра хранится в YCbCr варианте; если объект не металлический то цвет спекуляра белый, CbCr не используются - они содержат transmittance color.
Я тут не очень все уловил, из слайдов будет наверное более понятно, но вообще специфика запаковки каналов по-моему тоже обычно не очень интересна... (я по крайней мере уже видел слишком много разных и устал их сравнивать)
Arseny Kapoulkine:
А вот и слайды, http://www.crytek.com/download/2014_03_25_CRYENGINE_GDC_Schultz.pdf - можно проверить много ли я наврал в рассказе выше.
Arseny Kapoulkine:
Ну и конечно кроме этого есть RT с глубиной, и есть RT3 (вроде HDR), в которой кажется emissive цвет.

Теперь собственно вопрос - а почему не Forward+?

Проблема в том что времени на проект было не очень много чтобы экспериментировать с bleeding edge технологиями,
а основная масса опубликованного Forward+ ресерча не решает важных проблем.
Т.е. просто нарисовать очень много цветных сферических источников, но еще нужны тени, много разных типов источников света, текстуры прожекторов.
Непонятно как эффективно делать декалы которые модифицируют только один атрибут (т.е. например декал который меняет albedo но наследует нормали и roughness от поверхности под ним)
Есть опасение что из-за шейдера с большим количеством бранчинга по типу источников итп. будет плохой wave utilization на GPU т.к. шейдер будет использовать очень много регистров.
Опять появляется проблема маленьких треугольников (quad utilization страдает) - эта проблема менее актуальна в deferred shading.
Ну и так далее.
В итоге побоялись использовать это как основное решение и вместо этого основным решением является tile-based deferred shading,
но используют forward+ для избранных объектов (волосы, глаза) для кастомизации BRDF.
Используют ли forward+ для остальных прозрачных объектов не уловил.
Так, конспективно что-то у меня не выходит, ну да и черт с ним.

Решаем проблему specular aliasing

Считаем Toksvig factor (метрика variance в нормалях), запекаем его в мипы roughness (см. http://blog.selfshadow.com/2011/07/22/specular-showdown/ кажется),
это создает coupling между картой нормали и картой roughness.
Чтобы гарантировать что артисты не начинают шарить разные нормалмапы, артисты создают одну текстуру - в RGB нормал мап, в A - roughness (точнее smoothness)
Потом билд процесс распиливает текстуру на две, и в roughness впекает toksvig factor.
Это эффективно убирает specular aliasing если не трогать roughness - проблема в том что если например roughness искусственно меняется...
(конкретно в Ryse есть wetness геометрия которая блендится поверх сцены в gbuffer примерно как декали но меняет только roughness)
то информация о normal variance исчезает из roughness и альясинг спекуляра возвращается.
Чтобы бороться с этим используют depth aware screen space filter который смотрит на normal variance в screen space и меняет roughness в gbuffer.
Этот фильтр не очень хорошо работает на контурах, создавая там какие-то артефакты - но артефакты которые видны всегда часто лучше чем артефакты которые появляются и исчезают в силу особенностей зрения.
Так, мы поняли как вычислять освещение, поборолись с альясингом, пора победить light attenuation.
Традиционные формулы light attenuation типа (1-distance/radius)^2 не устраивали т.к. слишком жесткий falloff, и мало контроля артистами,
плюс изменение радиуса неинтуитивно меняет освещенность в центре.
Во-первых, в качестве light attenuation берется (1+distance/bulbsize)^-2.
bulbsize это произвольный параметр, который должен эмулировать размер лампочки, но напрямую с радиусом не связан.
Эта формула традиционно уходит в полный 0 на бесконечности, поэтому у лайта все еще есть радиус - и на диапазоне [0.8*radius..radius) attenuation lerp-ится в 0.
Хотят пробовать area lights в следующем тайтле.
Во-вторых, чтобы light intensity можно было тюнить безотносительно остальных параметров, определение light intensity которое тюнят артисты - это вклад источника на расстоянии 1 метра.
Соответственно настоящий light intensity который умножается на вклад источника света пересчитывается с учетом bulbsize, чтобы при distance=1 получить заданное артистом число.

Ну и наконец, чтобы закрыть тему освещения, затронем тему GI

Сначала отметим, что добавление случайных констант типа ambient к диффузной компоненте меняет баланс между diffuse и specular.
И делает материалы слишком "плоскими".
Обычно после этого меняют материалы, но цель PBS в том чтобы отделить тюнинг материала от тюнинга освещения.
Поэтому существовавшие раньше в Крайзисе константный и hemispherical ambient убьем.
Теперь навставляем около сотни environment probes на уровень,
для них посчитаем оффлайн диффузную и спекулярную кубмапу.
Делается convolution с помощью модифицированного ATi CubemapGen, результат сохраняется в BC6H формат.
Проблема теперь традиционно в том что probe собирает информацию о лайте видимую из точки,
но probe влияет на достаточно большой регион.
Во-первых, можно использовать parallax correction (это модификация вектора по которому мы семплим environment probe в зависимости от позиции пикселя чтобы условно не иметь во всех точках плоскости один и тот же диффузный фактор из-за того что нормаль совпадает)
Про это было в какой-то из статей отсюда http://seblagarde.wordpress.com/ но там вроде автор предлагал использовать представление пробы в виде сферы - Ryse использует параллелепипед т.к. он лучше приближает комнаты в индоре.
Я в FIFA Street делал отражения через box projected environment map, тут вроде похожая штука но есть ньюансы которые я оставлю до появления пейпра на сайте Крайтека.
Вот, parallax correction помогает но не спасает - все равно diffuse и specular вклад получается не очень интересным.
Чтобы решать проблему интересности diffuse вклада, дадим возможность артистам фейкать bounce light, добавляя источники света.
Если на этих источниках есть спекуляр то появляются какие-то левые блики.
Если на них нет спекуляра то возвращаемся к проблеме "слишком много диффуза без спекуляра - материал слишком плоский и неестественный".
Поэтому эти источники света (их в Крайтеке называют ambient lights) умножают и диффуз и спекуляр который уже посчитан в сцене на вклад источника, сохраняя тем самым баланс между диффузом и спекуляром.
Диффуз вроде победили, теперь хочется лучше specular вклад от GI.
Поскольку вариантов особо нет, адаптируем Screen-Space Reflections.
Теоретически можно делать что-то типа voxel cone tracing по depth buffer, но проще посчитать mirror reflections как это обычно делают в SSR, дальше сгенерировать для результата mip chain, в которой каждый следующий mip сильнее разблурен, и потом посемплить из получишегося screen-space glossy reflection буфера с мип уровнем зависящим от roughness, как это обычно делают для environment probes.
Получится не очень физически корректно, но выглядит прикольно и не очень сильно альясит.
Так, это была базовая графика.

Еще хочется хороших персонажей

Хочется хорошую кожу и хорошие волосы.
Для кожи так вышло что базовая shading model более или менее подходит - GGX можно настроить так чтобы specular highlights были какие хочется.
Но нужно все равно моделировать sub surface scattering и translucency.
Sub-surface scattering это screen-space размытие.
Радиус размытия фиксирован в world-space.
Используют двухпроходный bilateral фильтр, см. https://dl.dropboxusercontent.com/u/55891920/papers/cbf_skin.pdf
Translucency моделируется простым (стандартным) хаком - инвертируется нормаль и считается N.L чтобы оценить насколько виден источник "насквозь" геометрии.
Есть специальная текстура которая сделана артистами и указывает насколько толстый слой в каждом пикселе, чтобы контролировать количество света которое проходит насквозь.
Тот же шейдер используется для листьев.
Arseny Kapoulkine:
Про волосы есть три проблемы как уже обсуждалось - шейдинг, альясинг, прозрачность.
Шейдинг - Kaijya-Kay, это все уже давно industry standard, волосы это меш со специальной картой касательных (вместо нормалей) для шейдинга.
Альясинг и прозрачность это беда.
Простые методы не работают - альфа тест альясит, блендинг не сортируется, alpha to coverage требует большого количества MSAA семплов.
И еще и DoF не работает с blended волосами потому что волосы не пишут в depth buffer.
Короче все плохо, и самая засада на тонких разреженных волосах типа жиденьких бороденок.
Поскольку не работает ничего то придется таки использовать блендинг!
Проблемы порядка отрисовки и блендинга будет решать арт - для большого скопления волос артисты делают отдельный непрозрачный меш на основную часть волос и края делают прозрачными.
Прозрачную геометрию надо шейдить а deferred не получается - вот тут-то мы и используем Forward+
Forward+ требует списка лайтов - я там вскользь упомянул что шейдинг не просто deferred, a tile based deferred в compute shader - вместе с шейдингом мы заодно посчитаем список лайтов как принято в forward+ для каждого тайла, и используем этот список в шейдере волос.
Ну и наконец DoF будем решать хаком.
Прозрачная геометрия не может писать в depth т.к. будут очень серьезные проблемы из-за неотсортированности (хуже чем артефакты блендинга)
Но мы в альфа-канал запишем приблизительную глубину -
не понял, вторым проходом или нет.
И потом сделаем screen-space проход который скомбинирует глубину из альфа канала с основной глубиной,
специально для DoF.
В итоге глубина с одной стороны есть в DoF.
С другой стороны не создает нам проблем при растеризации геометрии.
Глубину мы будем писать только если альфа волоса достаточно большая т.к. иначе на DoF будут страшные артефакты из-за чрезмерного occlusion.
Ох, почти все.
Очень большой доклад :(

Скорость! Некст-ген! 1080p!

Хрен там, конечно.
Разрешение пришлось уменьшить до 900p.
UI уменьшать не получается так что UI рендерится в 1080p.
Чтобы делать upscale из 900p до 1080p, используется красивый трюк.
Хочется делать что-то более четкое чем bilinear upscale.
Для этого дробная часть координаты пикселя в pixel space после апскейла трансформируется функцией smoothstep,
т.е. подгоняется ближе к краям на краях.
И после этого делается обычный билинейный tap.
Вместо того чтобы делать 4 tap и фильтровать - 1 tap дешевле, хоть и приходится для него хитро поменять текстурные координаты.
Tiled deferred shading экономит bandwidth - в попугаях это от 2-5ms на кадр до десятков ms в сложных случаях.
Чтобы он работал приходится все данные для шейдинга держать в маленьком количестве ресурсов.
Все environment probes засунуты в большой cube texture array.
Все shadow maps живут в одном 8192x8192 16-bit атласе,
с логикой про аллокацию видимых лайтов в нем per-frame.
Все текстуры лайтов-проекторов в одном texture array.
И много возни чтобы один толстый шейдер который считает сразу все умещался в GPR limit.

Ну и последняя важная performance фича - static shadow maps

Вдруг внезапно на консоли слишком много памяти!!!
Что делать?
Давайте при загрузке уровня запечем одну 8192x8192 шадовмапу на весь уровень со статическими объектами.
Оказывается, с учетом размера региона/локации в 1 квадратный км эту SM можно использовать в качестве дальнего каскада,
а значит можно не рисовать статические объекты в дальний каскад.
Дальний == 4-ый и 5-ый.
Это экономит 40-60 процентов draw calls в SM проходах (видимо DX12 еще не завезли)
Все.
Ivan Spogreev:
Спасибо за репорт )
Волосы и dof та еще проблема.
Я на fifa придумал такой хак:
Рисуем прозразные волосы с блендингом после постэффетов, прямо в уже резолвленный буффер. Т.е. получается, что они офигенно смотрятся с тем же дофом, когда в фокусе.
Проблема, когда они не в фокусе. В таком случае мы по CoC считаем дополнительную прозрачность и делаем fadeout волос. После т.нинга смотрится очень даже неплохо.
Peter Sikachev:
я правильно понимаю, что на черепе под волосами такого же цвета текстура, как сами волосы, нарисована, чтобы оно работало?
Ivan Spogreev:
нет, там 2 прохода. первый с альфа-тестом, второй уже с блендингом. Первый как бы непрозрачная часть, второй прозрачная.
Denis Sladkov:
мы также на одном из прошлых проектов антиалиазинг волос делали. помнится там еще были шикарные хаки в стиле взять деферед масочку с оффсетом по вертекс нормали к центру головы :)