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

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

  Обновл. 8 Июн 2020  | 

 814

В предыдущих уроках мы узнали много нового об устройстве освещения в 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 световых куба. Для этого просто создайте другую матрицу модели для каждого из световых объектов (точно так же, как мы сделали это с контейнерами).

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

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

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

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

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

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

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

Упражнения

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

Ответ


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

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

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

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