В конце лета Nvidia представила видеокарты серии RTX — с аппаратной поддержкой трассировки лучей. Про саму технологию я писал в этой статье (настоятельно рекомендую ознакомиться, если вы никогда не читали про трассировку лучей), и, хотя прошло уже три месяца, дальше простейших демо новой технологии да и красивых видео от создателей некоторых игр дело пока не ушло.
Чтобы лучше понимать, как это все работает я переведу некоторые вопросы к Эдду и его ответы.
Почему Quake 2, а не Quake 3?
Я выбрал Quake 2, потому что в нем есть источники света, и карты были спроектированы с учетом многократного отражения. Насколько я знаю, Quake 3 не был спроектирован таким образом и даже не имел источников света для заранее рассчитанного освещения. Плюс статическая геометрия Quake 2 по-прежнему почти полностью определялась BSP-деревом (двоичное разбиение пространства, каждый узел дерева связан с прямой, разбивающей пространство на две части. Это позволяет сортировать визуальные объекты в порядке удаления от игрока и обнаруживать столкновения), и я обнаружил, что обход BSP довольно прост в GLSL (язык для программирования шейдеров) и, кажется, работает довольно хорошо, хотя я не делал никаких сравнений с другими подходами. Quake 3 имеет гораздо больше геометрии свободной формы, таких как тесселированные поверхности (грубо говоря — текстура с рельефом), поэтому он не очень хорошо подходит для специальных оптимизаций. Я большой поклонник обеих игр, конечно :)
Как движок обновляет динамические объекты?
Вся динамическая геометрия вставляется в единую структуру, которая перестраивается с нуля на каждом кадре. Каждый узел дерева представляет собой ограничивающую рамку, выровненную по линии разбиения пространства, и имеет «указатель допуска» для пропуска дочерних элементов. Я делаю узел для каждого пиксельного треугольника и строю структуру дерева снизу вверх после специальной сортировки методом Мортона (это позволяет замостить все пространство, пусть и не идеально):
Я выбрал этот подход, потому что реализация проста как для построения, так и для обхода, иерархия узлов довольно гибкая, и сборка выполняется быстро, хотя все это реализуется на одном потоке процессора (в основном потому, что Quake 2 однопоточен, конечно).
Как свести к минимуму шум при работе с таким количеством источников света?
Расчет света немного сложнее. Я разделил источники света на две категории - регулярные и «skyportals». Skyportal — это просто светоизлучающая поверхность из исходных данных карты, которая имеет специальную текстуру, которая указывает игре, где должен быть нарисован skybox (по сути — трехмерная коробка, на которую «натянута» текстура неба и помечен горизонт). Каждый лист на дереве BSP имеет два списка ссылок на источники света. В первом списке указаны регулярные источники света, которые потенциально видны из листа. Второй список ссылается на skyportals, которые содержатся в листе. В узлах первый список используется для отслеживания теневых лучей и создания явных источников света, а второй список используется для проверки того, находится ли узел внутри skybox. Если да, то такие источники света нужно обсчитать. Таким образом, я могу выполнить некую автономную множественную выборку (MIS), потому что skyportals обычно намного больше, чем регулярных источников света. Для регулярных источников я, конечно, использую выборку по важности, но я считаю, что способ, который я использую, более приближен к действительности, потому что в нем освещение рассчитывается всегда из центра источника света.
Одним из главных замечаний в отношении источников света теперь является то, что точечные светильники, которые использовалась в исходной игре, выглядят как четыре треугольных источника света, расположенных в тетраэдре. Я хотел бы попробовать добавить новый сферический источник света, чтобы увидеть, работает ли он лучше.
Как оптимизируется трассировка лучей?
Я прослеживаю теневые лучи непосредственно к точкам на источниках света. Выборка источников света не выполняется в шейдере, так что я решаю в «руками», должен ли источник света быть выбран явно или неявно.
В какие моменты рендеринга используется растеризация?
(По сути растеризация отвечает на вопрос «какие пиксели перекрывают данный объект», а трассировка — «какой объект находится за данным пикселем». Как вы уже поняли, оба эти метода позволяют рассчитать тени и освещение, но трассировка делает это более естественным путем, так что результат получается лучше).
Я использую аппаратную растеризацию только для первичных лучей и выполняю трассировку в этом же проходе по следующим причинам:
- Прозрачные поверхности могут быть освещены и могут получать тени как и любые другие поверхности.
- Конечно, можно использовать аппаратное сглаживание.
- Quake 2 сортирует полупрозрачные поверхности в дереве BSP и рисует их во втором проходе, но он не делает этого для анимированных объектов. Поэтому по-хорошему мне нужно будет изменить этот принцип, но это достаточно сложно и, скорее всего, что-то сломается. Одна из моих основных целей заключалась в том, чтобы сохранить родной рендеринг сцены в Q2.
- Я могу исключить overdraw (перерисовку), сделав предварительный проход только для одного измерения («глубины»), при этом используются те же самые буферы GL, что и для трассировки лучей, поэтому я могу улучшить производительность не потеряв в качестве.
- Приятно, что мне не нужно управлять файлами буфера кадров.
Весь этот проект возможен только благодаря тому факту, что файлы BSP по-прежнему содержат подробные данные о поверхностях, несмотря на то, что сама игра не использует их вообще. Это, очевидно, побочный продукт, который упрощает процесс построения карты, и очень повезло, что разработчики не выкинули их из релизной версии игры!
Дизайнеры оригинальных карт иногда помещают точечные фонари перед блестящими поверхностями, чтобы придать видимость, что они светятся или излучают свет по бокам. Это выглядит совершенно странно в моей реализации трассировки, поэтому по умолчанию отключено статическое освещение.
Оружие в руках героя нарисовано с помощью «глубинного взлома» (его буквально называют RF_DEPTHHACK), в котором диапазон значений глубины объекта уменьшается, чтобы предотвратить застревания оружия в стенах. В данном же случае проблема усугубляется тем, что используется трассировка лучей, и, если подвести оружие вплотную к стене, то оно станет полностью черным, так как лучи от источников света не будут на него попадать. Я работал над этим, «виртуально» масштабируя оружие. Это одна из многих проблем, из-за которых трассировка лучей оказывается сложной для видеоигр, но я уверен, что всегда найдутся элегантные решения.
Практическая часть
Ну а тем, кто прочитал все выше, бонус — Эдд выложил свои модифицированные файлы игры и объяснил, как ее собрать. И, дабы вам не мучиться, по ссылке доступна уже готовая игра — вам всего лишь нужно запустить quake2.exe, а в самой игре в консоли (вызывается на «~») написать команду gl_pt_enable 1, чтобы включить трассировку (ну и 0, чтобы выключить). И, разумеется, я не мог удержаться и сделал такую картинку:
Также следует понимать, что реализация не идеальна: в игре много шумов, и она достаточно требовательна: так, на полноценные 30 кадров в секунду в HD-разрешении могут рассчитывать владельцы как минимум GTX 1050 или RX 560, а для 60 кадров в FHD нужна уже RTX 2080 Ti. Но запустить игру могут владельцы любых видеокарт, вышедших не более 10 лет назад, так как требуется поддержка лишь OpenGL 3.3.