Shadow map что это

Урок по построению теней от объектов в реальном времени с использованием теневых карт.

Введение

В этом уроке мы рассмотрим как добавить тени от объектов на основе теневой карты (shadow map). Теневая карта использует буфер глубины (depth buffer), для того, чтобы определить, находится ли пиксель в прямой видимости источника освещения, либо он чем-то загорожен.

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

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

Сцена и источник освещения

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

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

Стоит также отметить, что для для направленного источника освещения используется не перспективная матрица проекции, а ортогональная, ее построение выглядит следующим образом:

Буфер глубины

Начнем по порядку, для начала создадим текстуру для хранения буфера глубины, делается это следующим образом:

Отдельно стоит сказать про размеры текстуры для хранения буфера глубины, часто, для использования этой текстуры как теневой карты, берут удвоенный размер окна, в которое происходит рендер. Но это совсем не обязательное требование, размер надо подбирать исходя из задачи.

Далее нам необходимо создать и настроить FBO, рендер сцены мы будем осуществлять с его использованием, создание FBO выглядит следующим образом:

После создания FBO и привязки к нему текстуры мы можем выполнить рендер всей сцены с использованием этого FBO:

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

Во-вторых, рендер внутренних граней объекта помогает избежать некоторых артефактов при использовании теневой карты. Однако данный способ подходит только для замкнутых выпуклых объектов, именно такие мы и используем в этом уроке.

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

В минимальном варианте вершинный шефдер для такой шейдерной программы выглядит следующим образом:

Т.к. нас интересует только глубина фрагментов, а не их цвет, то фактически фрагментный шейдер может быть пустым:

Использование теневой карты

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

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

Комментарий по поводу матрицы bias. Сначала мы получаем видовую матрицу источника освещения view, она переводит координаты вершины из мирового пространства координат в пространство координат источника освещения. Затем, используя проекционную матрицу, мы переводим координаты в однородные. Однако, как нам уже известно из предыдущих уроков, однородные координаты лежат в диапазоне [-1, 1], а нам необходимо получить текстурные координаты, которые лежат в диапазоне [0, 1], именно для этого используется матрица bias. Трансформация этой матрицы равносильна операции a * 0.5 + 0.5 для каждого элемента трансформируемого вектора.

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

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

Использование специального текстурного самплера sampler2DShadow позволяет изменить логику работы некоторых функций для работы с текстурой, а использование функции textureProj позволяет получить значение текселя текстуры с коррекцией текстурных координат.

При передаче в функцию textureProj в качестве текстурных координат 4х компонентного вектора для получения текселя она использует первые два компонента деленные на последний, а третий компонент используется для сравнения при тесте на затенение. Это позволит нам получить корректное значение карты глубины с учетом глубины фрагмента относительно камеры источника освещения.

При создании текстуры для хранения значений глубины мы использовали специальный параметр:

Использование этого параметра позволяет производить автоматический тест на затенение (shadow test), это происходит когда мы вызываем функцию textureProj. В случае если глубина фрагмента с координатами smccord больше чем в текстуре глубины depthTexture функция textureProj вернет 1.0, иначе она вернет 0.0. Итоговый фрагментый шейдер:

Сглаживание краев тени

Для сглаживания краев тени в этом уроке используется техника Percentage Closer Filtering (PCF). Данная техника заключается в том, что производится несколько тестов на затенение в окрестностях искомого фрагмента и для затенения используется среднее значение этих тестов, таким образом, граница тени будет более «мягкой».

Относительно описанного выше фрагментного шейдера эту технику можно использовать так:

Исходный код и программа

Источник

Learn OpenGL. Урок 5.4 – Всенаправленные карты теней

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

Всенаправленные карты теней

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

Данный же урок будет посвящён созданию динамических теней, проецирующихся во всех направлениях. Этот подход отлично подходит для работы с точечными источниками освещения, ведь они должны отбрасывать тени во всех направлениях сразу. Соответственно, данная техника называется всенаправленной картой теней.

