Как работать с освещением на opengl
Перейти к содержимому

Как работать с освещением на opengl

  • автор:

Освещение в OpenGL

Освещение какого-либо пространства — это процесс, благодаря которому это пространство наполняется светом и все находящиеся в нём предметы делаются видимыми.
Освещение любого объекта зависит от двух факторов:

  • Первый — это материал, из которого сделан объект.
  • Второй — это свет, которым он освещен.

В зависимости от реализации OpenGL на сцене могут присутствовать восемь и более источников света.По умолчанию освещение отключено. Включить нулевой источник света можно командой:

  • glEnable(GL_LIGHT0);

Остальные включаются аналогичным способом, где вместо GL_LIGHT0 указывается GL_LIGHTi. После того, как источник включен, необходимо задать его параметры. Если монотонное тело у вас равномерно освещено, то вы не можете увидеть его рельеф. Поэтому нам нужно использовать источники света.
В OpenGL существует три типа источников света:

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

Для управления свойствами источника света используются команды glLight*:

  • glLightf(GLenum light, GLenum pname, GLfloat param);
    glLightfv(GLenum light, GLenum pname, const GLfloat *param);

Параметр light указывает OpenGL для какого источника света задаются параметры. Команда glLightf используется для задания скалярных параметров, а glLightfv используется для задания векторных характеристик источников света.

Сначала рассмотрим функцию, которая устанавливает базовые настройки. Когда вы разрешили освещение, то вы можете уже устанавливать фоновую освещенность. По умолчанию, значение фоновой освещенности равно (0.2, 0.2, 0.2, 1). Создайте новый проект, скопируйте туда шаблонный файл и отключите освещение. Вы с трудом сможете различить сферу на экране. С помощью функции glLightModel вы можете установить фоновое освещение. Если вы повысите его до (1,1,1,1), т.е. до максимума, то включать источники света вам не понадобится. Вы их действия просто не заметите, т.к. объект уже максимально освещен. И получится, что вы как бы отключили освещение. В общем, добавьте в main вызов следующей функции:

  • float ambient[4] = ;
    .
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambient);

Попробуйте изменить параметры и посмотрите на результат.

Материал
Материал может рассеивать, отражать и излучать свет. Свойства материала устанавливаются при помощи функции

  • glMaterialfv(GLenum face, GLenum pname, GLtype* params)

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

  • GL_BACK задняя грань
    GL_FONT передняя грань
    GL_FRONT_AND_BACK обе грани

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

  • GL_AMBIENT рассеянный свет
    GL_DIFFUSE тоже рассеянный свет
    GL_SPECULAR отраженный свет
    GL_EMISSION излучаемый свет
    GL_SHININESS степень отраженного света
    GL_AMBIENT_AND_DIFFUSE оба рассеянных света

Цвет задается в виде массива из четырех элементов — RGBA. В случае GL_SHININESS params указывает на число типа float, которое должно быть в диапазоне от 0 до 128.
Вам надо всего лишь модифицировать функцию display.

  • void CALLBACK display(void)
    GLUquadricObj *quadObj;
    GLfloat front_color[] = ;
    GLfloat back_color[] = ;
    quadObj = gluNewQuadric();
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
    glMaterialfv(GL_FRONT, GL_DIFFUSE, front_color);
    glMaterialfv(GL_BACK, GL_DIFFUSE, back_color);
    glPushMatrix();
    glRotated(110, -1,1,0);
    gluCylinder(quadObj, 1, 0.5, 2, 10, 10);
    glPopMatrix();
    gluDeleteQuadric(quadObj);
    auxSwapBuffers();
    >

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

  • glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);

Источники направленного света
Источника света такого типа находится в бесконечности и свет от него распространяется в заданном направлении. Идеально подходит для создания равномерного освещения. Хорошим примером источника направленного света может служить Солнце. У источника направленного света, кроме компонент излучения, можно задать только направление.

  • GL_POSITION (0.0, 0.0, 1.0, 0.0) //(x, y, z, w) направление источника направленного света

Первые три компоненты (x, y, z) задают вектор направления, а компонента w всегда равна нулю (иначе источник превратится в точечный).

Функции затухания
Это функция изменения интенсивности освещения(интенсивность света не убывает с расстоянием) , используется вместе с точечным освещением

  • GL_POSITION(0.0, 0.0, 1.0, 0.0)//позиция источника света (по умолчанию источник света направленный)
  • GL_CONSTANT_ATTENUATION 1.0 //постоянная k_const в функции затухания f(d)
  • GL_LINEAR_ATTENUATION 0.0 //коэффициент k_linear при линейном члене в функции затухания f(d)
  • GL_QUADRATIC_ATTENUATION 0.0 //коэффициент k_quadratic при квадрате расстояния в функции затухания f(d)

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

  • GL_SPOT_DIRECTION (0.0, 0.0, -1.0) //(x, y, z) — направление прожектора (ось ограничивающего конуса)
  • GL_SPOT_CUTOFF 180.0 //угол между осью и стороной конуса (он же половина угла при вершине)
  • GL_SPOT_EXPONENT 0.0 //экспонента убывания интенсивности

