Урок №15. Множественные источники света в OpenGL

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

  Обновл. 6 Сен 2021  | 

 8336

 ǀ   3 

На предыдущих уроках мы узнали много нового об устройстве освещения в OpenGL. Рассмотрели такие технологии, как:

   освещение по Фонгу;

   материалы объектов;

   карты освещения;

   различные типы источников света.

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

Использование нескольких источников света

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

Функции в GLSL очень похожи на функции в языке С++. У нас есть имя функции, возвращаемый тип, и нам нужно в верхней части файла кода объявить прототип функции, если она не была объявлена до функции main(). Мы создадим несколько различных функций для каждого из типов света: направленного, точечного и прожектора.

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

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

Направленный свет


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

Сначала нам нужно определить минимальный набор необходимых переменных источника направленного света. Местом их хранения будет структура под названием DirLight, которую мы собираемся использовать в качестве uniform-переменной. Переменные данной структуры должны быть вам уже знакомы по предыдущему уроку:

Затем мы передаем uniform-переменную dirLight в функцию со следующим прототипом:

Примечание: Точно так же, как и в языках C/С++, когда мы хотим вызвать функцию (в данном случае, внутри функции main()), строка с определением функции должна предшествовать строке её вызова. Но в нашем случае мы бы предпочли определить функции после основной функции main(). Поэтому, чтобы сохранить выполнение вышеописанного требования, мы объявляем прототипы функций где-то выше основной функции main().

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

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

Точечный свет

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

Как вы можете видеть, мы использовали директиву препроцессора в GLSL, чтобы определить количество точечных источников света, которые будут присутствовать в нашей сцене. Затем мы используем константу NR_POINT_LIGHTS для создания массива структур PointLight. Массивы в GLSL так же, как и массивы в языке C++, могут быть созданы с помощью двух квадратных скобок. Сейчас у нас есть 4 структуры PointLight, которые нужно заполнить данными.

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

Функция в качестве аргументов принимает все необходимые ей данные и возвращает vec3, представляющий вклад, который вносит выбранный источник точечного света в цвет фрагмента. Опять же, использование «высокоинтеллектуальной» технологии copy/paste приводит нас к следующему результату:

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

Складываем всё вместе


Теперь, когда мы определили функцию для направленных источников света и функцию для точечных источников света, мы можем объединить всё это в функции main():

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

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

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

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

Здесь мы при помощи индекса 0 выбираем первую структуру PointLight в массиве pointLights и извлекаем её внутреннюю переменную constant, устанавливая ей значение 1.0.

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

Затем, при помощи нужного индекса, выбираем соответствующую структуру PointLight из массива pointLights и устанавливаем её атрибут position, равным значению первой позиции из всех, которые мы только что определили. Также теперь вместо одного (как было раньше) обязательно отрисуйте 4 световых куба. Для этого просто создайте другую матрицу модели для каждого из световых объектов (точно так же, как мы это сделали с контейнерами).

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

(вдали)

(вблизи)

  GitHub / Урок №15. Множественные источники света — Исходный код

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

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

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

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

Упражнения

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

Ответ


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

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

Комментариев: 3

  1. Некта:

    Спасибо большое за уроки!
    Но возникла довольно серьезная проблема, как и вопрос. Что делать то когда свет проходит сквозь стены? точнее сквозь все полигоны. Как я понимаю он вообще не учитывает это для простоты.
    Но для меня это серьезное упущение и никак не могу найти решение этой проблемы.

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

    1. Сергей Федоров:

      Это уже дальше в 32м уроке будет изучение теней.

  2. Сергей Федоров:

    Отличное заключение! Спасибо, Дмитрий.
    Признаюсь, получилось не сразу. Оказалось просто поспешил сразу «слепить» точечные источники в цикл, не подумав про изменение названия строковой uniform переменной в цикле.
    Решение:

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

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