Урок во многом опирается на материалы предыдущего урока, так что если вы еще не практиковались с обычными картами теней, стоит сделать это перед продолжением изучения этой статьи.

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

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

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

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

Создание кубической карты

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

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

Сначала создадим кубическую карту:

И каждую грань зададим как 2D текстуру, хранящую значения глубины:

Также не забудем задать подходящие параметры текстуры:

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

И снова отмечу вызовы glDrawBuffer и glReadBuffer: поскольку нам важны только значения глубины, мы явно указываем OpenGL, что можно не осуществлять запись в буфер цвета.
В конечном итоге здесь будет применяться два прохода: первым подготавливается карта теней, вторым рисуется сцена, а карта используется для создания затенения. С использованием кадрового буфера и кубической карты код выглядит примерно так:

В первом приближении процесс идет так же, как и при использовании направленных карт теней. Отличие только в том, что мы осуществляем рендер в кубическую карту глубины, а не в привычную 2D текстуру.

Прежде чем мы приступим к непосредственному рендеру сцены со направлений, относительно источника, нам необходимо подготовить подходящие матрицы трансформации.

Преобразование в систему координат источника света

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

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

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

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

В приведенном коде шесть созданных видовых матриц умножаются на матрицу проекции для задания шести уникальных матриц преобразование в пространство источника света. Параметр target в вызове glm::lookAt представляет собой направление взгляда на каждую из граней кубической карты.

Далее этот список матриц передается в шейдеры при рендере кубической карты глубин.

Шейдеры записи глубины

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

Именно геометрический шейдер будет отвечать за преобразование всех вершин в мировом пространстве в шесть отдельных пространств источника света. Таким образом, вершинный шейдер тривиален и просто выдает координаты вершины в мировом пространстве, которые уйдут в геометрический шейдер:

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

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

Представленный код должен быть довольно понятен. Шейдер получает примитив типа треугольника на входе, а в качестве результата выдает шесть треугольников (6 * 3 = 18 вершин). В функции main мы проходим в цикле по всем шести граням кубической карты, устанавливая текущий индекс как номер активной грани кубической карты соответствующей записью в переменную gl_Layer. Также мы преобразуем каждый входные вершины из мировой системы координат в соответствующее текущей грани кубической арты пространство источника света. Для этого FragPos умножается на подходящую матрицу преобразования из массива-юниформа shadowMatrices. Обратите внимание, что значение FragPos также передается во фрагментный шейдер для вычисления глубины фрагмента.

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

На вход фрагментного шейдера попадает переменная FragPos из геометрического шейдера, вектор положения источника, а также расстояние до дальней плоскости отсечения пирамиды проекции источника света. В данном коде мы просто вычисляем расстояние между фрагментом и источником, приводим к диапазону значений [0., 1.] и записываем как результат выполнения шейдера.

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

Всенаправленные карты теней

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

В примере функция renderScene выводит несколько кубиков, расположенных в большом кубическом помещении, которые будут отбрасывать тени от источника света, расположенного в центре сцены.

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

Вершинный шейдер, соответственно, теперь не должен преобразовывать вектор положения в пространство источника света, так что мы можем выкинуть переменную FragPosLightSpace:

Код модели освещения Блинна-Фонга во фрагментном шейдере остается нетронутым, в нем также оставлено умножение на коэффициент затенения в конце:

Также отмечу несколько тонких отличий: код модели освещения действительно неизменен, но теперь используется сэмплер типа samplerCubemap, а функция ShadowCalculation принимает координаты фрагмента в мировых координатах, вместо пространства источника света. Также мы используем параметр пирамиды проекции источника света far_plane в дальнейших расчетах. В конце шейдера мы вычисляем коэффициент затенения, который равен 1 при нахождении фрагмента в тени; или 0 при нахождении фрагмента вне тени. Данный коэффициент используется для воздействия на подготовленные значения диффузной и зеркальной составляющих освещения.

