Сервис для
сео - оптимизаторов

Найди ошибки на сайте
Ошибки мешают продвижению сайта
Исправь ошибки на сайте
Сайт без ошибок продвигать легче
Получи новых клиентов
Новые клиенты принесут больше прибыль

Modern OpenGL 06 - диффузное точечное освещение

  1. Доступ к коду
  2. Управление с клавиатуры для этой статьи
  3. Point Lights
  4. Модель отражения Фонга
  5. Интенсивность света
  6. Поглощение и отражение цвета
  7. Угол падения
  8. Поверхностные Нормы
  9. Вычисление угла между двумя векторами: точечный продукт
  10. Матричное преобразование нормалей
  11. Вершинный шейдер
  12. Фрагмент шейдера
  13. Изменения в main.cpp
  14. Per-vertex и Per-фрагментное освещение
  15. Future Article Sneak Peek

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

Доступ к коду

Загрузите весь код в виде почтового индекса здесь: https://github.com/tomdalling/opengl-series/archive/master.zip

Весь код из этой серии статей доступен на github: https://github.com/tomdalling/opengl-series , Вы можете загрузить zip-файл всех файлов с этой страницы или клонировать репозиторий, если вы знакомы с git.

Эта статья основана на коде из предыдущей статьи.

Код этой статьи можно найти в источник / 06_diffuse_lighting папка. В OS X откройте файл opengl-series.xcodeproj в корневой папке и выберите цель, соответствующую этой статье. В Windows откройте файл opengl-series.sln в Visual Studio 2013 и откройте проект, соответствующий этой статье.

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

Управление с клавиатуры для этой статьи

W Движение вперед A Движение влево S Движение назад D Движение вправо X Движение вверх Z Движение вниз 1 Установите положение света в положение камеры 2 Установите интенсивность света на зеленый 3 Установите интенсивность света на красный 4 Установите интенсивность света на белый

Point Lights

Точечные источники света излучают свет во всех направлениях из одной точки, как свеча.

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

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

Существуют и другие распространенные типы источников света, такие как направленные источники света и прожекторы, но мы рассмотрим их в следующей статье

Изображение от 4028mdk09

Модель отражения Фонга

Модель отражения Фонга предоставляет метод расчета цвета пикселя на основе параметров источника света и поверхности.

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

Изображение от Брэд Смит ,

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

Интенсивность света

Модель отражения Фонга слабо основана на том, как свет ведет себя в реальном мире. Итак, чтобы понять освещение в OpenGL, мы должны немного понять физику света - не много, но достаточно, чтобы наша 3D-сцена выглядела более реалистично.

Белый свет содержит все цвета.

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

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

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

Если бы вы сделали это, вы бы увидели изображение ниже

Из этого можно сделать некоторые выводы:

  • Белый = красный + зеленый + синий
  • Желтый = красный + зеленый
  • Голубой (голубой) = синий + зеленый
  • Пурпурный (пурпурно-розовый) = красный + синий
  • Черный = ни один из цветов

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

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

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

Цвета представляют собой комбинации различной интенсивности красного, зеленого и синего света. Цвет света называется интенсивностью света.

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

Поглощение и отражение цвета

Допустим, вы смотрите на красную машину. Солнце излучает луч белого света. Луч отскакивает от машины и попадает в твои глаза. Ваш глаз обнаруживает, что луч содержит только красный свет, поэтому вы видите красную машину вместо белой. Мы знаем, что белый свет содержит все цвета, так что же случилось с зеленым и синим? Зеленый и синий свет был поглощен поверхностью, а красный свет был отражен .

Зеленый и синий свет был поглощен поверхностью, а красный свет был отражен

Что, если бы мы зажгли чистый красный (синий + зеленый) свет на красной машине? Если бы автомобиль был чисто красным, он бы выглядел черным , потому что он поглощал бы 100% света.

Если бы автомобиль был чисто красным, он бы выглядел черным , потому что он поглощал бы 100% света

