Россия и Беларусь начали и продолжают войну против народа Украины!

Урок №13. Карты освещения в OpenGL

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

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

 9306

 ǀ   7 

На предыдущем уроке мы обсуждали, что каждому объекту можно назначить свой собственный уникальный материал, который будет по-разному реагировать на свет. Это отличный способ придать любому объекту уникальный (в сравнении с другими объектами) внешний вид, но все же он не дает нам необходимой гибкости при визуальном отображении объекта.

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

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

Диффузные карты

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

Вероятно, данная подсказка уже навела вас на определенные мысли. Ведь всё это очень напоминает текстуры и это действительно именно так: текстура — вот тот способ, который нам нужен. Ведь если присмотреться, то это просто другое название для того же самого основополагающего принципа: использовать изображение, наложенное на объект, с помощью которого мы можем задавать разные значения цвета для каждого фрагмента. В сценах с освещением такая текстура обычно носит название диффузной карты (или «карта рассеянного света», от англ. «diffuse map»), поскольку текстурное изображение описывает все цвета рассеянной составляющей цвета объекта.

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

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

Примечание: Имейте в виду, что sampler2D — это так называемый непрозрачный тип данных (англ. «opaque type»), экземпляры которого мы создать не можем, а можем только определить их как uniform-переменные. Если бы экземпляр структуры определялся не так, как uniform-переменная (например, в качестве параметра функции), то GLSL выбрасывал бы странные ошибки; то же самое относится и к любой другой структуре, содержащей такие непрозрачные типы данных.

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

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

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

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

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

Обновленные вершинные данные выглядят следующим образом:

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

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

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

  GitHub / Урок №13. Карты освещения в OpenGL — Исходный код №1

Карты отраженного цвета


Вы, вероятно, заметили, что отраженное освещение ящика выглядит немного странно, так как объект представляет собой контейнер, состоящий в основном из дерева, а дерево не имеет таких ярко выраженных зеркальных бликов. Мы можем исправить эту ситуацию, установив значение отраженной составляющей цвета материала равным vec3(0.0), но это будет означать, что стальные края контейнера перестанут отбрасывать зеркальные блики, а сталь должна их отбрасывать. Было бы неплохо, если бы у нас была возможность контролировать то, какие части объекта могут отбрасывать отраженный цвет, а какие — нет, и также варьировать его интенсивность. Звучит знакомо, не так ли?

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

Интенсивность отраженного цвета зависит от яркости каждого пикселя изображения. Каждый пиксель карты отраженного цвета может быть представлен в виде цветового вектора, где, например, черный цвет задается цветовым вектором vec3(0.0), а серый цвет — цветовым вектором vec3(0.5). Затем во фрагментном шейдере мы выбираем соответствующее значение цвета и умножаем это значение на интенсивность отраженной составляющей света. Чем сильнее цвет пикселя уходит в сторону белого цвета, тем больше значение результата умножения и тем ярче становится составляющая отраженного цвета объекта.

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

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

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

Сэмплирование карт отраженного цвета

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

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

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

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

Примечание: Если вы не хотите быть и делать «как все», то можете использовать фактические цвета в карте отраженного цвета, чтобы не только устанавливать зеркальную интенсивность каждого фрагмента, но и задавать цвет отраженного блика. Однако в реальности цвет отраженного блика в основном определяется самим источником света, поэтому он не создает реалистичных визуальных эффектов (вот почему изображения обычно черно-белые: нам важна только интенсивность).

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

  GitHub / Урок №13. Карты освещения в OpenGL — Исходный код №2

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

Упражнения


Задание №1

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

Задание №2

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

Ответ №2

Задание №3

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

Результат должен быть примерно таким:

Задание №4

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

Результат должен быть примерно следующим:

Ответ №4

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

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

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

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

    Мне показалось простейшее решение — добавить по краям текстуры эмиссии черную рамку в 40 pix (на ширину металла).

     

  2. Иван:

    для эмиссии можно просто другие текстурные координаты задать

  3. morou:

    Если у кого в 4-м задании получилось также как у меня: https://ibb.co/HY6bnvM
    то вот небольшое дополнение, вроде работает

    1. Николай:

      Хорошее решение, но вообще это плохая практика использовать долго вычислимые блоки кода в шейдерных программах, коими являются if else ветвления. Ведь из-за того что пикселей на экране — не меньше 2млн (1920х1080) , то делать столько раз обрыв операции и переход на другую — не эффективно. Советую лучше попробовать ограничить использование ветвей , и использовать встроенные (build in) функции в GLSL, которые справляются с этой работой не хуже, или пытаться продумать другое решение.

      как это например.

      1. morou:

        спасибо! попробую.

    2. Grave18:

      Еще как вариант:

      Чем больше будет кат, тем больше будет проявляться эмиссия на блестящей части.

      1. Николай:

        Либо так)

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

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