Skyforge: технологии рендеринга

0aa050406432462d8bad41a05e225f44.jpgВсем привет! Меня зовут Сергей Макеев, и я технический директор в проекте Skyforge в команде Allods Team, игровой студии Mail.Ru Group. Мне хотелось бы рассказать про технологии рендеринга, которые мы используем для создания графики в Skyforge. Расскажу немного о задачах, которые стояли перед нами при разработке Skyforge с точки зрения программиста. У нас свой собственный движок. Разрабатывать свою технологию дорого и сложно, но дело в том, что на момент запуска игры (три года назад) не было технологии, которая могла бы удовлетворить всем нашим запросам. И нам пришлось самим создать движок с нуля.Основной арт-стиль игры — это смесь фентези с Sci-Fi. Чтобы реализовать задумки арт-директора и художников, нам нужно было создать очень сильную, мощную систему материалов. Игрок может видеть проявления магии, технологии и природные явления, и система материалов нужна, чтобы правдоподобно нарисовать все это на экране. Еще одним «столпом» нашего графического стиля является то, что мы создаем стилизованную реальность. То есть объекты узнаваемы и выглядят реалистично, но это не фотореализм из жизни. Хорошим примером, на мой взгляд, является фильм «Аватар». Реальность, но реальность художественно приукрашенная, реальность и в то же время сказка. Следующий столп графического стиля — освещение и материалы — выглядят максимально естественно. А «естественно» с точки зрения программиста — это значит «физично».

Важный момент для программиста: нам нужны огромные открытые пространства. В игре существуют как небольшие локации-инстансы, так и огромные территории, на которых находится много игроков. На этом видео вы можете посмотреть, чего мы добились на текущий момент. Все, что показано в видео, отрендерено с помощью игры в максимальных настройках

[embedded content]

6fe279ddc7c74b20ad265ae660a942af.jpg

837efda573d14ef8bae9a47e0abcc664.jpg

ff78321b586244c89fe309056220b9f4.jpg

6821aa2a715b4790967d47af7f2a18dd.jpg

50dddb1d74d343d2a06084cc8075f9c5.jpg

5b74d4c1adff4669a9fca1e94640a05d.jpg

Далее я расскажу о том, как мы добились такой картинки.

Зачем нужен шейдинг, основанный на физике? Это дает нам более реалистичную и сведенную картинку. 3D-модели, сделанные разными художниками, выглядят целостно в разных типах освещения. Картинка меньше разваливается на части, и художникам по освещению не нужно сводить все варианты освещения со всеми 3D-моделями. Материалы можно настраивать отдельно от света. То есть художники по материалам и художники по свету могут работать параллельно. Корректно настроенные физичные материалы при любом освещении выглядят как положено, не становятся неожиданно черными и пересвеченными, как это часто бывало раньше, при нефизичных материалах. Параметров материала стало меньше, и все они имеют физический смысл. Художникам легче ориентироваться в этих параметрах. Конечно, сначала им приходится переучиваться, но потом работа становится гораздо более предсказуемой. Соблюдение закона сохранения энергии в системе материалов означает, что художникам по свету проще работать. Нельзя, настраивая свет, «рассыпать» картинку на части. Кстати, физическая корректность не обязывает нас к фотореализму. Например, все последние мультфильмы, созданные студиями Pixar и Disney, сделаны с помощью физического рендера. Но при этом там нет фотореализма, а присутствует вполне узнаваемая стилизация. Прежде чем программировать что-либо, надо сначала понять физику процесса. Что происходит, когда свет отражается от поверхности? В реальной жизни, в отличие от компьютерной графики, поверхности не гладкие, а на самом деле состоят из множества маленьких неровностей. Эти неровности настолько мелкие, что глазом их не видно. Однако они достаточно большие, чтобы влиять на свет, отраженный от поверхности. Здесь на картинке я назвал это микроповерхностью.5df38f8e2a414670abe9ecdd8e42040d.jpg

Вот пример из реальной жизни. На картинке лист бумаги А4 под электронным микроскопом. Видно, что лист бумаги на самом деле состоит из множества переплетенных древесных волокон, но глаз их не различает.

6de217cbef004d77a18e6a52ed13967e.jpg