Как насчет голубого (синего + зеленого) света на пурпурной (красной + синей) поверхности?

Как насчет голубого (синего + зеленого) света на пурпурной (красной + синей) поверхности

Так что, если вы светите голубым фонариком на пурпурно-розовую поверхность, поверхность будет темно-синей. Странно, но это правда.

Цвет RGB поверхности показывает, как свет поглощается и отражается этой поверхностью.

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

Расчет отраженного цвета прост. Основная формула: интенсивности × цвет поверхности = отраженные интенсивности. Например:

голубой свет × пурпурная поверхность = синий свет (0, 1, 1) × (1, 0, 1) = (0, 0, 1)

Умножение выполняется путем умножения каждого из компонентов RGB по отдельности, например так:

[blockmath] (X, Y, Z) (A, B, C) = (XA, YB, ZC) [/ blockmath]

Угол падения

Вот милая анимация, когда я вращаю блокнот перед светом:

Вот милая анимация, когда я вращаю блокнот перед светом:

Эта анимация демонстрирует, как угол падения (AoI) света влияет на цвет поверхности (блокнот). Обратите внимание, что блокнот ярче всего, когда он направлен на фонарь. Когда блокнот поворачивается от своего яркого положения, поверхность становится темнее.

Когда блокнот поворачивается от своего яркого положения, поверхность становится темнее

Угол, под которым лучи света попадают на поверхность, называется углом падения (AOI). Угол падения влияет на яркость поверхности.

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

Давайте посмотрим на AoI на двух крайностях: максимальная яркость и полная темнота.

Давайте посмотрим на AoI на двух крайностях: максимальная яркость и полная темнота

Максимальная яркость возникает, когда поверхность перпендикулярна световым лучам (AoI = 0 °). Полная темнота происходит, когда поверхность параллельна лучам света (AoI = 90 °). Если AoI больше 90 °, то луч попадает на заднюю часть поверхности. Если свет падает на заднюю часть, то он определенно не попадает на переднюю, поэтому пиксель также должен быть полностью темным.

Если мы представляем яркость в виде единого числа, где 0,0 - полностью темный, а 1,0 - максимальная яркость, то это легко вычислить на основе косинуса AOI. Формула: яркость = cos (AoI). Давайте посмотрим на косинус некоторых углов, просто чтобы доказать, что он работает:

cos (0 °) = 1,00 (100% от максимальной яркости) cos (5 °) = 0,98 (98% от максимальной яркости) cos (45 °) = 0,71 (71% от максимальной яркости) cos (85 °) = 0,09 ( 9% от максимальной яркости) cos (90 °) = 0,00 (полностью темный) cos (100 °) = -0,17 (полностью темный. Отрицательное значение означает, что свет попадает на обратную сторону)

Если у нас есть значение яркости от 0 до 1, мы можем умножить его на интенсивность отраженного света, чтобы получить окончательный цвет для пикселя. Вот пример с голубым светом:

яркость × интенсивность света = конечный цвет для пикселя 1,0 × (0, 1, 1) = (0, 1, 1) (голубой, без изменений) 0,5 × (0, 1, 1) = (0, 0,5, 0,5) (бирюзовый , затемненный голубой) 0,0 × (0, 1, 1) = (0, 0, 0) (черный)

Это значение «яркости» между 0 и 1 иногда называют «коэффициентом диффузии».

Поверхностные Нормы

Нормалы - это единичные векторы, перпендикулярные (под прямым углом, 90 °) к поверхности.

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

Угол падения определяется как угол между нормалью поверхности и направлением на источник света.

NN

= вектор нормали поверхности
L = вектор от поверхности до источника света
θ = угол падения

Вектор от поверхности до источника света, L , можно рассчитать с помощью вычитания вектора, например, так:

[blockmath] L = lightPosition - surfacePosition [/ blockmath]

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

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

Вычисление угла между двумя векторами: точечный продукт

