Что такое Global Illumination (GI)
Понимание принципов работы GI очень важно для создания качественных и реалистичных рендеров.
Что такое GI?
Global illumination или GI – процесс симуляции непрямого освещения, при котором учитывается свет, отраженный от поверхности, а поверхность, на которую падает свет, окрашивается в цвет отражений.
Если посмотреть на изображение выше, то можно увидеть, что сфера, расположенная в правой части изображения, окрашена в зеленый цвет стены. Это явление называется непрямым освещением, поскольку как такого зеленого источника света в сцене нет, напротив, белый свет, падая на зеленую стену, окрашивает в зеленый близлежащие поверхности.
GI, для достижения такого эффекта, полностью полагается на фотоны mental ray. Фотон – это частица, ответственная за энергию света. О фотонах нужно думать как о мельчайших частицах, используемых рендером, которые обладают каким-то запасом света, большому числу фотонов мы обязаны светом, который видим на рендере. Эти фотоны испускает прямой источник света в сцене. В момент испускания фотон вступает в контакт с поверхностью, наследуя ее цвет и энергию. При отскоке он «награждает» следующую поверхность свойствами первой, и так продолжается, пока фотон не поглощается, полученный же эффект, созданный за счет непрямого освещения, можно увидеть на изображении ниже.
Так когда же использовать Global illumination? GI, естественно, не является панацеей на все случаи жизни для получения фотореалистичного рендера. Например, не стоит использовать GI в мультяшных проектах. Хотя, с другой стороны, непрямое освещение идеально подходит для архитектурной визуализации, рендера интерьеров и сцен с прямым солнечным светом, и, вообще, для всех фотореалистичных рендеров.
В целом, GI стоит использовать тогда, когда свет должен взаимоотражаться (или отбрасываться) и множество раз отражаться на большом участке в сцене. Использование GI дает возможность наблюдать непрямое освещение, природное явление, согласно которому свет на своем пути отражается ото всех препятствий до тех пор, пока полностью не поглощается. Например, щель в приоткрытой двери впустит свет в комнату, или красные стены отразят свет от источника света, окрасив деревянные полы в красный цвет. Использование GI поможет достичь большей реалистичности на рендере.
Советы по настройке GI в mental ray
При рендере с помощью mental ray необходимо включать опцию испускания фотонов как в настройках источника света, так и в настройках рендерера. В противном случае эффект непрямого освещения не будет применен.
По умолчанию все объекты в сцене как испускают, так и поглощают фотоны, в действительности же это не так. Для уменьшения времени рендера или для оптимизации GI можно настроить, какие объекты в сцене будут испускать и/или поглощать фотоны.
Довольно часто случается так, что при включенном GI, рендер может получиться пятнистым или излишне зернистым. К счастью это довольно легко пофиксить. Для этого необходимо увеличить количество отскоков или Bounces или число испускаемых объектом фотонов или Level of photons. Хотя это также увеличит время рендера.
Лучших результатов на рендере можно также достичь путем комбинации GI И FG. Final gathering или FG в сочетании с GI даст более качественный рендер без злоупотребления количеством фотонов.
GI в mental ray’s Global Illumination и в других 3D-редакторах
GI используется в большинстве 3D-редакторов, однако не все они используют подход mental ray. На самом деле, в большинстве других 3D-редакторов GI скорее ближе к FG, одной из основных особенностей mental ray, о которой мы еще поговорим в следующей статье. В любом случае, при рендере встроенным рендерером 3D-редактора проверьте, нужно ли включить GI. Необходимо помнить, что даже если рендерер и не использует фотоны, результат GI и принципы его использования одинаковы для всех рендереров.
Декомпозиция света: как работает освещение в играх
Освещение в играх напрямую влияет на наше восприятие происходящего на экране — и даже может являться основой геймплея. Как и в реальном мире, это понятие комплексное, едва ли реализуемое при помощи одного метода.
Пожалуй, ближе всего к этому смогла приблизиться трассировка лучей — простая по своей концепции, но требовательная к вычислительным ресурсам системы. И та получила свое развитие в игровой индустрии сравнительно недавно, с началом эпохи RTX. А ведь геймдев развивается гораздо дольше — и к сегодняшнему дню разработан уже не один метод симуляции распространения света в игровой сцене.
О методах реализации глобального освещения в играх (которые мы, кстати, используем и в своих проектах) и о том, что это вообще такое — далее в статье.
Для лучшего понимания, из чего вообще состоит освещение в играх, разделим его на два этапа:
Именно непрямое освещение придает изображению большую реалистичность. На скриншоте ниже, например, показана сцена из Quake 2 RTX только с прямым освещением (слева) и с освещением ото всех источников сразу (справа).
Но его же и сложнее реализовать — при том сделать это хорошо.
Как уже упоминалось, лучше всего этой цели служит, конечно же, трассировка лучей. Суть ее заключается в следующем. Давайте представим, что на 3D-сцене расположена камера, имеющая конечное разрешение. Для получения трассировки лучей один из непосредственно лучей выстреливает из пикселя этой камеры в сцену и попадает в 3D-объект на ней. Этот объект с точки зрения пикселя можно закрасить, отследив больше лучей с этой точки. Например, с этого момента луч может продолжить движение в направлении источника света с целью выяснения, будет ли пиксель находиться в тени от этого источника света или на него воздействовать. Или другой луч также может быть отправлен в сцену и иметь в ней отражения и даже переотражения в зависимости от свойств материала объекта. Чтобы получить еще более точную информацию о цвете для этого пикселя, понадобится больше лучей и больше отражений — а значит, гораздо больше времени на вычисления.
Хотя сама по себе концепция трассировки лучей проста, эта технология очень ресурсозатратна: так, даже для игры 1997 года реализовать ее удалось лишь к 2019 году. Поэтому до недавнего времени игровая графика чаще всего рисовалась на экранах с помощью гораздо более быстрой техники — растеризации.
Проблема с растеризацией в том, что она на самом деле не про свет: это просто способ отрисовки трехмерных объектов на двумерном экране. Растеризация — это процесс, при котором трехмерный объект преобразуется в двухмерное изображение на основе перспективы с целью получения правильной геометрии без затенения на двумерном изображении.
По умолчанию она вообще не имитирует освещение и не особо этим занимается. А чтобы получить достоверное освещение и затенение объектов, требуются дополнительные проходы вычислений.
Как выглядит освещение при растеризации? Представьте источник света, помещенный в трехмерное пространство. Поверхность объектов в сцене затеняется в зависимости от их нормали и направления вектора света: таким образом, отдаленная от источника света часть объекта будет темнее, находящаяся ближе к свету — светлее. В данном случае речь идет только о прямом освещении: растеризация по умолчанию не отображает никаким образом непрямой свет.
Впрочем, несмотря на это, непрямое освещение в 3D-играх существует уже очень давно — иначе все бы они были похожи на Doom 3, где градации тени как таковой практически нет.
Ниже мы как раз рассмотрим — не исчерпывающий — список техник, при помощи которых в играх эмулируют непрямое освещение.
Первый метод, о котором мы поговорим, — lightmapping. Техника эта использовалась еще в оригинальном Quake II 1997 года и используется до сих пор даже в самых современных тайтлах — но, конечно, в более продвинутом и детализированном виде.
Заключается она в том, чтобы предварительно рассчитать информацию об освещении в игровой сцене и заложить ее в текстуру, которая впоследствии будет применяться к этой сцене. Таким образом, метод карт освещения избавляет игру от необходимости расчета освещения в реальном времени, что дает огромный выигрыш в производительности.
Впрочем, как и любая другая техника, она имеет ряд недостатков:
Техника все время улучшается и обрастает модернизациями, пытающимися решить перечисленные проблемы — но многое все равно упирается в необходимость освещения динамических объектов, и тогда на помощь приходят другие методы.
Техника IBL работает следующим образом: давайте представим, что шесть камер снимают одну и ту же сцену с одной и той же точки в шести разных направлениях, тем самым образуя кубическую карту (cube map).
Впоследствии этот куб можно преобразовать в сферу, которую затем можно применить к различным — в частности, глянцевым — поверхностям для получения непрямого освещения.
Шесть граней карты позволяют получить меняющиеся в зависимости от угла камеры отражения, захватывая в том числе и происходящее за пределами экрана. Техника уже довольно старая — используется в играх с начала 2000-ых. И, как обычно, имеет свои недостатки:
Наибольшее распространение эта техника получила, пожалуй, в гоночных симуляторах, которые используют кубические карты в реальном времени, закрепленные на модели автомобиля игрока. Это позволяет учитывать зеркальные и рассеянные отражения на протяжении всей трассы и адаптироваться к изменениям в окружающей среде, хотя и требует ряда оптимизаций — например, для таких карт обычно используется упрощенная версия сцены с более низким разрешением и/или FPS.
Эволюционное продолжение предыдущего метода, световые пробы предназначены для частичного решения проблем с видеопамятью и интерактивностью окружения. Упрощенно говоря, световые пробы (light probes) представляют собой зонд меньшего размера и с меньшим числом сторон, чем кубические карты — например, с четырьмя, а не шестью сторонами. То есть, идейно это та же кубическая карта, но более низкого разрешения и всего с несколькими битами цветовой информации.
Такие зонды можно размещать на уровне с разной плотностью либо равномерно в виде сеток, использовать для статического освещения и обновлять динамически.
Основное преимущество световых проб заключается в их размере. Из-за того, что они крайне малы, места в видеопамяти они занимают немного — а значит, их можно обновлять экономнее, чем в случае кубических карт. А также расставить куда большее количество таких зондов на уровне, благодаря чему освещение может оказаться даже более детальным, чем в случае с кубическими картами.
Однако и здесь есть свои минусы и компромиссы:
Как можно заметить, оговорка про использование дополнительных техник освещения звучала неоднократно: все эти методы вовсе не обязательно являются конкурирующими и взаимоисключающими — скорее наоборот, все чаще в современных играх используются их комбинации и различные улучшения. Например, мы уже раньше писали статью о том, как реализовали кастомные тени в своих проектах: предварительно рассчитанное освещение для статики — при помощи теневых карт, для динамической геометрии — зонды освещения.
Все это доказывает, что, сколько бы еще различных методов не было изобретено, рендеринг действительно правдоподобного непрямого освещения в реальном времени — сложная задача, требующая комплексных решений.
2D магия в деталях. Часть третья. Глобальное освещение
Глобальное освещение, динамический свет и декали (да, есть такое слово 🙂 ) в действии.
Я очень люблю смотреть на белые предметы без текстуры. Недавно в художественном магазине я долго рассматривал гипсовые фигуры, которые художники используют в качестве модельных объектов. Очень приятно видеть все эти плавные переходы света и мягкие тени. Позже, когда я вернулся домой и открыл Unity3D, пришло понимание, что свет в моём проекте по-прежнему скучный и нереалистичный.
С этого момента началась история глобального освещения, которую я сегодня расскажу.
Предыдущие статьи
Оглавление
Как делать процедурно генерируемые эффекты
Самый первый комментарий к начальной статье этого цикла звучал так: «Магия! И прямые руки.» Не уверен в полной прямоте моих рук (в конце предыдущей статьи — визуальные баги, которые это подтверждают), но никакой магии тут нет. Поделюсь секретом процедурных эффектов:
Минимум треть работы уже сделана, как только вам в голову пришла идея сделать процедурно генерируемый контент. Это может быть что угодно: пятна на крыльях бабочек или атмосфера планеты, деревья и кусты и т.д. Иногда, особенно со светом, сразу понятно, как происходит «генерация» в реальном мире. Чаще всего алгоритм сводится к: «пустить бесконечно много лучей в бесконечное количество направлений и получить реалистичную картинку».
И это вторая треть — написать подобный алгоритм (с учетом того, что бесконечность хорошо аппроксимируется тысячей). Он получается простой, как «hello world», но медленный. Руки сразу тянутся что-нибудь оптимизировать, но, поверьте, не стоит. Лучше запустить его в редакторе и пойти пить чай. А после чая понять, что придуманный метод не даст красивой картинки и всё переделать. Если планируется единожды предрассчитать какую-то картинку в редакторе, и потом использовать её в билде — на этом можно остановится.
И, наконец, последняя треть — придумать алгоритм, который даст визуально близкий результат, но будет работать быстрее. Обычно тут пригождается знание всяких интересных контейнеров, алгоритмов, деревьев и т.д. За один из таких алгоритмов — большое спасибо Dionis_mgn, который когда-то рассказал, как сделать классные двумерные тени.

