Привет, Хабр! Представляю вашему вниманию перевод статьи «Reflective Shadow Maps» автора Eric Polman.
Reflective Shadow Maps (RSM) (отражающие карты теней) ? это алгоритм, расширяющий “простые” shadow map. Алгоритм учитывает свет, рассеянный после первого попадания на поверхность (diffuse). Это означает, что кроме прямого освещения, вы получаете непрямое освещение. В данной статье я разберу алгоритм из официальной статьи, чтобы объяснить его по-человечески. Я также кратко расскажу о shadow mapping.
Shadow mapping
Shadow Mapping (SM) ? это алгоритм генерации теней. Согласно алгоритму, мы храним расстояние от источника освещения до объекта в карте глубины. На рисунке 1 показан пример карты глубины. В ней хранится расстояние (глубина) для каждого пикселя.
Рисунок 1: Данное изображение демонстрирует карту глубины. Чем ближе пиксель, тем он ярче.
Таким образом, когда у вас есть карта глубины с точки зрения источника освещения, вы затем рисуете сцену с точки зрения камеры. Чтобы определить, освещен ли объект, вы проверяете расстояние от источника освещения до объекта. Если расстояние до объекта больше значения, хранимого в карте теней (глубины), объект находится в тени. Это означает, что объект не должен быть освещен. На рисунке 2 показан пример. Вы совершаете эти проверки для каждого пикселя.
Рисунок 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 из официальной статьи.
Рисунок 3: Изображены четыре карты, содержащиеся в RSM. Слева направо: карта глубины, world-space позиции, world-space нормали, flux.
Применение данных
Теперь, когда данные сгенерированы (теоретически), пришло время применить их к финальному изображению. Когда вы отрисовываете финальное изображение, вы рассчитываете влияние каждого источника освещения на каждый пиксель. Помимо простого освещения пикселей, используя источники освещения, теперь вы также используете Reflective Shadow Map.
Наивным подходом к расчету вклада освещения является проход по всем текселям в RSM. Вы проверяете, не попадает ли свет из текселя в RSM на пиксель, который вы рассчитываете. Это делается, используя world-space позиции и world-space нормали. Вы рассчитываете направление от world-space позиции в текселе RSM до пикселя. Затем вы сравниваете его с нормалью, используя скалярное произведение векторов. Любое положительное значение означает, что пиксель должен быть освещен с помощью flux, который храниться в RSM. Рисунок 4 демонстрирует данный алгоритм.
Рисунок 4: Демонстрация вклада непрямого освещения, основываясь на world-space позициях и нормалях.
Shadow maps (и RSMs) по своей природе большие (512x512=262144 пикселя), так что проверка каждого текселя далека от оптимальности. Вместо этого лучше всего сделать определенное количество сэмплов из карты. Количество сэмплов зависит от того, насколько мощное у вас железо. Недостаточное количество сэмплов может дать такие артефакты, как полосы или мерцания.
Тексели, которые в наибольшей степени будут влиять на результат освещения, находятся ближе всего к рассчитываемому пикселю. Метод сэмплинга, который собирает большинство сэмплов рядом с координатами пикселя, даст лучшие результаты. Данный метод называется “importance sampling” (сэмплинг по важности). В официальной статье описывается, что плотность сэмплинга уменьшается с квадратом расстояния от пикселя, который мы рассчитываем.
Также нам необходимо масштабировать интенсивность сэмплов с учетом коэффициента, зависящего от расстояния. Это связано с тем, что тексели, расположенные дальше, хоть и сэмплируются реже, но в действительности оказывают влияние тем же количеством flux. Поэтому у дальних пикселей нужно увеличить интенсивность, чтобы сгладить неравенство, сохраняя при этом небольшое количество сэмплов. На рисунке 5 показано, как это работает.
Рисунок 5: Importance sampling. Больше сэмплов берется из центра и сэмплы масштабируются коэффициентом, основанным на их расстоянии от центральной точки. Заимствовано из статьи о RSM.
К сэмплу вы должны относиться как к точечному источнику освещения. Вы используйте значение flux в качестве light color и только те источники освещения, которые находятся напротив пикселя.
Заключение
В официальной статье более подробно рассказывается о других оптимизациях этого алгоритма, но я остановлюсь на этом. В разделе Screen-Space Interpolation описывается, как вы можете увеличить производительность, но я думаю для начала importance sampling будет достаточно.
Во второй части представлена реализация RSM.
Комментарии (9)
Mingun
17.02.2019 12:08Как-то не очень понятно все же, как работает карта глубины с точки зрения источника освещения? С его точки зрения сцена же совершенно другая будет! Как она поможет основному рендеру? Например, камера смотрит в вертикальную стену, а источник освещения за стеной и чуть выше нее, так что в кадр попадает и что-то освещает. Получается, он освещает заднюю часть стены и с его точки зрения передней части вообще не видно, но именно эту часть показывает камера. И чем тут поможет глубина?
migom Автор
17.02.2019 12:58В карту глубины попадут значения, которые видны из источника освещения. Это будет задняя часть стены.
Во время расчета прямого освещения это означает, что передняя часть не видна, и передняя часть стены будет черной. (Если не светит глобально ambient)
А вот во время расчет непрямого освещения получиться:
Когда оно будет рассчитываться для пикселя из передней части стены, то сэмплируя соседние тексели (виртуальные источники освещения rsm), получиться, что они все на задней части стены. А это означает, что они не освещают наш пиксель (рассчитываться с помощью скалярного произведения) и соответственно дадут нулевой вкляд в непрямое освещение, так как задняя часть стены не видит переднюю.
Однако наш первоначальный источник освещения может находиться чуть выше этой стены и свет попадёт на пол перед передней частью стены. И вот виртуальные источники освещения от пола вполне могут осветить стену.
Конечно для всех этих вычислений используется карта позиций, нормалей и flux из RSM.э, а карта глубины нет. Но от нее не избавиться, ибо без нее не выйдет сформировать другие карты RSM.
Надеюсь, я смог ответить)
Bhudh
18.02.2019 18:44Reflective Shadow Maps (RSM) (отражающие карты теней)
А не «карты отражённых/отражающих/отражающихся теней»?
«Отражающие карты» — это как-то… абсурдистски звучит.
Переводчику надо немного уметь в attributive adjectives.
OldFisher
Не совсем ясно, зачем хранить worldspace координаты. Они восстанавливаются из экранных умножением на инвертированную матрицу (View * Projection). Или экономия на расчётах (одно умножение на матрицу 4x4 для каждого сэмпла) оказывается достаточно существенной, чтобы потратить на это память?
migom Автор
Резонный вопрос)
И да, это сделано в целях экономии в вычислениях, так как каждый тексель становиться новым виртуальным источник освещения. Если для каждого восстанавливать позицию из глубины, то FPS сильно упадет.
DimPal
Ну не факт что упадет. Что быстрее: обращение в память (Multiple render target) или регистровое 4x4 умножение?
migom Автор
Согласен, не факт) При небольшом количестве сэмплов может не упадет и останется на том же уровне.
Однако вопрос не в умножении vs обращение в память. При расчете непрямого освещения в любом случае придется обращаться к 3 текстурам: позиция, нормаль, flux или глубина, нормаль, flux. При том сэмплинг идёт по случайным координатам, так что с кэшем все грустно и так и так.