Можно рассчитать угол между двумя векторами, используя скалярное произведение векторов.

Можно рассчитать угол между двумя векторами, используя скалярное произведение векторов. Точечное произведение - это операция, которая принимает два вектора и приводит к единственному числу (скаляру). Шокирующе, точечный продукт выглядит как точка в математической записи: $$ \ vec {v_1} \ bullet \ vec {v_2} $$. В GLSL и GLM это функция с именем «точка»: точка (v1, v2) и glm :: dot (v1, v2).

Результат скалярного произведения связан с углом между двумя векторами. Точные отношения:

[blockmath] \ begin {align} \ vec {v_1} \ bullet \ vec {v_2} & = | \ vec {v_1} || \ vec {v_2} | cos (\ theta) \\ \ frac {\ vec {v_1 } \ bullet \ vec {v_2}} {| \ vec {v_1} || \ vec {v_2} |} & = cos (\ theta) \\ cos ^ {- 1} \ left (\ frac {\ vec {v_1) } \ bullet \ vec {v_2}} {| \ vec {v_1} || \ vec {v_2} |} \ right) & = \ theta \ end {align} [/ blockmath]

Где $$ \ vec {v_1} $$ и $$ \ vec {v_2} $$ - векторы, $$ \ theta $$ - угол между двумя векторами, а $$ | \ vec v | $$ - величина $$ \ vec v $$.

Точно такая же вещь, написанная как код, выглядит так:

точка (v1, v2) == длина (v1) * длина (v2) * cos (угол) точка (v1, v2) / (длина (v1) * длина (v2)) == cos (угол) acos (точка ( v1, v2) / (длина (v1) * длина (v2))) == угол

длина - это функция GLSL, которая возвращает величину вектора.

Нам на самом деле не нужно знать AoI, нам просто нужен cos (AoI), который представляет яркость. Расчет яркости в фрагментном шейдере основан на средней строке приведенных выше формул.

// cos (угол) = точка (v1, v2) / (длина (v1) * длина (v2)) яркость с плавающей точкой = точка (нормальная, surfaceToLight) / (длина (surfaceToLight) * длина (нормальная));

Матричное преобразование нормалей

Нормалы обычно предоставляются в пространстве модели , что означает, что они относятся к вершинам актива до применения каких-либо преобразований. Однако, когда мы вычисляем вектор от поверхности до света, это делается в мировом пространстве . Мировое пространство - это место, где все трехмерные объекты были расположены / масштабированы / повернуты на свои места внутри трехмерной сцены. Например, в модельном пространстве центр нашего актива деревянного ящика равен (0,0,0). После того, как мы разместим и изменим размеры ящиков, чтобы обозначить «Привет» на трехмерной сцене, эти ящики будут в мировом пространстве. Преобразование из модельного пространства в мировое пространство выполняется с помощью «модельной матрицы» каждого экземпляра сцены, которая является переменной ModelInstance :: transform в коде для этой статьи.

Когда мы преобразовываем вершины актива, мы также должны преобразовывать нормали.

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

Масштабирование или перевод нормального значения приведет к неправильному нормальному.

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

Удалить часть перевода матрицы 4x4 просто: мы просто удаляем 4-й столбец и строку, преобразовывая ее в матрицу 3x3. Исправить масштабирование немного сложнее, но я сразу перейду к ответу, который должен инвертировать и транспонировать матрицу. Нам также нужно будет перенормировать каждую нормаль после ее преобразования, чтобы убедиться, что она все еще является единичным вектором. GLSL для этого выглядит следующим образом:

mat3 normalMatrix = transpose (обратный (mat3 (модель))); vec3 transformedNormal = normalize (normalMatrix * normal);

Переменная модели - это исходная матрица преобразования модели 4x4. Функция mat3 удаляет часть перевода матрицы. Обратные и транспонированные функции исправят часть масштабирования. Наконец, после того, как мы преобразовали исходную нормаль с помощью normalMatrix * normal, функция нормализации будет гарантировать, что преобразованная нормаль является единичным вектором.