Тени
Тени напрямую не поддерживаются библиотекой OpenGL, поэтому их нужно разбирать отдельно .

Как работать с освещением на opengl

В этом разделе речь пойдет о возможностях настройки освещения в OpenGL при использовании фиксированного графического конвейера (fixed pipeline). В OpenGL используется модель освещения Блинна-Фонга в рамках которой можно использовать три типа источников света: направленный, точечный и прожектор. Используется повертексная модель освещения, т.е. каждая вершина освещается независимо. Такой способ освещения не учитывает препятствия между источником света и освещаемым объектом (не просчитывает тени), эффектов преломления, многократного отражения света и т.д.

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

Модель освещения

В модели Блинна-Фонга свет разбивается на три составляющие:

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

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

glEnable(GL_LIGHTING);

Для точной настройки освещения требуется:

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

В статьях этого раздела будет подробно рассказано о каждом пункте.

Использование источников света OpenGL для построения цветных теней

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

Должен сказать, что статья эта планировалась очень давно – года так с 2004, когда был написан трёхмерный движок, использующий описываемый метод. Однако, обилие формул и лень заставили меня уже в скором времени забросить написание статьи. Последний раз я касался текста в 2008 году. На этой дате статья и остановилась, и возрождать я её не планировал. Но недавно, в теме про исходники Quake 2, меня попросили всё же рассказать о применённой в моём трёхмерном движке технологии построения картинки. Актуальность столь запоздавших статей для современного программирования трёхмерной графики, конечно, нулевая, но возможно, начинающим изучать OpenGL эта статья будет интересна.

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

Скриншоты трёхмерного движка.

Как видите, для простейшего движка получаются достаточно красивые картинки.

Идея алгоритма заключается в том, что многоугольник, источник тени (в дальнейшем – МИТ), своей тенью разбивает многоугольник, приёмник тени (в дальнейшем – МПТ) на фрагменты A, B, C, D, E, как показано на рисунке ниже. Можно заметить, что для фрагментов A, B, E, D источник света включён, а для фрагмента C источник света отключён

Разбиение на фрагменты тенью.

Общий алгоритм построения освещения таков:

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

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

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

Реализация алгоритма

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

Функция, определяющая позицию точки относительно некоторой плоскости.

Входные данные:

— Точка плоскости ;
— Вектор нормали к плоскости ;
— Точка, для которой определяется положение относительно плоскости .

Возвращаемое значение: 1 — точка находится выше плоскости, -1 — точка находится ниже плоскости, 0- точка находится в плоскости.

Для реализации этой функции воспользуемся уравнением плоскости, тогда (1).

Полученное определяет положение точки относительно плоскости.

Функция должна возвращать -1, если , возвращать 1, если и возвращать 0, если .

Однако, мы работаем на разрядной сетке ЭВМ, поэтому сравнивать с нулём нельзя. Вместо этого мы будем здесь и далее сравнивать с некоторым малым , значение которого проще всего подобрать опытным путём так, чтобы алгоритм не давал сбоев.

В этом случае функция должна возвращать -1, если , возвращать 1, если и возвращать 0 во всех остальных случаях.

Функция, определяющая точку пересечения прямой с плоскостью.

Входные данные:
— Точка плоскости ;
— Вектор нормали к плоскости ;
— Первая точка прямой ;
— Вторая точка прямой ;

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

Вектор , задающий направление прямой вычисляется как

Тогда скалярное произведение между вектором нормали и вектором прямой будет .

Если (в нашем случае это условие примет вид и ), то прямая и плоскость пересекаться не могут, поскольку вектора перпендикулярны.

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

где — значение, полученное по формуле (1).

Функция, определяющая точку пересечения луча с плоскостью.

Входные данные:

— Точка плоскости ;
— Вектор нормали к плоскости ;
— Первая точка луча (начало луча) ;
— Вторая точка луча .

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

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

Так, если получится, что для или для (в нашем случае для или для ), то луч плоскость не пересекает.

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

Входные данные:

— Точки многоугольника, лежащие в одной плоскости ;
— Точка, находящаяся в плоскости многоугольника, для которой проверяется её нахождение внутри многоугольника .

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

Алгоритм работы функции выглядит так:

Введём переменную =индекс последней точки многоугольника;
Цикл по от 0 до ;
Введём переменные индексов трёх последовательных точек ,,;
Если , то ;
Если , то ;
Возьмём три последовательные точки многоугольника ,,;
Создадим три вектора: по формулам

Вычислим векторные произведения и ;

Вычислим скалярное произведение ;
Если (в нашем случае ), то точка внутрь многоугольника не попадает.
Конец цикла;
Точка попадает внутрь многоугольника.

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

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

Алгоритм построения фрагментов

Входные данные:

— Точки выпуклого многоугольника, являющегося источником тени), лежащие в одной плоскости;
— Точки выпуклого многоугольника, на который отбрасывается тень), лежащие в одной плоскости;
-Точка положения источника света (далее по тексту ИС — «источник света»);

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

Получение точек тени в плоскости МПТ

Рассмотрим алгоритм получения точек тени в системе координат МПТ.

    Проверим, что МИТ и МПТ имеют не менее трёх точек, иначе это, строго говоря, вообще не многоугольники, и тени нет.

Для этого сравним положение точки, точно попадающей в теневой объём относительно плоскостей, задаваемых гранями теневого объёма. Такая точка может быть получена, если найти геометрический центр МИТ
и пустить луч от ИС через этот центр, так же, как это делалось для нахождения вершин граней теневого объёма. Если при определении положения этой точки относительно плоскостей граней будет получено для какой-либо грани, что точка находится выше выбранной грани, то грань следует “перевернуть”, т.е. поменять знаки координат вектора нормали грани на противоположные. Направление обхода самих точек, задающих грань можно не менять, так как это в дальнейшем не понадобится.

Создание фрагмента

В архиве по ссылке (опять не на github — я с ним пока ещё не подружился) присутствуют программы с исходниками:

  1. Демонстрационная программа Lighting, строящая в реальном времени тени от двух источников света на пирамидку и плоскость под ней.
  2. 3D-движок, использующий вышеописанную технологию.
  3. Редактор карт 3D-движка (целиком на Win32API, никакого OWL или MFC).

Добавил ссылки на github:

Уроки OpenGL

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

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

треугольник( v1, v2, v3 )
сторона1 = v2-v1
сторона2 = v3-v1
треугольник.нормаль = вектПроизведение(сторона1, сторона2).нормализировать()

Вершинная Нормаль

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

вершина v1, v2, v3, .
треугольник tr1, tr2, tr3 // они все используют вершину v1
v1.нормаль = нормализовать( tr1.нормаль + tr2.нормаль + tr3.нормаль)

Использование нормалей вершин в OpenGL

Использовать нормали в OpenGL очень просто. Нормаль — это просто атрибут вершины, точно так же, как и позиция, цвет или UV координаты. Тоесть ничего нового учить не придется. даже наша простенькая функция loadOBJ уже загружает нормали.

GLuint normalbuffer;
glGenBuffers(1, &normalbuffer);
glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals[0], GL_STATIC_DRAW);

// Третий атрибутный буфер : нормали
glEnableVertexAttribArray(2);

glBindBuffer(GL_ARRAY_BUFFER, normalbuffer);
glVertexAttribPointer(
2, // атрибут
3, // размер
GL_FLOAT, // тип
GL_FALSE, // нормализованный ли?
0, // шаг
(void*)0 // смещение в буфере
);

И этого достаточно чтобы начать:

Диффузное освещение

Важность нормали к поверхности

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

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

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

С точки зрения компьютерной графики, цвет пикселя очень зависит от разности углов направления света и нормали поверхности.

// Косинус угла между нормалью и направлением света
// 1 — если свет перпендикулярен к треугольнику
// 0 — если свет параллелен к треугольнику
float cosTheta = dot( n,l );
color = LightColor * cosTheta;

В этом коде «n» — это нормаль, а «l» — единичный вектор который идет от поверхности к источнику света(а не наоборот, хотя это может показаться непонятным)

Будьте внимательны со знаком

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

// Косинус угла между нормалью и направлением света
// 1 — если свет перпендикулярен к треугольнику
// 0 — если свет параллелен к треугольнику
// 0 — если свет позади треугольника
float cosTheta = clamp( dot( n,l ), 0,1 );
color = LightColor * cosTheta;

Цвет материала

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

Мы можем промоделировать это простым умножением:

color = MaterialDiffuseColor * LightColor * cosTheta;

Моделирование света

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

С таким источником света, уровень освещения поверхности будет зависеть от расстояния до источника света: чем дальше, тем темнее. Эта зависимости рассчитывается так:

color = MaterialDiffuseColor * LightColor * cosTheta / (distance*distance);

Вскоре нам понадобится еще один параметр чтобы управлять уровнем силы света — цвет света, но пока, давайте предположим, что у нас есть лампочка белого света с определенной мощностью(например, 60 ватт).