Самые большие изменения касаются тела функции ShadowCalculation, где выборка значения глубины теперь ведется из кубической карты, а не 2D текстуры. Разберем код этой функции по порядку.

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

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

Поскольку значение closestDepth заключено в интервале [0., 1.], то сперва следует провести обратное преобразование в интервал [0., far_plane]:

Далее, мы получаем значение глубины для текущего фрагмента относительно источника света. Для выбранного подхода это предельно просто: нужно только вычислить длину уже подготовленного вектора fragToLight:

Таким образом получим значение глубины, лежащее в том же (а, может, и в большем) интервале, что и closestDepth.

Теперь мы можем приступить к сравнению обоих значений глубины, дабы выяснить, находится ли текущий фрагмент в тени или нет. Также мы сразу включаем в сравнение значение смещения, чтобы не столкнуться с проблемой «теневой ряби», объясненной в предыдущем уроке:

Полный код ShadowCalculation:

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

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

Полный исходный код лежит здесь.

Визуализация кубической карты глубин

Если вы в чем-то похожи на меня, то, думается, с первого раза у вас не получится сделать все верно, и потому какое-то средство отладки приложения вполне бы пригодилось. Как самый очевидный вариант было бы неплохо иметь возможность проверить корректность подготовки карты глубин. Поскольку у нас теперь используется кубическая карта, а не двухмерная текстура, вопрос визуализации требует несколько более замысловатого подхода.

Простым выходом было бы взять нормализованное значение closestDepth из тела функции ShadowCalculation и вывести его как результат фрагментного шейдера:

В результате получается сцена в градациях серого, где интенсивность цвета соответствует линейному значению глубины в данной сцене:

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

Также видны области затенения на стенах помещения. Если результат визуализации схож с приведенным, то можно быть уверенным, что карты теней подготовлена верно. В противном случае где-то закралась ошибка: например, значение closestDepth было взято из интервала [0., far_plane].

Percentage-closer filtering

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

Возьмем код PCF из предыдущего урока и добавим третье измерение (выборка из кубической карты ведь требует вектора направления):

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

Теперь тени выглядят намного более достоверно и края их достаточно гладкие.

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

Однако, установив число выборок samples = 4, мы по факту потратим целых 64 выборки на каждый фрагмент, что очень много.

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

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

В приведенном коде вектор смещения умножается на величину diskRadius, представляющую радиус диска, построенного вокруг исходного вектора выборки fragToLight и в пределах которого производится дополнительные выборки.

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

Результат такого алгоритма PCF выдает мягкие тени не хуже, если не лучше, чем исходный подход:

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

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

Исходный код примера можно найти здесь.

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

Источник

Reflective Shadow Maps: Часть 1

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это

Привет, Хабр! Представляю вашему вниманию перевод статьи «Reflective Shadow Maps» автора Eric Polman.

Reflective Shadow Maps (RSM) (отражающие карты теней) ― это алгоритм, расширяющий “простые” shadow map. Алгоритм учитывает свет, рассеянный после первого попадания на поверхность (diffuse). Это означает, что кроме прямого освещения, вы получаете непрямое освещение. В данной статье я разберу алгоритм из официальной статьи, чтобы объяснить его по-человечески. Я также кратко расскажу о shadow mapping.

Shadow mapping

Shadow Mapping (SM) ― это алгоритм генерации теней. Согласно алгоритму, мы храним расстояние от источника освещения до объекта в карте глубины. На рисунке 1 показан пример карты глубины. В ней хранится расстояние (глубина) для каждого пикселя.

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это
Рисунок 1: Данное изображение демонстрирует карту глубины. Чем ближе пиксель, тем он ярче.

Таким образом, когда у вас есть карта глубины с точки зрения источника освещения, вы затем рисуете сцену с точки зрения камеры. Чтобы определить, освещен ли объект, вы проверяете расстояние от источника освещения до объекта. Если расстояние до объекта больше значения, хранимого в карте теней (глубины), объект находится в тени. Это означает, что объект не должен быть освещен. На рисунке 2 показан пример. Вы совершаете эти проверки для каждого пикселя.

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это
Рисунок 2: Расстояние от источника освещения до пикселя в тени больше, чем расстояние, хранимое в карте теней.