Примечание по оптимизации: пересчет матрицы нормального преобразования для каждого фрагмента / пикселя не очень хорош для производительности. Для лучшей производительности вычислите матрицу в C ++ и сделайте ее равномерной внутри шейдеров.

Вершинный шейдер

Уф! Это было много чтения, но теперь мы входим в код. Давайте начнем с рассмотрения вершинного шейдера.

# версия 150 униформа mat4 камера; униформа mat4 модель; в vec3 vert; в vec2 vertTexCoord; в vec3 vertNormal; out vec3 fragVert; out vec2 fragTexCoord; out vec3 fragNormal; void main () {// Передаем некоторые переменные в шейдер фрагмента fragTexCoord = vertTexCoord; fragNormal = vertNormal; fragVert = vert; // Применяем все преобразования матрицы к vert gl_Position = camera * model * vec4 (vert, 1); }

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

Фрагмент шейдера

Теперь давайте посмотрим на фрагментный шейдер, где все расчеты освещения сделаны.

# версия 150 униформа mat4 модель; равномерный сэмплер2D текс; равномерная структура Light {vec3 position; VEC3 интенсивности; // он же цвет света} light; в vec2 fragTexCoord; в vec3 fragNormal; в vec3 fragVert; out vec4 finalColor; void main () {// вычислить нормаль в мировых координатах mat3 normalMatrix = transpose (inverse (mat3 (model))); vec3 normal = normalize (normalMatrix * fragNormal); // вычисляем местоположение этого фрагмента (пикселя) в мировых координатах vec3 fragPosition = vec3 (model * vec4 (fragVert, 1)); // вычисляем вектор от этой поверхности пикселей до источника света vec3 surfaceToLight = light. position - fragPosition; // вычисляем косинус угла падения float яркости = точка (нормальная, surfaceToLight) / (length (surfaceToLight) * length (нормальная)); яркость = зажим (яркость, 0, 1); // вычисляем конечный цвет пикселя, основываясь на: // 1. Угол падения: яркость // 2. Цвет / интенсивность света: light.intensities // 3. Текстура и координата текстуры: текстура (текс , fragTexCoord) vec4 surfaceColor = texture (tex, fragTexCoord); finalColor = vec4 (яркость * свет. интенсивность * surfaceColor. rgb, surfaceColor. a); }

У нас есть новая форма шейдера под названием light, которая представляет собой структуру, содержащую положение и интенсивность / цвет света.

Переменные fragTexCoord, fragNormal и fragVert все поступают прямо из вершинного шейдера. fragTexCoord - это координата текстуры, как мы видели в предыдущих статьях. fragNormal - это нормальная для этого фрагмента / пикселя нетрансформированная поверхность. fragVert - нетрансформированная координата поверхности, которую мы рисуем.

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

mat3 normalMatrix = transpose (обратный (mat3 (модель))); vec3 normal = normalize (normalMatrix * fragNormal);

Следующая часть преобразует поверхностную координату fragVert в мировое пространство.

vec3 fragPosition = vec3 (модель * vec4 (fragVert, 1));

Униформа модели представляет собой матрицу 4x4, поэтому мы преобразовываем координату в vec4, чтобы выполнить преобразование, а затем возвращаем обратно в vec3.

Затем мы вычисляем вектор от координаты поверхности до координаты света, которые находятся в мировом пространстве.

vec3 surfaceToLight = light. position - fragPosition;

Далее рассчитаем яркость, равную cos (angleOfIncidence). Как объяснялось ранее в статье, мы используем точечное произведение нормали и вектора, указывающего на свет.

яркость с плавающей точкой = точка (нормальная, surfaceToLight) / (длина (surfaceToLight) * длина (нормальная));

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

яркость = зажим (яркость, 0, 1);