Для масштаба я вывел длины волн для разного спектра освещения в нанометрах и выделил на скане бумаги квадрат со стороной в 100000 нанометров. Очевидно, что хотя мы и не видим эти волокна, они оказывают влияние на отражение света.

c69408f7683b4b529dcb496ecdf6cd2b.jpg

Рассчитывать освещение с таким уровнем детализации не под силу даже офлайн-рендерам для кино. Это огромное количество вычислений.

Итак, микрогеометрия поверхности. Часть света проникает внутрь и переизлучается после случайных отражений внутри материала или поглощается — превращается в тепло. Часть падающего света отражается от поверхности. Существует разница в том, как разные материалы отражают свет. Две группы, которые ведут себя по-разному — это диэлектрики и проводники (металлы). Внутрь металлов свет не проникает, а практически весь отражается от поверхности. От диэлектриков свет же в основном переизлучается, а отражается малое количество света — около 5%.

cd2fdb9fed254a158682db1eeafc5cd1.jpg

Теория — это хорошо, но мы создаем компьютерную игру и оперируем не лучами света, а пикселями на экране. При этом каждый пиксель — это большое количество фотонов света. И весь этот свет может быть поглощен, переизлучен или отражен. Это нам и предстоит рассчитать при разработке шейдеров.

С физикой процесса в общих чертах определились, перейдем к математической модели. Основная функция, которая позволит нам определить, какой процент света был отражен, а какой переизлучен или поглощен, называется BRDF (Bidirectional Reflection Distribution Function) или по-русски ДФОС (двухлучевая функция отражательной способности). Цель данной функции — рассчитать количество энергии, излучаемой в сторону наблюдателя при заданном входящем излучении. В теории это многомерная функция, которая может зависеть от большого количества параметров 3D, 4D, 6D.Мы же на практике будет рассматривать функцию от двух параметров F (l, v), где l — направление от точки поверхности на источник света, а v — направление взгляда.

Для модели переизлученного света мы делаем несколько допущений: можем пренебречь точкой входа и выхода луча, т.к. это очень незначительная в нашем случае величина; считаем, что все переизлученные лучи равномерно распределены внутри полусферы. Поведение фотона внутри материала очень сложное, и для текущего развития компьютерной графики это нормальное приближение, к тому же оно в достаточной мере соответствует реальным физическим замерам.8c6ef72f1598466e835ab8b9da25a9f8.jpg

Получаем следующую функцию для расчета переизлученного (рассеянного) света.

704ffa3faeb8475cbd5a7169a97ad3b4.jpg

l — направление на источник света, v — направление взгляда, оно никак не используется в данной упрощенной модели, т.к. все энергия переизлучается равномерно по полусфере.

albedo (rgb) — определяет, сколько энергии поглощает поверхность, а сколько переизлучает. Так, к примеру, поверхность с абсолютно черным albedo всю энергию поглощает (преобразует в тепло). На самом деле это известный всем графическим программистам dot (n, l), за исключением деления на PI. Деление на PI нужно для соблюдения закона сохранения энергии. Т.к. свет рассеивается по полусфере, то при n, равном l, мы отразим падающий свет без изменения интенсивности во все стороны по полусфере, что физически невозможно. Но обычно интенсивность источника света, переданная в шейдер, уже учитывает деление на PI, поэтому в шейдере остается только dot (n, l).

Мы знаем, что скалярное произведение векторов (dot) — это косинус между этими векторами. Возникает вопрос: как угол падения света влияет на количество переизлученного света? Ответ прост: площадь проекции зависит от угла падения луча на поверхность и равна косинусу угла падения. Соответственно, чем острее угол падения, тем меньше энергии попадает на поверхность.

c93fff42a81e42d5abc6e3398d004f31.jpgСвет падает под «тупым» углом

b9e96f10b5aa4b469697cc82cb1c4472.jpgСвет падает под острым углом, площадь проекции стала больше

Перейдем к модели отраженного света. Всем известно, что угол падения равен углу отражения, но на приведенной картинке отраженных лучей стало много. Так происходит из-за микронеровностей поверхности. Благодаря этим неровностям каждый отдельный фотон отражается немного в разную сторону. Если поверхность достаточно гладкая, то большинство лучей отражаются в одну сторону и мы видим четкое отражение предметов, вроде как в зеркале.Если поверхность более шероховатая, то гораздо большее количество лучей отражаются в разные стороны, и тогда мы видим очень матовое отражение. Для сильно неровной поверхности отраженный свет может быть распределен равномерно внутри полусферы и выглядеть для внешнего наблюдателя как рассеянный свет.

Для моделирования эффекта отражения света от поверхности с учетом микрогеометрии поверхности была разработана Microfacet Theory. Это математическая модель, которая представляет поверхность в виде множества микрограней, ориентированных в разные стороны.При этом каждая из микрограней является идеальным зеркалом и отражает свет по тому же простому закону: угол падения равен углу отражения.Для того чтобы рассчитать освещение в точке, нам нужно рассчитать сумму вклада отраженного света каждой из микрограней. Т.е. нам нужен интеграл. Микрограней в точке очень много, и мы не можем просто взять и численно проинтегрировать. Будем находить решение интеграла в аналитическом виде (приближенно). Вот как в общем виде выглядит функция для расчета отраженного света.

55f5a1f11bb84e1599715aafeca48b5b.jpg

Это функция отраженного света Кука-Торренса.l — направление светаv — направление взгляда наблюдателяn — нормаль к поверхностиh — вектор между векторами l и v (half vector)

D (h) — функция распределения микро гранейF (v, h) — функция ФренеляG (l, v, h) — функция затенения микро граней

Все параметры данной функции достаточно простые и имеют физический смысл. Но какой физический смысл имеет half-vector? Half-vector нужен, чтобы отфильтровать те микрограни, которые вносят свой вклад в отражение света для наблюдателя. Если нормаль микрограни равна half-vector, значит данная микрогрань вносит вклад в освещение при направлении взгляда V.

734cd07b685a4656be2c8ed8ba5c8a04.jpg

Расcмотрим подробнее члены нашей BRDF.

В качестве функции распределения отраженного от микрограней света мы используем степень косинуса, с нормированием для соблюдения закона сохранения энергии. Для начала мы берем коэффициент шероховатости поверхности, который лежит в диапазоне 0…1, и раcчитываем из него степень альфа, которая лежит в диапазоне 0.25 — 65536. Далее мы берем скалярное произведение N и H векторов и возводим их в степень альфа. И чтобы получившийся результат не нарушал закон сохранения энергии, мы применяем константу нормализации NDF.Без нормализации от поверхности будет отражаться больше энергии, чем пришло. Таким образом мы задаем объем, в котором происходит отражение света и распределение энергии в этом объеме. И этот объем зависит от того, насколько гладкая или шероховатая поверхность. Теперь рассмотрим следующий член BRDF.

Сила отражения света, зависит от угла падения. Это поведение описывается формулами Френеля. Формулы Френеля определяют амплитуду и интенсивность отраженной и преломленной электромагнитной волны при прохождении через границу двух сред. Этот эффект очень заметен на воде, если смотреть на воду под острым углом, то вода отражает большинство света и мы видим отражение. Если же смотреть на воду сверху-вниз, то отражения мы практически не видим, а видим то, что находится на дне.f76c9cc459df4b3bbf0830f6a00902ed.jpg

Вот так, к примеру, выглядит график отраженного света в зависимости от угла падения для различных материалов. Табличные данные я взял с сайта http://refractiveindex.info/

1482d7c69c8f4005aae5ca2c27770f50.jpg

На графике видно, что пластик практически весь свет рассеивает, но не отражает, пока угол падения не станет 60–70 градусов. После чего количество отраженного света резко увеличивается. Для большинства диэлектриков график будет схожий.

Гораздо интереснее ситуация с металлами. Металлы отражают очень много света под любым углом, но количество отраженного света разное для разной длины волны. Пунктирные графики показывают отражение света для меди. Длины волн соответствуют красному, зеленому и синему цветам. Как видно, красного цвета медь отражает гораздо больше, именно поэтому металлы окрашивают отраженный свет в цвет своей поверхности. А диэлектрики отражают свет, не окрашивая его.

В качестве функции для расчета Френеля мы используем аппроксимацию Шлика, т.к. оригинальные уравнения Френеля слишком тяжелые для вычислений в реальном времени.

85f4f0981a8b407db5146ad7d839df3a.jpg