color = MaterialDiffuseColor * LightColor * LightPower * cosTheta / (distance*distance);

Объединяем все вместе

Чтобы этот код работал нам нужен определенный набор параметров(цвета и мощности) и немного дополнительного кода.

MaterialDiffuseColor — мы можем взять прямо из текстуры.
LightColor и LightPower нужно будет выставить в шейдере с помощью GLSL uniform.

CosTheta будет зависеть от векторов n и l. Его можно вычислять для любого из пространств, угол будет одним и тем же. Мы будем использовать пространство камеры, так как тут очень просто посчитать положение светового источника:

// Нормаль фрагмента в пространстве камеры
vec3 n = normalize( Normal_cameraspace );
// Направление света(от фрагмента к источнику света
vec3 l = normalize( LightDirection_cameraspace );

Normal _ cameraspace и LightDirection _ cameraspace подсчитываются в вершинном шейдере и передаются во фрагментный для дальнейшей обработки:

// Позиция вершины в пространстве камеры :МВП * положение
gl_Position = MVP * vec4(vertexPosition_modelspace,1);
// Положение вершины в мировом пространстве: M * положение
Position_worldspace = (M * vec4(vertexPosition_modelspace,1)).xyz;
// Вектор который идет от вершины камере в пространстве камеры
// В пространстве камеры, камера находится по положению (0,0,0)
vec 3 vertexPosition _ cameraspace = ( V * M * vec 4( vertexPosition _ modelspace ,1)). xyz ;
EyeDirection_cameraspace = vec3(0,0,0) — vertexPosition_cameraspace;
// Вектор который идет от вершины к источнику света в пространстве камеры.
//Матрица M пропущена, так как она в в этом пространстве единичная.
vec3 LightPosition_cameraspace = ( V * vec4(LightPosition_worldspace,1)).xyz;
LightDirection_cameraspace = LightPosition_cameraspace +
EyeDirection_cameraspace;
// Нормаль вершины в пространстве камеры
Normal_cameraspace = ( V * M * vec4(vertexNormal_modelspace,0)).xyz; // Будет работать лишь в том случае , когда матрица модели не изменяет её размер .

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

Обязательно попробуйте.

M и V – это матрицы Модели и Вида, которые передаются в шейдер точно так же, как и наша старая добрая MVP.

Время испытаний

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

Результат

Только лишь с одной диффузной компонентой у нас получается вот такая вот картинка(простите меня за некрасивые текстуры).

Вроде бы как получше, чем было раньше, но многого еще не хватает. Особенно заметна проблема с неосвещенными частями. Затылок нашей дорогой мартышки Сюзанны полностью черный(мы ведь использовали clamp()).

Окружающее освещение(ambient lighting)

Окружающее освещение – это чистой воды читерство.

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

Однако это слишком вычислительно затратно делать в реальном времени. И именно поэтому мы будем добавлять некую постоянную составляющую. Как будто сам объект излучает немного света, чтобы не быть полностью черным.

vec3 MaterialAmbientColor = vec3(0.1,0.1,0.1) * MaterialDiffuseColor;
color =
// Окружающее освещение : симулируем непрямое освещение
MaterialAmbientColor +
// Диффузное : « цвет « самого объекта
MaterialDiffuseColor * LightColor * LightPower * cosTheta /
(distance*distance);

Результат

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

Отраженный свет(Specular light)

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

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

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

// вектор взгляда(в сторону камеры)
vec3 E = normalize(EyeDirection_cameraspace);
//Направление в котором треугольник отражает свет
vec 3 R = reflect (- l , n );
// Косинус угла между вектором взгляда и вектором отражения обрезанный до нуля если нужно
// — Смотрим прям на отражение -> 1
// -Смотрим куда-то в другую сторону -> < 1
float cosAlpha = clamp( dot( E,R ), 0,1 );
color =
// Окружающее освещение :симулируем непрямое освещение
MaterialAmbientColor +
// Диффузное : « цвет « самого объекта
MaterialDiffuseColor * LightColor * LightPower * cosTheta /
(distance*distance) ;
// Отраженное : отраженные отблески, как зеркало
MaterialSpecularColor * LightColor * LightPower * pow(cosAlpha,5) / (distance*distance);

R – направление в которое отражается свет. E – инвертированный вектор направления взгляда. Если угол между этими векторами маленький, мы смотрим прямо на отражение.

pow(cosAlpha,5) используется для того, чтобы контролировать размер светового пятна. Увеличивайте 5, чтобы получить пятнышко меньше.

Финальный результат

Обратите внимание на отблески на носу, глазах и бровях.

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

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

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