Наконец, используя яркость, интенсивность света и цвет поверхности, мы можем вычислить цвет фрагмента / пикселя.

vec4 surfaceColor = texture (tex, fragTexCoord); finalColor = vec4 (яркость * свет. интенсивность * surfaceColor. rgb, surfaceColor. a);

текстура (tex, fragTexCoord) получит цвет поверхности от текстуры. Этот цвет поверхности определяет, как интенсивность света отражается или поглощается. Запомните формулу интенсивности × цвет поверхности = отраженные интенсивности. Мы реализуем это здесь с помощью кода light.intensities * surfaceColor.rgb.

После того, как мы рассчитаем интенсивность отраженного света с помощью light.intensities * surfaceColor.rgb, мы умножим их на яркость, чтобы получить окончательный цвет. Умножение на яркость затемнит отраженные интенсивности в зависимости от угла падения.

Интенсивность света - vec3 (RGB), но окончательный цвет - vec4 (RGBA), поэтому мы преобразуем интенсивности в vec4 и устанавливаем альфа-канал в альфа-значение из текстуры: surfaceColor.a. Это означает, что отображаемая поверхность будет прозрачной везде, где текстура прозрачна.

Изменения в main.cpp

Большая часть освещения выполняется фрагментным шейдером, поэтому изменения в коде C ++ довольно незначительны.

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

struct Light {glm :: vec3 position; интенсивность glm :: vec3; // он же цвет света}; // ... Light gLight;

Мы обновляем VBO, чтобы включить нормаль для каждой вершины. Старые данные буфера выглядели так:

// XYZUV // внизу - 1,0f, - 1,0f, - 1,0f, 0,0f, 0,0f, 1,0f, - 1,0f, - 1,0f, 1,0f, 0,0f, - 1,0f, - 1,0f, 1,0 f, 0,0f, 1,0f, 1,0f, - 1,0f, - 1,0f, 1,0f, 0,0f, 1,0f, - 1,0f, 1,0f, 1,0f, 1,0f, - 1,0f, - 1,0f, 1,0 f, 0.0f, 1.0f, // ...

И теперь это выглядит так:

// XYZUV Normal // bottom - 1.0f, - 1.0f, - 1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, 0.0f, 1.0f, - 1.0f, - 1.0f, 1.0f, 0.0 f, 0,0f, - 1,0f, 0,0f, - 1,0f, - 1,0f, 1,0f, 0,0f, 1,0f, 0,0f, - 1,0f, 0,0f, 1,0f, - 1,0f, - 1,0f, 1,0f, 0,0f, 0,0f, - 1,0f, 0,0f, 1,0f, - 1,0f, 1,0f, 1,0f, 1,0f, 0,0f, - 1,0f, 0,0f, - 1,0f, - 1,0f, 1,0f, 0,0f, 1,0f, 0,0f, - 1,0f, 0,0f, // ...

Вершины, показанные выше, составляют нижнюю грань деревянного ящика. Обратите внимание, что все они имеют одинаковую нормаль (0, -1, 0), которая представляет собой единичный вектор, направленный прямо вниз по оси Y. На деревянном ящике шесть граней, и у каждой грани есть нормаль, которая указывает прямо вниз в положительном или отрицательном направлении оси X, Y или Z.

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

// подключаем xyz к атрибуту "vert" вершинного шейдера glEnableVertexAttribArray (gWoodenCrate. shaders -> attrib ("vert")); glVertexAttribPointer (gWoodenCrate. shaders -> attrib ("vert"), 3, GL_FLOAT, GL_FALSE, 5 * sizeof (GLfloat), NULL); // соединяем координаты uv с атрибутом «vertTexCoord» вершинного шейдера glEnableVertexAttribArray (gWoodenCrate. shaders -> attrib ("vertTexCoord")); glVertexAttribPointer (gWoodenCrate. shaders -> attrib ("vertTexCoord"), 2, GL_FLOAT, GL_TRUE, 5 * sizeof (GLfloat), (const GLvoid *) (3 * sizeof (GLfloat)));

И это новый код установки VAO:

// подключаем xyz к атрибуту "vert" вершинного шейдера glEnableVertexAttribArray (gWoodenCrate. shaders -> attrib ("vert")); glVertexAttribPointer (gWoodenCrate. shaders -> attrib ("vert"), 3, GL_FLOAT, GL_FALSE, 8 * sizeof (GLfloat), NULL); // соединяем координаты uv с атрибутом «vertTexCoord» вершинного шейдера glEnableVertexAttribArray (gWoodenCrate. shaders -> attrib ("vertTexCoord")); glVertexAttribPointer (gWoodenCrate. shaders -> attrib ("vertTexCoord"), 2, GL_FLOAT, GL_TRUE, 8 * sizeof (GLfloat), (const GLvoid *) (3 * sizeof (GLfloat))); // подключаем нормаль к атрибуту "vertNormal" вершинного шейдера glEnableVertexAttribArray (gWoodenCrate. shaders -> attrib ("vertNormal")); glVertexAttribPointer (gWoodenCrate. shaders -> attrib ("vertNormal"), 3, GL_FLOAT, GL_TRUE, 8 * sizeof (GLfloat), (const GLvoid *) (5 * sizeof (GLfloat)));

Это в основном то же самое, с добавлением нормалей.

Теперь давайте посмотрим, как изменился рендеринг внутри функции RenderInstance. Перед рендерингом каждого экземпляра все, что нам нужно сделать, это установить положение и цвет света в шейдерах. Мы занимаем позицию и интенсивность прямо из gLight global.

шейдеры -> setUniform ("light.position", gLight. position); шейдеры -> setUniform ("light.intensities", gLight. интенсивности);

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

// переместить свет, если (glfwGetKey (gWindow, '1')) gLight. position = gCamera. позиция (); // изменить цвет света if (glfwGetKey (gWindow, '2')) gLight. интенсивности = glm :: vec3 (1, 0, 0); // красный if (glfwGetKey (gWindow, '3')) gLight. интенсивности = glm :: vec3 (0, 1, 0); // зеленый, если if (glfwGetKey (gWindow, '4')) gLight. интенсивности = glm :: vec3 (1, 1, 1); // белый

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

Нажатие кнопок 2, 3 и 4 изменит цвет подсветки.

Внутри функции AppMain мы устанавливаем начальную позицию и цвет источника света при запуске программы.

Свет. position = gCamera. позиция (); Свет. интенсивности = glm :: vec3 (1, 1, 1); // белый

Наконец, есть пара изменений, которые не связаны с освещением. Я увеличил скорость движения камеры, потому что она казалась немного медленной. Я также увеличил расстояние до камеры от 0,01 до 0,5, потому что 0,01 было слишком мало, и из-за этого на деревянных ящиках появлялись странные неровные края.

gCamera. setNearAndFarPlanes (0.5f, 100.0f);

Это оно! Теперь у нас есть единственный рассеянный точечный свет. Это первый шаг в 3D освещении, и мы будем опираться на него в следующих нескольких статьях.

Per-vertex и Per-фрагментное освещение

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

В этой статье мы реализовали осветление каждого фрагмента, также известное как Фонг затенение - не путать с Модель отражения Фонга , Вместо этого мы могли бы реализовать освещение для каждой вершины, также известное как Затенение Гуро , Разница между этими двумя реализациями заключается в том, где выполняются вычисления освещения: в фрагментном шейдере или в вершинном шейдере. В старом конвейере с фиксированными функциями OpenGL реализовано затенение для каждой вершины, если вы хотели освещение для каждого фрагмента, вы должны были написать свои собственные шейдеры. Освещение по фрагментам выглядит лучше, чем освещение по вершинам, что является хорошей причиной для перехода от старого конвейера с фиксированной функцией к шейдерам.

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

Теперь давайте посмотрим на один из ящиков на скриншоте для этой статьи:

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

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

Изображения от З-Б

Низкополигональная сфера имеет странную пятиугольную подсветку, но высокополигональная сфера имеет круглую подсветку, которая выглядит более правильной.

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

Освещение по вершинам обычно быстрее, чем освещение по фрагментам.

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

Future Article Sneak Peek

В следующих нескольких статьях мы будем реализовывать больше аспектов освещения. Мы будем реализовывать внешние и зеркальные компоненты модели отражения Фонга. Мы также рассмотрим другие виды освещения: направленное освещение и прожекторы.

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

Похожие

Руководство по DraftSight БЕСПЛАТНО!
Вы хотели бы быть продвинутым пользователем DraftSight ! Если так, то учебник для электронной книги ждет вас, и это первый том DraftSight Fundsmentals бесплатно! Он предоставит вам необходимые основы о том, как эффективно и эффективно работать в программном обеспечении. Это очень простой способ учиться и приобретать знания, содержит обширную коллекцию советов, подсказок и обучающих видео, которые помогут вам максимально эффективно работать с программным
HandyNotes - Дополнения - World of Warcraft - CurseForge
... преобразование возможно. Настоятельно рекомендуется переключиться на GetNodes2, чтобы вы могли поддерживать новые зоны! Изменения с v1.2.0 до v1.4.0 HandyNotes теперь использует HereBeDragons-1.0 вместо неуправляемой Astrolabe Плагины, которые напрямую ссылаются на Astrolabe, должны быть обновлены, и в идеале не требуют ничего, что API-интерфейс HandyNotes не предоставляет Новая функция API HandyNotes: HN: GetContinentZoneList
Мобильный Wi-Fi
Даже горячая точка есть! из Оливер Шоншек Отели предлагают это, аэропорты предлагают это, и на бензозаправочной станции это также доступно: бесплатный Fi -Доступ. На самом деле, беспроводной доступ в Интернет или обмен данными
USB type C станет новым стандартом в мире компьютеров
USB является самым распространенным стандартом портов в мире технологий. Сейчас, спустя 15 лет, оно полностью меняется. Большая часть информации о новом типе соединения была подтверждена на IDF14 в Шензене - Арстехника сообщает , Мы узнали, среди прочего, что пропускная способность USB 3.1 составляет 10 Гбит / с, что в два раза больше,
Управление учетными записями агентов в LiveChat
... ина Яновска Если вы планируете использовать LiveChat в команде, вам нужно будет пригласить своих товарищей по команде . Вы можете сделать это вручную или по приглашению. Создание профиля для каждого товарища по команде позволяет отслеживать не только отдельные статистические данные, но и группировать агентов на основе их навыков и задач. В этой статье: Добавить агентов Обратите внимание, что адрес электронной почты агента не может быть
Безопасный менеджер загрузок WordPress и плагин онлайн-каталога файлов
Лучшее безопасное решение для управления файлами и загрузкой WP Плагин CM WordPress File Manager - превосходное решение для создания каталога
Samsung Galaxy Tab S 8.4: на пути к совершенству
Samsung Galaxy Tab PRO 8.4, без сомнения, является одним из лучших планшетов на рынке, и кто знает, может быть, даже лучший маленький планшет с Android по разумной цене. Однако, как это бывает, не обошлось без изъянов. Является ли их лучшая версия Galaxy Tab S 8.4 лишенной их? Читайте также: Samsung Galaxy Tab Pro: 8.4 теперь новая 7 - паутина

Комментарии

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

Но как насчет других цветов, таких как оранжевый?
Мы знаем, что белый свет содержит все цвета, так что же случилось с зеленым и синим?
Что, если бы мы зажгли чистый красный (синий + зеленый) свет на красной машине?
Как насчет голубого (синего + зеленого) света на пурпурной (красной + синей) поверхности?
Лишенной их?
Как ограничить доступ к загрузкам для определенных групп пользователей?