Как видно, в функции Шлика участвуют H и V вектора, с помощью которых определяют угол падения, и F0, с помощью которого практически задается тип материала. Коэфицент F0 возможно рассчитать, зная Index of refraction (IOR) материала, который мы моделируем. Фактически его можно найти в справочниках в интернете. Т.к. мы знаем, что IOR воздуха 1, то, зная табличный IOR материала, мы рассчитываем F0 по формуле

99c4e44968f14b1ab5a112330156a9f6.jpg

Физический смысл F0 следующий — это процент отраженного под прямым углом света. Итак: френель задает, сколько света будет поглощено, а сколько отражено в зависимости от угла падения.

F0 используется в аппроксимации Шлика и задает, сколько света отражается под прямым углом к поверхности. Обычное значение F0 для диэлектриков 2% — 5%, т.е. диэлектрики мало отражают и много рассеивают.

Металлы практически весь свет отражают, при этом для разных длин волн это количество разное. Отражения на металлах окрашиваются в цвет поверхности. Теперь рассмотрим следующий член BRDF.

На самом деле, не каждая микрогрань, нормаль которой соответствует half-vector’у, вносит свой вклад в освещение. Луч, отраженный микрогранью, может не достичь наблюдателя.a8f73fc2f728488fa981f92b7f8d229b.jpg

Отраженному лучу может помешать микрогеометрия поверхности. Видно, что часть отраженных лучей не достигнет наблюдателя. Таким образом, не все микрограни будут участвовать в отражении света. Мы используем простейшую функцию видимости. Фактически мы считаем, что все микрограни отражают свет. Это допустимо с нашей функцией распределения отраженного света.

Если представить, что микроповерхность задана картой высот (выпуклая), то, если смотреть на поверхность под прямым углом и освещать поверхность тоже под прямым углом, но нет таких микрограней, которые не участвуют в освещении, т.к. поверхность выпуклая. При этом наша функция стремится к нулю при больших углах, что также согласуется с представлением микрогеометрии в виде карты высот. Т.к. чем острее угол, тем меньше микрограней отражают свет.

Так выглядит наша финальная функция для расчета отраженного света:

7f7ac4ef104d4ca2b47a1380d4d97182.jpg

Это соответствует нормированной модели Блина-Фонга с представлением микроповерхности в виде карты высот. Вот несколько картинок-примеров, как параметры материала, шероховатость и IOR влияют на внешний вид материала.

6388d985cca04e99b757c307c8ca8d55.jpg

Я уже неоднократно упоминал про сохранение энергии. Сохранение энергии означает простую вещь: сумма отраженного и рассеянного света должна быть меньше или равна единице. Из этого следует важное для художников свойство: яркость и форма/площадь блика отраженного света связаны. Такой связи раньше не было в нефизичном рендере, т.к. там возможно нарушать закон сохранения энергии. Для примера — серия изображений. Источник света на всех картинках одинаковый, я буду изменять только шероховатость поверхности.35d50d61115a494982ba1d238b67cb99.jpg

1a2f282ad03743c7834764603fcaa32d.jpg

13f3f41748584f1ab37406fba0350ee8.jpg

ea496948a40149179c897598ebe2cc42.jpg

3aac404570ad45b8ab32d2dc97c6a4f2.jpg

Видно, что чем на меньшей площади распределен блик, тем он ярче.

Наша модель опирается на физику, и нам важно использовать честное затухание энергии источника света. Мы знаем, что интенсивность света обратно пропорциональна квадрату расстояния до источника света. Это поведение можно описать следующей формулой: 3917776708ce48d9b1b992e450bcb685.jpg

Эта функция хорошо подходит для описания, как затухает энергия, но имеет несколько недостатков:

мы хотим учитывать затухание энергии не от точки в пространстве, а от объема, т.к в реальной жизни не существует источников света, не имеющих собственного размера; интенсивность излучения, описанная этой функцией, стремится к нулю, но никогда его не достигает. Мы же хотим, чтобы на определенном расстоянии интенсивность стала равна нулю. Это простая оптимизация вычислительных ресурсов, нам не нужно рассчитывать то, что не влияет на финальный результат. Для новой функции затухания света нам нужны будут два новых параметра: Rinner — размер источника света.Router — дистанция, на которой нам больше не важен вклад источника в освещение.Наша функция затухания:

0ef872d21aa64d1db5b1ed7a0aad00eb.jpg

Обладает следующими свойствами:

Константна внутри Rinner. На дистанции Router равна 0. Соответствует квадратичному закону затухания. Достаточно дешева для расчета в шейдере. //функция расчета затухания для источника света float GetAttenuation (float distance, float lightInnerR, float invLightOuterR) { float d = max (distance, lightInnerR); return saturate (1.0 — pow (d * invLightOuterR, 4.0)) / (d * d + 1.0); } Вот сравнение двух графиков. Квадратичное затухание и наше. Видно, что большую часть наша функция совпадает с квадратичной.3f621277bcaa47e0994c32ba5423306e.jpg

С физикой и математикой процессов разобрались. Теперь определимся, на что же влияют художники. Какие именно параметры они настраивают? Наши художники задают параметры материалов через текстуры. Вот какие текстуры они создают: для диэлектриков для металлов Base color задает значение albedo задает векторную часть F0 Normal нормаль к поверхности (макро уровень) нормаль к поверхности (макро уровень) Roughness (Gloss) шероховатость поверхности (микро уровень) шероховатость поверхности (микро уровень) Fresnel F0 тип материала (IOR) для диэлектриков практически всегда константа для металлов скалярная часть F0 Metal всегда 0 всегда 1 Вот пример свойств поверхности такого механического пегаса: 33705a02564f439d9c2b2471e52a31aa.jpg

21866fd3bc614a2e80c12c9332169a22.jpgalbedo

9e0b6151657840a2af69e71dfc5a16db.jpgnormal

b555203a1ade4eefb5d0c06c36213641.jpggloss

f176abe358134917b9d4d88053aaeeae.jpgFO (IOR)

518fa1027fd44dd7ad7677097723ff72.jpgmetal

Для упрощения работы наши художники составили достаточно большую библиотеку материалов. Вот некоторые примеры.

47935ae611794bb98ec26301d1dd2c86.jpg

e0963551e5ed44f8be3287ea74397d67.jpg

d6d3ebb8a5714d5b9f84a69e2deb4bd0.jpg

430d4bc32b3f43779eeddb093a39056c.jpg

ca19db1764414db1b45c9d095b71717c.jpg

cd5c7576614c461eaa7450675d985e3d.jpg

При разработке Skyforge мы используем модель отложенного шейдинга. Это широко распространенный в настоящее время метод. Метод называется отложенным, т.к. во время основного прохода рендеринга происходит только заполнение буфера, содержащего параметры, необходимые для расчета финального шейдинга. Такой буфер параметров называют G-Buffer, сокращение от geometry buffer.Кратко опишу плюсы и минусы отложенного шейдинга:

Плюсы:

Шейдеры геометрии и освещения разделены. Легко сделать большое количество источников света. Отсутствие комбинаторного взрыва в шейдерах. Минусы: Bandwidth. Нужна большая пропускная способность памяти, т.к. буфер параметров поверхности достаточно толстый. Источники света с тенями по-прежнему дорогие. Источники света без теней имеют достаточно ограниченное применение. Сложность встраивания различных BRDF. Тяжело сделать разную модель освещения для разных поверхностей. Например, BRDF для волос или анизотропного металла. Прозрачность. Практически не поддерживается, прозрачность нужно рисовать после того как основная картинка нарисована и освещена. Основной минус технологии — нужна большая пропускная способность памяти. Мы стараемся максимально упаковать параметры поверхности, необходимые для освещения. В результате мы пришли к формату, который укладывается в 128 бит на пиксель — 96, если не учитывать информацию о глубине.Как мы храним свойства поверхности (128 бит на пиксель).

Skyforge G-Bufferf34422ef554f49a3979f85c4cd9ac35c.jpg

При использовании Deferred shading мы часто сталкиваемся с необходимостью реконструкции позиции пикселя в различных пространствах. Например, world space, view space, shadow space и т.д. В нашем GBuffer’e же мы храним только глубину пикселя, используя аппаратный depth buffer. Нам нужно уметь решать задачу: как быстро получить позицию пикселя в пространстве, имея только аппаратную глубину, которая к тому же имеет гиперболическое распределение, а не линейное. Наш алгоритм делает такое преобразование в два этапа. После того как мы заполнили Gbuffer, мы преобразуем depth buffer с гиперболическим распределением в линейный. Для этого мы используем полноэкранный шаг, «выпрямляющий» глубину. Преобразование происходит с помощью такого шейдера: // Функция для преобразования глубины с гиперболическим распределением в линейную float ConvertHyperbolicDepthToLinear (float hyperbolicDepth) { return ((zNear / (zNear-zFar)) * zFar) / (hyperbolicDepth — (zFar / (zFar-zNear))); } Линейную глубину мы сохраняем в формате R32F и потом на всех этапах рендера используем только линейную глубину. Второй этап — это реконструкция позиции, используя линейную глубину. Для быстрой реконструкции позиции мы используем следующее свойство подобных треугольников. Отношение периметров и длин (либо биссектрис, либо медиан, либо высот, либо серединных перпендикуляров) равно коэффициенту подобия, т.е. в подобных треугольниках соответствующие линии (высоты, медианы, биссектрисы и т. п.) пропорциональны. Рассмотрим два треугольника: треугольник (P1, P2, P3) и треугольник (P1, P4, P5).fd60f09e3a2848eabe7db427e2d4e496.jpg

Треугольник (P1, P2, P3) подобен треугольнику (P1, P4, P5).

c3200fef00c34de4853cda484ea48baa.jpg

4f27c9d0243e45b4b56d720cc3be4d36.jpg

Таким образом, мы, зная дистанцию (P1-P4) (наша линейная глубина) и гипотенузу (P1, P3), пользуясь подобием треугольников, можем рассчитать дистанцию пикселя до камеры (P1, P5). А зная дистанцию до камеры, позицию камеры и направление взгляда, мы с легкостью можем рассчитать позицию в пространстве камеры. Сама же камера в свою очередь может быть задана в любом пространстве: world space, view space, shadow space и т.д., что дает нам реконструированную позицию в любом нужном нам пространстве.

Итак, еще раз алгоритм по шагам:

Преобразование гиперболической глубины в линейную. В вершинном шейдере рассчитываем треугольник (P1, P2, P3). Передаем отрезок (P1, P3) в пиксельный шейдер, через интерполятор. Получаем интерполированный вектор RayDir (P1, P3). Считываем линейную глубину в данной точке. Position = CameraPosition + RayDir * LinearDepth. Алгоритм очень быстрый: один интерполятор, одна ALU инструкция MAD и одно чтение глубины. Можно реконструировать позицию в любом удобном однородном пространстве. HLSL код для реконструкции в конце статьи. При разработке Skyforge перед нами стояла задача: уметь рисовать локации с очень большой дальностью видимости, порядка 40 км. Вот несколько картинок, иллюстрирующих дистанцию отрисовки.448007b01cf24700a6b48af2284b7133.jpg

4d07d9f5eb26415095248b072a22a087.jpg

5d128f574ca74c9ea57d1fdef95c5678.jpg

ece0a1575f4d4ea2b8ef18fc1b02108d.jpg

Для того чтобы избежать Z-fighting при больших значениях Far Plane, мы используем технику reversed depth buffer. Смысл этой техники очень прост: при расчете матрицы проекции необходимо поменять местами Far Plane и Near Plane и инвертировать функцию сравнения глубины на больше или равно (D3DCMP_GREATEREQUAL). Этот трюк работает, только если менять местами FarPlane и NearPlane в матрице проекции. Трюк не работает, если менять местами параметры вьюпорта или разворачивать глубину в шейдере.

Сейчас я объясню, почему так происходит, и где мы выигрываем в точности расчетов. Чтобы понять, где теряется точность, разберемся, как же работает матрица проекции.

a1bbc89e2e50402683ce7892e957d42c.jpg

Итак, стандартная матрица проекции. Нас интересует та часть матрицы, которая выделена серым. Z и W компоненты позиции. Как рассчитывается глубина?

// умножение позиции на матрицу проекции float4 postProjectivePosition = mul (float4(pos, 1.0), mtxProjection);

// перспективное деление float depth = postProjectivePosition.z / postProjectivePosition.w; После умножения позиции на матрицу проекции мы получаем позицию в пост-проективном пространстве. После перспективного деления на W мы получаем позицию в clip space, это пространство задано единичным кубом. Таким образом, получается следующее преобразование.d18a020f87854686b80a21e4967d2003.jpg