Reflective Shadow Mapping

Теперь, когда вы поняли основную концепцию Shadow Mapping, мы продолжим с Reflective Shadow Mapping (RSM). Данный алгоритм расширяет функциональность “простых” shadow maps. Помимо данных о глубине, вы также храните world-space (в мировой системе координат) позицию, world-space нормали и flux (световой поток). Я объясню, зачем вам нужны эти данные.

Данные

World-space позиция

В RSM world-space позицию нужно хранить для того, чтобы определить расстояние между пикселями. Это полезно для расчета затухания света. Свет затухает (становится менее концентрированным), когда проходит определенное расстояние. Расстояние между двумя точками в пространстве используется для расчета интенсивности освещения.

Нормали

Нормали (world-space) используются для расчета отражения света от поверхности. В случае RSM они также используются для определения, является ли данный пиксель источником освещения для другого пикселя. Если две нормали практически совпадают, они не будут давать друг другу много отраженного света.

Luminous Flux (световой поток)

Flux ― это световая интенсивность источника освещения. Ее единицей измерения является люмен, термин, который в настоящее время вы можете увидеть на упаковках лампочек. Алгоритм сохраняет flux для каждого пикселя, пока рисуется карта теней. Flux рассчитывается умножением интенсивности света на коэффициент отражения. Для directional light (направленный источник освещения) вы получите равномерно освещенное изображение. Для spot light вы также учитываете угол падения. Затухание и принимающий косинус (между нормалью и light вектором) не берутся в расчет, так как это учитывается, когда вы считаете непрямое освещение. В данной статье не будут рассматриваться подробности. На рисунке 3 изображены текстуры для spot light из официальной статьи.

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это
Рисунок 3: Изображены четыре карты, содержащиеся в RSM. Слева направо: карта глубины, world-space позиции, world-space нормали, flux.

Применение данных

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

Наивным подходом к расчету вклада освещения является проход по всем текселям в RSM. Вы проверяете, не попадает ли свет из текселя в RSM на пиксель, который вы рассчитываете. Это делается, используя world-space позиции и world-space нормали. Вы рассчитываете направление от world-space позиции в текселе RSM до пикселя. Затем вы сравниваете его с нормалью, используя скалярное произведение векторов. Любое положительное значение означает, что пиксель должен быть освещен с помощью flux, который храниться в RSM. Рисунок 4 демонстрирует данный алгоритм.

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это
Рисунок 4: Демонстрация вклада непрямого освещения, основываясь на world-space позициях и нормалях.

Shadow mapsRSMs) по своей природе большие (512×512=262144 пикселя), так что проверка каждого текселя далека от оптимальности. Вместо этого лучше всего сделать определенное количество сэмплов из карты. Количество сэмплов зависит от того, насколько мощное у вас железо. Недостаточное количество сэмплов может дать такие артефакты, как полосы или мерцания.

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

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

Shadow map что это. Смотреть фото Shadow map что это. Смотреть картинку Shadow map что это. Картинка про Shadow map что это. Фото Shadow map что это
Рисунок 5: Importance sampling. Больше сэмплов берется из центра и сэмплы масштабируются коэффициентом, основанным на их расстоянии от центральной точки. Заимствовано из статьи о RSM.

К сэмплу вы должны относиться как к точечному источнику освещения. Вы используйте значение flux в качестве light color и только те источники освещения, которые находятся напротив пикселя.

Заключение

В официальной статье более подробно рассказывается о других оптимизациях этого алгоритма, но я остановлюсь на этом. В разделе Screen-Space Interpolation описывается, как вы можете увеличить производительность, но я думаю для начала importance sampling будет достаточно.

Во второй части представлена реализация RSM.

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *