Урок №42. IBL. Диффузная облученность

  Дмитрий Бушуев  | 

  Обновл. 15 Дек 2020  | 

 1718

На этом уроке мы рассмотрим, что такое IBL в OpenGL.

Освещение на основе изображения (IBL)

Освещение на основе изображения (сокр. «IBL» от англ. «Image Based Lighting») — это набор методов освещения объектов путем рассмотрения окружающей среды как одного большого источника света. Обычно этот эффект достигается при помощи определенных манипуляций с кубической картой окружения (взятой из реального мира или сгенерированной из 3D-сцены) таким образом, что у нас появляется возможность непосредственно задействовать её в наших уравнениях освещения, рассматривая каждый тексель кубической карты как излучатель света. Таким образом, создается ощущение глобального освещения сцены, из-за чего объекты сцены (да и вся сцена в целом) воспринимаются более гармонично.

Поскольку IBL-алгоритмы передают освещение некоторой (глобальной) окружающей среды, то в таком случае входные данные считаются более точной формой окружающего освещения (даже грубым приближением к глобальному освещению). Используя IBL совместно с PBR, объекты станут выглядеть значительно более физически точными.

А начнем мы внедрение IBL в нашу PBR-систему с того, что еще раз бегло взглянем на уравнение отражения:

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

   нам нужен какой-то способ определения энергетической яркости сцены для любого вектора направления ωi;

   решение интеграла должно быть быстрым и выполняться в режиме реального времени.

Итак, первое требование относительно простое. Мы уже намекали на один из способов представления облученности окружения или сцены — использование специально обработанной кубической карты окружения, где мы сможем визуализировать каждый тексель кубической карты как отдельный излучающий источник света. Проводя выборку значений из данной кубической карты при любом векторе направления ωi, мы получим величину энергетической яркости сцены для выбранного направления.

Получение энергетической яркости сцены по направлению ωi:

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

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

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

При ближайшем рассмотрении диффузного интеграла мы обнаруживаем, что диффузная составляющая Ламберта является постоянным членом (цвет c, коэффициент преломления kd и π являются постоянными для данного интеграла) и не зависит ни от одной из подынтегральных переменных. Учитывая данный факт, мы можем вынести упомянутые константы из-под знака интеграла:

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

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

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

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

Примечание: Уравнение энергетической яркости также зависит от положения точки p, которая (по принятому нами предположению) находится в центре карты облученности. Это означает, что весь рассеянный непрямой свет должен исходить из карты окружающей среды, а это в определенных случаях может приводить к разрушению иллюзии реальности (например, в варианте с помещением). Механизмы рендеринга решают эту проблему, размещая по всей сцене т.н. «зонды» отражения. Каждый «зонд» вычисляет собственную карту облученности своего окружения. Таким образом, облученность (и энергетическая яркость) в точке p — это интерполированная облученность между ближайшими «зондами». В рамках данного урока условимся считать, что выборка значения из карты окружения проводится всегда из её центра.

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

 

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

PBR и HDR


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

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

В картах окружения, которые мы использовали до этого момента, типа кубических карт (скайбоксов и т.п.), используется низкий динамический диапазон (LDR). Мы напрямую выбирали их цветовые значения (ограниченные диапазоном [0.0, 1.0]) из отдельных изображений граней и обрабатывали их как есть. Хотя это и может хорошо работать непосредственно для визуального вывода, но для случая передачи их в качестве физических входных параметров — нет.

Формат файлов HDR-изображений

Файл с расширением .hdr содержит в себе информацию обо всей кубической карте со всеми 6 гранями в виде данных типа с плавающей точкой. Это позволяет нам задавать значения цвета вне стандартного диапазона значений [0.0, 1.0], придавая свету необходимую яркость. Формат файла также использует хитрый трюк для хранения каждого значения типа с плавающей точкой, но не в виде 32-битного значения для каждого канала, а в виде 8 бит на канал, используя при этом альфа-канал цвета в качестве экспоненты (правда, с потерей точности). Работает довольно хорошо, но требует, чтобы программа-обработчик преобразовывала каждый цвет в эквивалент типа с плавающей точкой.

Существует довольно много карт окружения формата RGBE (Radiance HDR), многие из которых можно свободно скачать с сайтов типа sIBL archive, а ниже представлен пример подобной карты:

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

HDR и библиотека stb_image.h

Ручная работа с RGBE-изображениями требует некоторых познаний о внутреннем устройстве файлов данного формата. К счастью для нас, популярная библиотека stb_image.h поддерживает загрузку RGBE-изображений напрямую в массив значений типа с плавающей точкой, что идеально соответствует нашим потребностям. С добавлением stb_image.h в наш проект загрузка HDR-изображений теперь будет выглядеть следующим образом:

stb_image.h автоматически сопоставляет HDR-значения со списком значений типа с плавающей точкой: по умолчанию 32 бита на канал и 3 канала на цвет. Это всё, что нам нужно, чтобы сохранить равнопромежуточную HDR-карту окружения в 2D-текстуру типа с плавающей точкой.

Преобразование равнопромежуточной карты в кубическую карту

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

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

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

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

Мы видим результат сопоставления равнопромежуточного изображения с кубической формой, но он пока не помогает нам преобразовать исходное HDR-изображение в текстуру кубической карты. Для этого мы должны визуализировать один и тот же куб 6 раз, фокусируя взгляд на каждой отдельно взятой грани куба и записывая визуальный результат с помощью объекта фреймбуфера:

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

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

Я не буду вдаваться в подробности, поскольку детали данного кода мы уже обсуждали на предыдущих уроках, отмечу лишь то, что всё дело сводится к настройке 6 различных матриц вида (обращенного к каждой стороне куба), настройке матрицы проекции с FoV = 90 градусов для захвата всей грани и рендерингу куба 6 раз, сохраняя результаты во фреймбуфере типа с плавающей точкой:

Мы берем прикрепленный объект фреймбуфера и переключаем его текстурную цель для каждой грани кубической карты, непосредственно визуализируя сцену в одну из граней кубической карты. Как только эта процедура будет завершена (её нам нужно выполнить только один раз), кубическая карта envCubemap должна будет содержать результат преобразования исходной равнопромежуточной карты HDR-изображения.

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

Обратите внимание на трюк с xyww, который гарантирует, что значение глубины визуализированных фрагментов куба всегда будет равняться 1.0 — максимальному значению глубины. Также обратите внимание, что нам нужно изменить функцию сравнения глубины на GL_LEQUAL:

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

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

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

Нам потребовалось сделать довольно много настроек, чтобы добраться сюда, но мы успешно сумели считать HDR-карту окружения, преобразовать её из равнопромежуточного изображения в кубическую карту и визуализировать кубическую HDR-карту в виде скайбокса. Кроме того, мы создали небольшую систему для визуализации на всех 6 гранях кубической карты, которая нам снова понадобится при свертке карты окружающей среды.

  GitHub / Урок №42. IBL. Диффузная облученность — Исходный код №1

Свертка кубической карты

Как уже было сказано в начале данного урока, наша главная цель — решить интеграл для всего рассеянного непрямого освещения с учетом облученности сцены от кубической карты окружения. Мы знаем, что можем получить энергетическую яркость сцены L(p, ωi) в заданном направлении, взяв HDR-карту окружения в направлении ωi. Чтобы решить интеграл, мы должны произвести для каждого фрагмента выборку значений излучения сцены во всех возможных направлениях внутри полусферы Ω.

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

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

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

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

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

environmentMap — это сэмплер кубической HDR-карты, сконвертированной из равнопромежуточной HDR-карты окружения.

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

Интеграл уравнения отражения зависит от телесного угла , с которым довольно трудно работать. Вместо интегрирования по телесному углу , мы будем интегрировать по его эквиваленту в виде сферических координат θ и ϕ.

Мы используем азимутальный угол ϕ в интервале от 0 до для выборки значений в горизонтальной плоскости полусферы и зенитный угол θ в интервале от 0 до π для выборки в вертикальной плоскости полусферы. Это приводит нас к обновленному интегралу отражения:

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

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

Дискретное сэмплирование полусферы с учетом сферических координат интеграла приводит к следующему коду:

Мы задаем фиксированное значение переменной sampleDelta, используемой в качестве шага по поверхности полусферы; уменьшение или увеличение значения данной переменной приведет соответственно к увеличению или уменьшению точности вычислений.

Внутри обоих циклов мы берем обе сферические координаты и преобразовываем их в обычный трехмерный вектор выборки декартовой системы координат, далее преобразуем вектор из касательного в мировое пространство, а затем используем этот вектор выборки для непосредственной выборки значений из HDR-карты окружения. Мы добавляем значение каждой выборки к переменной irradiance, которую в конце делим на общее количество взятых выборок, что дает нам усредненное значение облученности. Обратите внимание, что мы масштабируем значение цвета выборки при помощи умножения на cos(theta) из-за того, что свет слабее под большими углами, и умножения на sin(theta), чтобы учесть уменьшение площади выборки при приближении к полюсу сферы.

Теперь осталось настроить код OpenGL-рендеринга, чтобы мы смогли применить операцию свертки к ранее захваченной кубической карте окружения envCubemap. Сначала мы создаем кубическую карту облученности (опять же, нам нужно сделать это только один раз перед циклом рендеринга):

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

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

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

Если результат выглядит как сильно размытая версия карты окружения, то вы успешно выполнили свертку карты окружающей среды.

PBR и освещенность непрямым облучением


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

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

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

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

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

Мы можем решить эту проблему, введя коэффициент шероховатости в уравнение Френеля-Шлика так, как это описал Себастьян Лагард:

Принимая во внимание шероховатость поверхности при расчете коэффициента Френеля, код фоновой составляющей принимает следующий вид:

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

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

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

  GitHub / Урок №42. IBL. Диффузная облученность — Исходный код №2

Дополнительные ресурсы

   Coding Labs: Physically based rendering: введение в PBR, а также как и зачем создавать карту облученности.

   The Mathematics of Shading: краткое введение от ScratchAPixel в некоторые математические аспекты, описанные на данном уроке, в частности, в полярные координаты и интегралы.


Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (4 оценок, среднее: 5,00 из 5)
Загрузка...

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

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