Что: трассировка каскадов воксельных конусов
Для The Tomorrow Children мы реализовали инновационную систему освещения, основанную на трассировке воксельных конусов. Вместо использования традиционных систем прямого или отложенного освещения мы создали систему, освещавшую всё в мире трассировкой конусов через воксели.
Таким способом обрабатывается и прямое, и отражённое освещение. Он позволяет нам рассчитывать на PlayStation 4 три отражения глобального освещения в полудинамических сценах. Мы трассируем конусы в 16 фиксированных направлениях через шесть каскадов 3D-текстур и выполняем поглощение света с помощью направленного затенения в экранном пространстве (Screen Space Directional Occlusion) и сферическими окклюдерами динамических объектов для получения конечного результата. Движок также поддерживает модель сферического освещения на основе гармоник, что позволяет рассчитывать освещение частиц и реализовать спецэффекты, например аппроксимированное подповерхностное рассеяние (approximating subsurface scattering) и преломляющие материалы.
Кто: Джеймс Макларен, директор по технологии движков в Q-Games
Я начал программировать в возрасте 10 лет, когда мне подарили на день рождения старый добрый ZX, и с тех пор я ни разу не пожалел об этом. Когда был подростком, я работал на 8086 PC и Commodore Amiga, а затем поступил в Манчестерский университет на специальность «компьютерные науки».
После университета я несколько лет работал в Virtek/GSI над лётными симуляторами для ПК (F16 Aggressor и Wings of Destiny), потом занялся гоночными играми (F1 World Grand Prix 1 и 2 для Dreamcast) в манчестерском офисе японской компании Video System. Благодаря ей я получил возможность несколько раз слетать в Киото, влюбился в этот город, а потом переехал в него, начав работать на Q-Games в начале 2002 года.
В Q-Games я работал над Star Fox Command для DS, базовый движок использовался в серии игр PixelJunk. Также мне повезло напрямую поработать с Sony над визуализаторами графики и музыки для ОС PS3. В 2008 году я переехал на три года в Канаду, чтобы работать в Slant Six games над Resident Evil Raccoon City, а в 2012 году вернулся в Q-Games, чтобы поучаствовать в разработке The Tomorrow Children.
Зачем: полностью динамический мир
На этапе раннего концепта The Tomorrow Children мы уже знали, что хотим полностью динамический мир, модифицируемый и изменяемый игроками. Наши художники начали рендерить концепты изображений с помощью рендерера графического процессора Octane. Они освещали объекты с помощью очень мягкого градиентного освещения неба, и были в восторге от красивых световых отражений. Поэтому мы задались вопросом, как нам удастся достичь нужного им эффекта глобального освещения в реальном времени без предварительного запекания.
Скриншот раннего концепта, отрендеренного с помощью Octane. Он демонстрирует стиль, к которому стремились художники.
Сначала мы пробовали множество разных подходов с VPL и безумными попытками трассировки лучей в реальном времени. Но мы почти сразу поняли, что самым интересным направлением был подход, предложенный Сирилом Крэссином (Cyril Crassin) в его работе 2011 года о трассировке воксельных конусов с помощью разреженных воксельных восьмеричных деревьев. Я был особенно привязан к этой технике, потому что мне нравилась возможность фильтрации геометрии сцены. Нас также вдохновило то, что другие разработчики, например Epic, тоже исследовали эту технику. Epic использовала её в демо Unreal Elemental (к сожалению, потом они отказались от этой техники).
Что же такое «трассировка конусов»?
Техника трассировки конусов в чём-то схожа с трассировкой лучей. В обоих техниках мы стремимся получить множество сэмплов падающего излучения в точке, испуская примитивы и пересекая их со сценой.
Если вычислить достаточно сэмплов с хорошим распределением, мы можем соединить их вместе, получив оценку падающего освещения в нужной точке. Эти данные мы можем затем пропустить через ДФОС, представляющую свойства материала в точке, и вычислить выходное освещение в направлении угла зрения. Очевидно, что я опустил в описании подробности, особенно что касается трассировки лучей, но для сравнения они не слишком важны.
При пересечении луча мы получаем точку, а в случае с конусом — область или объём, в зависимости от того, как к этому подходить. Важно то, что это уже не бесконечно малая точка, и из-за этого меняются свойства оценки освещения. Во-первых, поскольку нам нужно оценить сцену в области, наша сцена должна быть фильтруемой. Кроме того, из-за фильтрации мы получаем не точное значение, а среднее, и точность оценки снижается. С другой стороны, так как мы оцениваем среднее, то шум, обычно получаемый при трассировке лучей, практически отсутствует.
Именно это свойство трассировки конусов привлекло моё внимание, когда я увидел презентацию Сирила на Siggraph. Внезапно мы поняли, что у нас есть техника приемлемой оценки освещённости точки с небольшим количеством сэмплов. А так как геометрия сцены фильтруется, то шум отсутствует, и мы сможем быстро выполнять вычисления.
Очевидно, что проблема в следующем: как получать сэмплы конуса? Фиолетовую область поверхности на рисунке выше, определяющую пересечения, рассчитать не так просто. Поэтому вместо этого мы берём несколько объёмных сэмплов вдоль конуса. Каждый сэмпл возвращает оценку количества света, отражённого по направлению к вершине конуса, а также оценку поглощения света в этом направлении. Выясняется, что мы можем сочетать эти сэмплы с простыми правилами, используемыми при трассировке лучей через объём.
Исходная работа Сирила требовала вокселизации в разреженное воксельное восьмеричное дерево, поэтому в начале 2012 года, перед возвращением в Q-Games я начал экспериментировать только с ними в DX11 на ПК. Вот скриншоты очень ранней (и очень простой) первой версии. (На первом показана вокселизация простого объекта, на втором — освещение, добавленное в воксели из света со схемой затенения):
В проекте было много взлётов и падений, поэтому в результате я отложил на какое-то время свои исследования, чтобы помочь в реализации первого прототипа. Затем я адаптировал его на оборудование для разработки PS4, но там были очень серьёзные проблемы с частотой кадров. Мои первоначальные тесты работали плохо, поэтому я решил, что нужен новый подход. Я всегда немного сомневался, имеют ли смысл воксельные восьмеричные деревья в графическом процессоре, и попробовал сделать всё намного проще.
На сцене появляется каскад вокселей:
Благодаря каскаду вокселей мы можем просто хранить воксели в 3D-текстуре вместо восьмеричных деревьев, и создавать разные уровни хранящихся вокселей (в нашем случае шесть), каждый со своим разрешением, но с удваивающимися каждый раз размерами объёма, покрываемого им. Это значит, что вблизи у нас будут объёмные данные с хорошим разрешением, но при этом сохранится грубое представление объектов, находящихся далеко. Такой тип размещения данных должен быть знаком тем, кто реализовывал clipmapping или объёмы с распространением света (light propagation volumes).
Для каждого вокселя нам нужно хранить базовую информацию о свойствах его материала, такую как альбедо, нормали, излучение и т.д., во всех шести его направлениях (+x,-x,+y,-y,+z,-z). Затем мы можем вставить в объём освещение для любого вокселя, находящегося на поверхности, и несколько раз оттрассировать конусы, чтобы получить отражённое освещение и снова сохранить эту информацию для каждого из шести направлений в другой текстуре каскада вокселей.
Упомянутые шесть направлений важны, потому что тогда воксели становятся анизотропными, и без этого свет, который, например, отражается от одной стороны тонкой стенки, может просочиться на другую сторону, что нас не устроит.
После того, как мы рассчитали освещение в воксельной текстуре, нужно вывести его на экран. Можно сделать это трассировкой конусов из положений пикселей в пространстве мира с последующей комбинацией результата со свойствами материалов, которые мы тоже рендерим в более традиционный двухмерный G-буфер.
Ещё одно тонкое отличие от исходного метода Сирила заключается в способе рендеринга: всё освещение, даже прямое, получается из трассировки конусов. Это значит, что нам не надо делать ничего сложного для вставки освещения, мы просто трассируем конусы для получения прямого освещения, а если они затем выходят за пределы границ каскада, мы аккумулируем освещение неба, уменьшая его всем полученным частичным поглощением. В то же время динамические точечные источники света обрабатываются с помощью другого каскада вокселей, в котором мы используем шейдер геометрии для заполнения значениями излучения. Затем можно сэмплировать данные из этого каскада и аккумулировать их при трассировке конусов.
Вот пара скриншотов техники, реализованной в раннем прототипе.
Во-первых, вот наша сцена без отражённого освещения от каскадов, только с освещением неба:
А теперь с отражённым освещением:
Даже каскады вместо восьмеричных деревьев при трассировке конусов довольно медленные. Наши первые тесты выглядели отлично, но мы совсем не вписывались в технические требования, потому что одна только трассировка конусов занимала порядка 30 миллисекунд. Поэтому чтобы использовать технику в реальном времени, нам нужно было внести в неё дополнительные изменения.
Первое сделанное нами большое изменение: выбор фиксированного количества направлений трассировки конусов. В исходной технике Сирила Крэссина количество сэмплируемых конусов на писель зависело от нормали поверхности. Это значит, что для каждого пикселя мы потенциально должны пройти по каскадам текстур безотносительно к ближайшим пикселям. В этой технике есть и скрытые затраты: для определения освещённости в направлении пикселя/вокселя области обзора при каждом сэмплировании из освещённого каскада вокселей нужно было выполнять сэмплирование для трёх из шести граней анизотропного вокселя и брать среднее взвешенное на основании направления обхода. При трассировке конусов они на своём пути касаются нескольких вокселей, и это каждый раз приводит к излишнему сэмплированию и трате пропускной способности и ресурсов АЛУ.
Если вместо этого мы зафиксируем направления трассировки (мы проделали это с 16 направлениями в сфере), то сможем использовать хорошо согласованный проход по каскаду вокселей и очень быстро создавать каскады вокселей излучения, дающие информацию о том, как наши анизотропные воксели будут выглядеть с каждого из 16 направлений. Затем мы используем их для ускорения трассировки конусов, поэтому вместо трёх сэмплов текстур нам нужен только один.
Мы сделали ещё одно важное наблюдение: в процессе трассировки конусов и прохождения по уровням каскада воксели, к которым мы получаем доступ для смежных пикселей/вокселей, становятся всё более схожими, чем дальше мы удаляемся от вершины конуса. Это наблюдение представляет собой хорошо известный простой эффект: «параллакс». При перемещении головы объекты, находящиеся ближе, для глаз кажутся движущимися, а отдалённые объекты движутся гораздо более медленно. Учтя это, я решил попробовать рассчитывать ещё одно множество текстурных каскадов, по одному для каждого из 16 направлений, чтобы мы могли периодически заполнять заднюю половину конуса от центра каждого вокселя предварительно вычисленными результатами трассировки конусов.
Эти данные показывают, что освещение дальних объектов едва изменяется с точки зрения пользователя. Затем можно скомбинировать эти данные, которые возможно просто сэмплировать из нашей текстуры с помощью полной конусной трассировки «ближней» части конуса. Правильно настроив расстояние, после которого мы переходим к данным «дальнего» конуса (для наших целей достаточно 1-2 метров), мы получим значительное повышение скорости с незначительным снижением качества.
Даже эти оптимизации не дали нам нужного результата, поэтому пришлось вносить изменения в объёмы вычисляемых данных, как во временнóм, так и в пространственном отношении.
Оказывается, зрительная система человека «прощает» задержки при обновлении отражённого освежения. При задержке обновления отражённого освещения вплоть до 0,5-1 секунды всё равно создаются хорошо воспринимаемые человеком изображения, а «неправильность» расчётов трудно распознать. Осознав это, я понял, что можно не выполнять обновление каждого уровня каскада системы в каждом кадре. Поэтому вместо обновления всех 6 уровней каскада мы выбираем только один и обновляем его каждый кадр. При этом уровни мелкой детализации обновляются чаще, чем уровни грубой. Поэтому первый каскад обновляется каждый второй кадр, следующий — каждый четвёртый кадр, следующий — каждый восьмой, и так далее. В таком случае результаты расчёта отражённого освещения близко к игроку будут обновляться с необходимой скоростью, а грубые результаты вдали будут обновляться гораздо реже, экономя нам кучу времени.
Очевидно, что для вычисления результата освещения нашего финального экранного пространства можно значительно повысить скорость, слегка потеряв в качестве с помощью расчётов результатов трассировки экранного пространства в меньшем разрешении. Затем увеличением разрешения с учётом геометрии и дополнительным исправлением ошибок затенения мы можем получить окончательные результаты освещения экранного пространства. Мы выяснили, что для The Tomorrow Children можно получить хорошие результаты, трассируя с 1/16 разрешения экрана (1/4 по высоте и ширине).
Результаты получаются хорошими, потому что мы применяем этот принцип только к информации об освещённости. Свойства материалов и нормали в сцене по-прежнему вычисляются в двухмерных G-буферах с полным разрешением, после чего комбинируются с данными освещённости на пиксельном уровне. Очень просто понять, почему это работает: нужно представить данные об освещённости, полученные в 16 направлениях вокруг сферы, как маленькую карту среды. Если мы перемещаемся на малые расстояния (от пикселя к пикселю) то при условии отсутствия резких прерываний по глубине эта карта будет меняться очень незначительно. Поэтому её можно просто восстановить из текстур с низким разрешением.
Скомбинировав всю эту информацию, нам удалось добиться обновления воксельных каскадов каждый кадр за время порядка 3 мс. Время расчёта попиксельного диффузного освещения тоже снизилось примерно до 3 мс, что позволяет уже использовать эту технологию в играх с частотой 30 Гц.
Взяв за основу эту систему, мы смогли добавить к ней некоторые другие эффекты. Мы разработали разновидность направленного затенения в экранном пространстве (Screen Space Directional Occlusion), вычисляющую конус видимости для каждого пикселя, и использует его для модификации освещённости, полученной с 16 направлений трассировки конусов. Также мы добавили что-то похожее на тени наших персонажей (потому что персонажи движутся очень быстро и они слишком детализированы для вокселизации) и использовали деревья сфер, полученные из объёмов коллизий персонажей, для вычисления направленного поглощения.
Для частиц мы создали упрощённую четырёхкомпонентную версию текстур каскадов освещения, которые имеют гораздо меньшее качество, но всё равно выглядят достаточно хорошо для освещения частиц. Также нам удалось использовать эту текстуру для создания резких отражений с помощью ускоренной трассировки лучей по полям расстояний, и написать эффекты, похожие на простое подповерхностное рассеяние (sub surface scattering). Подробное описание всех этих процессов займёт много времени, поэтому я рекомендую желающим узнать подробности прочитать мою презентацию с GDC 2015, где эта тема рассмотрена более подробно.
Результат
Как вы видите, в результате у нас получилась система, радикально отличающаяся от традиционных движков. На неё ушло много труда, но мне кажется, что результаты говорят сами за себя. Выбрав своё собственное направление, нам удалось получить в игре совершенно уникальный стиль. Мы создали что-то новое, чего бы никогда не достигли, используя стандартные техники или чужие технологии.
Автор: PatientZero