Планета из предыдущего проекта.
Например, небо для планет в одном из проектов предрассчитывалось так: для каждого пикселя неба выпускались по 20-30 лучей до разных частей Солнца, считалось, сколько лучей пересекается с самой планетой, какую часть пути луч прошел в атмосфере (для подобия рассеивания Рэлея). С хорошим качеством расчеты для одной планеты длились около 30-40 секунд и давали на выходе разнообразные атмосферы в зависимости от удаленности Солнца, «состава» и плотности атмосферы. А еще этому алгоритму удавались неплохие закаты.
Вся звёздная система.
Что такое глобальное освещение?
Необходимость что-то делать с освещением я заметил, когда добавил в демку смену дня и ночи. Лучи света от солнца и луны красиво освещали стены замков, но вот внутри помещений творилось что-то странное: как только рассветные лучи касались верхушек башен, в самых глубоких казематах становилось светло, простите за каламбур, как днём. Конечно, причина не в источнике света «defaultSun»: при смене дня и ночи менялись цвет и яркость неба. Вот они и влияли на каждый пиксель, в не зависимости того, был ли это пиксель травинки на старой крыше или камня в мрачной пещере.
Давайте определимся, какую картинку мы вообще хотим получить. «На свету светло, в темноте — темно» — звучит неплохо для отправной точки. Как в реальном мире: в шкафу темно, в коридоре светлее, в комнате еще светлее, а на крыше совсем ярко. Переформулируем: элементы фона, персонажи и прочие объекты должны получать столько света, сколько фотонов смогло добраться до них от небесной сферы (в нашем 2D случае — небесной окружности). Понятно, что лучше направлять наши «фотоны» не с неба, как в реальном мире, а наоборот, из освещаемой точки в небо: в противном случае нам понадобится слишком много бросков, да и то, многие уйдут «в молоко».
Ещё одно из условий: рассчитываем глобальное освещение только для статических объектов: стен, земли. Так мы сможем запускать его при загрузке и пользоваться результатами весь уровень (без влияния на fps).
Кусочек сцены. На самом деле, расчеты идут для всей сцены целиком.
Прямое освещение
Сказано — сделано. Создаём текстуру размером со всё игровое поле. Пробегаемся по каждому пикселю и смотрим, как много прямых лучей можно протянуть от этой точки до «неба». Лучи будем бросать с равными углами по всей окружности, а «небом» считаем ближайшую точку за пределами карты (вполне хватит расстояния диагонали описывающего карту прямоугольника).
Итого, алгоритм прямого освещения:
Напрашивается оптимизация: бросать лучи только в верхнюю полуплоскость. И только для непрямого освещения работать со всей плоскостью целиком. К сожалению, оптимизации (об этом ниже) не позволяют использовать разное количество лучей для прямого и непрямого освещения.
Демонстрация освещения одного пикселя.
Чтобы ускорить процесс, будем работать не с текстурой, а с одномерным массивом яркостей. Да и не обязательно обрабатывать каждый пиксель: введем коэффициент scale, при scale=4 будем работать с каждым четвёртым пикселем. Размер текстуры и скорость работы вырастет в scale^2 раз. Кроме того, нам не нужно обрабатывать «твёрдые» пиксели стен, но они нам понадобятся в дальнейшем. Заведём для них отдельный массив с булевыми значениями «твёрдости».
При 25и лучах получаем такую текстуру.
Помните, в прошлой части был раздел про Region tree? С его помощью бросать raycast’ы через всю карту оказывается достаточно быстрым делом.
Я не использую цикл по всей текстуре, так как больше половины пикселей принадлежат стенам. Вместо этого итерация производится по массиву индексов «нетвёрдых пикселей».
Непрямое освещение
Прямых лучей явно недостаточно: слишком темно будет в комнатах замка, да и резкие границы хорошо видны. Вспоминаем умные слова, вроде raytracing’а, и понимаем, как много времени займёт применение этих умных слов. С другой стороны — ведь любой переотраженный луч приходит откуда-то с карты, а всё прямое освещение мы только что построили! Расширяем массив и храним там целую структуру:
Переделаем алгоритм прямого освещения, добавляя данные о коллизиях:
* Нормали нужны по простой причине: точка пересечения, возвращаемая raycast’ом — в стене. Нам нужно отступить в сторону, чтобы получить координаты ближайшего к стене пикселя.
* Метод raycast’а для region tree я найти не смог, поэтому делюсь своими наработками:
1. Берем узел (изначально — корневой) и находим пересечение с ним с помощью алгоритма Лианга-Барски;
2. Из четверых узлов потомков находим тот, которому принадлежит ближайшая точка пересечения;
2.1. Если узел — твёрдый лист, возвращаем координаты точки пересечения и нормали;
2.2. Если узел не является листом, спускаемся ниже, начиная с шага 1;
3. Находим дальнюю точку пересечения прямой с узлом потомком (тот же алгоритм Лианга-Барски). Находим еще одного потомка, которому принадлежит эта точка (т.е., если мы сначала попали в верхний левый узел, а прямая — вертикальна, то теперь это будет нижний левый угол). Продолжаем с шага 2.1.
Если проще, мы проверяем пересечения отрезка с квадратами, начиная от самого большого и до самого мелкого, причем сортируем их по близости к началу луча, до тех пор, пока не наткнёмся на твердый узел.
Теперь у нас достаточно информации, чтобы рассчитать любое количество отражений: если
луч ушел в небо, получаем прямое освещение, в противном случае — непрямое из точки пересечения.
Так получается алгоритм непрямого освещения:
Демонстрация непрямого освещения. Собираем из коллизий уже рассчитанное прямое освещение.
Самое главное, что теперь вместо операции raycast’а по region tree нам достаточно взять значение яркости в массиве: так мы получим одно отражение. Конечно, этот метод подходит только для pixelart’a: не нужно учитывать нормали или заботиться о возникающих артефактах.
Посмотрите, какие результаты даёт этот алгоритм:
Готовый результат для фоновых стен.
Довольно шумная картинка получается. На самом деле, после применения такого освещения к реальным текстурированным объектам шумы почти не заметны. К тому же высокочастотный шум исчезнет при использовании scale > 1.
Освещение стен
Вот только стены в текущей текстуре чёрные. «Конечно», возразит зануда, далёкий от геймдева, пиксельарта и чувства прекрасного — «Ведь это не стены, а срез трехмерных стен в двумерном пространстве. А внутри стен, как известно, темно.». Поблагодарим зануду и продолжим эксперименты. Попробуем вообще не затемнять стены:
Стены без применения освещения.
В первом случае результат красиво смотрелся только под землёй, во втором — на поверхности. Нужно адаптивно менять яркость стен в зависимости от окружения.
А теперь история одного фейла. После многочасовых размышлений и прогулок в мне голову пришел исключительной красоты алгоритм, включающий в себя добавление новых методов в region tree, поиск ближайшей точки, не принадлежащей стене и прочее, прочее. Я реализовал этот код, потратив на него все выходные, оптимизировал, как только мог. Этот монстр вычислялся около минуты и всё равно выглядел не идеально. В какой-то момент я решил скрыть огрехи алгоритма, немного размыв по Гауссу результат. Это было идеально! Я ещё некоторое время вносил правки и небольшие изменения. Пока не наткнулся на ошибку в условии, из которой следовало, что результаты моего чудесного алгоритма отправлялись прямиком в garbage collector, а на финальные пиксели влияло только размытие. А вот картинка оставалась такой же красивой.
Зато теперь это самый быстрый этап всего глобального освещения. 🙂
Переведём наши массивы в текстуру, где в одном канале будет яркость пикселя, а другом — принадлежность стене. Размоем пиксели стены на GPU с помощью простого шейдера (простое среднее арифметическое с соседями) в цикле.
Размытые стены (scale = 2).
Вот такое недоразумение получится, если применить освещение.
В первой статье цикла я рассказывал про основы пиксельарта. Дополню еще одной важной аксиомой: никаких градиентов в духе photoshop’а! Это превращает аккуратную картинку в мыло и пластилин. На фоне градиенты не так бросаются в глаза, как на стенах. Пройдемся по текстуре с еще одним шейдером: для каждого пикселя стены с помощью простого округления (с коэффициентом из параметров шейдера) получим несколько градаций яркости. Конечно, полученные переходы далеки от идеала — рука художника не двигала пиксели, убирая кривые лесенки, но нам подойдет.
Световая маска с низкой дискретизацией (scale = 2).
Результат применения маски.
Результат применения маски при использовании реальных текстур.
Обратите внимание, как хорошо скрываются шумы и недочеты освещения, когда мы применяем его к реальным текстурам. Если бы глобальное освещение было динамическим, человеческий мозг, отлично распознающий движение, сразу же нашел бы косяки.
Итак, у нас есть глобальное освещение!
Плюсы этого алгоритма:
Декали
Хотя основная тема статьи раскрыта, это ещё не повод заканчивать стучать по клавишам. Скорее всего, это последняя статья про освещение. А значит, есть смысл рассказать про некоторые новые фишки, которые были добавлены после рефакторинга игры.
Декали («decal» — «переводная картинка»), это отличный способ сделать игру более живой, не сильно жертвуя производительностью. Идея проста: на определенную поверхность (стена, пол и т.д) накладывается прямоугольник с текстурой, как настоящая переводная картинка. Это может быть след от пули, какой-нибудь мусор, надпись, что угодно.
Но мы будем использовать декали немного иначе: в качестве источников света произвольной формы. Раз уж мы генерируем текстуру с освещением, мы можем добавлять в неё объекты произвольной формы. И эти объекты сразу же начнут светиться! Так можно легко реализовать эффекты люминесценции, теплового излучения.
Но есть два важных момента:
По сути, алгоритм простой:
Разделим все декали (например, с помощью тегов Unity3D) на декали переднего и заднего планов:
На примере будет понятнее:
Находим старый спрайт травы.
Позиционируем «траву» так, чтобы она закрывала кончики стен.
Рендерим спрайт только в текстуру освещения.
Добавляем свечение на стены.
Добавляем свечение на фон.
И получаем интересную радиоактивную плесень.
А еще можно делать раскаленные стены, уникальные светящиеся предметы и многое другое.
Стена светится от счастья.
Доработки динамического освещения
Это очень короткий раздел и весь от первого лица. Наконец-то добрались руки сделать рендеринг только видимых источников света. Все источники, которые не попадают в камеру, не отрисовываются и не кушают драгоценный fps.
Более того, оказалось, что источники света составляют отличную иерархию:
1. SkyLight. Фоновое освещение, где важны яркость и цвет;
2. SunLight. Точечный источник света без затухания. Важны яркость, цвет и позиция;
3. PointLight. Точечный источник света c затуханием. Важны яркость, цвет, позиция и радиус;
4. FlashLight. Фонарик с коническим лучом. Важны яркость, цвет, позиция, радиус, угол поворота и ширина луча.
А еще появилась возможность создавать любые другие источники света, наследуясь от базовых.
Вышеописанные источники света.
Заключение
Теперь в нашем проекте есть реалистичный свет, эффекты светимости и обновленные динамические источники света. Сравните с изображением из первой статьи, не так уж мало различий, правда?
Изображение из начала этой статьи.
Изображение из первой части цикла.
И самое интересное: теперь когда готово освещение и произведен рефакторинг алгоритмов и структуры проекта, пришло время написать про воду!
Спасибо за чтение и комментарии к прошлым частям и до следующей статьи!