Для примера рассмотрим Znear и Zfar, дистанция между которыми очень большая, порядка 50 км.

Znear = 0.5Zfar = 50000.0

Получим следующие две матрицы проекции:

1706acfb63d94fb6ad8e3069c24a10b1.jpgСтандартная матрица проекции

949c36440b8b426b89da9cd0d11307cb.jpgРазвернутая матрица проекции

Глубина после умножения на стандартную матрицу проекции будет равна следующему:

758f7afe7c1a46958869f92f9684d6cf.jpg

Соответственно, после умножения на развернутую матрицу проекции:

ccf0eba27ce548af9cb94bad6be69d86.jpg

Как мы видим, в случае стандартной матрицы проекции при расчете глубины будет происходить сложение чисел очень разного порядка — десятки тысяч и 0.5. Для сложения чисел разного порядка FPU должен сначала привести их экспоненты к единому значению (бjльшей экспоненте), после чего сложить и нормировать полученную сложением мантису. Фактически на больших значениях z это просто добавляет белый шум в младшие биты мантисы. В случае использования развернутой матрицы проекции такое поведение происходит только вблизи камеры, где из-за гиперболического распределения глубины и так избыточная точность. Вот пример, что получится при значении z = 20 км:

b142ef642e5f4c50827216bfd351dd90.jpg

И для развернутой матрицы проекции:

c2c3bbd0237147ec93b62eeaaa9e907d.jpg

Итого:

Стандартный 24-битный буфер глубины D24 легко покрывает дальность в 50 км без Z-fighting. Reverse depth подходит для любого движка, я бы рекомендовал его использовать во всех проектах. Лучше всего закладывать поддержку с начала разработки, т.к. существует много мест, которые, возможно, придется переделать: извлечение плоскостей фрустума из матрицы проекции, bias у теней и т.п. Если на целевой платформе доступен float depth буфер, то лучше его использовать. Это еще увеличит точность, т.к. значения будут хранится с большей точностью. На этом все, спасибо за внимание! Литература

Naty Hoffman. Crafting Physically Motivated Shading Models for Game Development

Yoshiharu Gotanda. Physically Based Shading Models in Film and Game Production — Practical implementation at tri-Ace

Emil Persson. Creating Vast Game Worlds

Nickolay Kasyan, Nicolas Schulz, Tiago Sousa. Secrets of CryENGINE 3 Graphics Technology

Eric Heitz. Understanding the Masking-Shadowing Function

Brian Karis. Real Shading in Unreal Engine 4

HLSL код реконструкции (вершинный шейдер)

// Часть матрицы проекции float tanHalfVerticalFov; // invProjection.11; float tanHalfHorizontalFov; // invProjection.00; // Базис камеры в пространстве реконструкции float3 camBasisUp; float3 camBasisSide; float3 camBasisFront; // postProjectiveSpacePosition в homogeneous projection space float3 CreateRay (float4 postProjectiveSpacePosition) { float3 leftRight = camBasisSide * -postProjectiveSpacePosition.x * tanHalfHorizontalFov; float3 upDown = camBasisUp * postProjectiveSpacePosition.y * tanHalfVerticalFov; float3 forward = camBasisFront; return (forward + leftRight + upDown); } void VertexShader (float4 inPos, out float4 outPos: POSITION, out float3 rayDir: TEXCOORD0) { outPos = inPos; rayDir = CreateRay (inPos); } HLSL код реконструкции (пиксельный шейдер) // Позиция камеры в пространстве реконструкции float3 camPosition; float4 PixelShader (float3 rayDir: TEXCOORD0) : COLOR0 { … float linearDepth = tex2D (linearDepthSampler, uv).r; float3 position = camPosition + rayDir * linearDepth; … } // Функция для преобразования глубины с гиперболическим распределением в линейную float ConvertHyperbolicDepthToLinear (float hyperbolicDepth) { return ((zNear / (zNear-zFar)) * zFar) / (hyperbolicDepth — (zFar / (zFar-zNear))); } Слайды оригинального докладаwww.slideshare.net/makeevsergey/skyforge-rendering-techkri14finalv21

© Habrahabr.ru