Материалы и освещение. Продолжаем изучение OpenGL: освещение по Фонгу Направленные источники света

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

  • Каксделатьтак,чтобыобъектбылярчекогданаходитсяближекисточникусвета.
  • Каксделатьотблескикогдамывидимотраженныйсветнапредмете(specular lighting )
  • Как сделать, чтобы объект был немного затененный, когда свет падает не прямо на объект(diffuse lighting)
  • Подсветка сцены(ambient lighting)
  • Тени. Эта тема заслуживает отдельного урока(или уроков, если даже не книг).
  • Зеркальное отражение(например, вода)
  • Подповерхностное рассеивание(например, как у воска)
  • Анизотропные материалы(окрашенный металл, например)
  • Затенение основанное на физических процессах, чтобы имитировать реальность еще лучше.
  • Преграждениесвета(Ambient Occlusion есличто-топреграждаетсвет,тостановитсятемнее)
  • Отражение цвета(красный ковер будет делать белый потолок слегка слегка красноватым)
  • Прозрачность
  • Глобальное освещение(в принципе все что мы указали выше можно назвать этим термином)

Другими словами, самое простое освещение и затенение.

Нормали

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

Нормали Треугольников

Нормаль к плоскости — это единичный вектор который направлен перпендикулярно к этой плоскости.

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

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

треугольник(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);

glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(glm::vec3), &normals, GL_STATIC_DRAW);

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

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

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


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

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

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

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


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


//
//
float cosTheta = dot(n,l);

В этом коде «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) /

В следующем уроке мы будем разбирать, как можно ускорить рендеринг нашего VBO.

Без источника света изображения не видно. Что бы инициализировать источник, и включить обработчик расчёта воздействия источника на объекты достаточно выполнить команды: glEnable(gl_lighting);// включить режима анализа освещения

GlEnable(gl_light0); // включить в сцену конкретный (нулевой) источник, с его характеристиками

Для отключения источника используется функция Disable(). Источник света по умолчанию располагается в пространстве с координатами (0,0,∞). Можно создавать источник света в любой точке пространства изображений.

В библиотеке OpenGl поддерживаются источники света четырех типов:

  • фонового освещения (ambient lighting),
  • точечные источники (point sources),
  • прожекторы (spotlights),
  • удаленные источники света (distant light).
Каждый источник света имеет свой набор характеристик.
Характеристики источника света, соответствуют параметрам модели Фонга.
Для установки векторных параметров используется функция glLightfv(), которая имеет следующий формат:

glLightfv(source, parameter, pointer_to_array) ;

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

glLightf(source, parameter, value) ;

Пусть, например, требуется включить в сцену источник GL_LIGHT0, который должен находиться в точке (1.0, 2.0, 3.0). Положение источника сохраняется в программе в виде точки в однородных координатах:

GLfloat light0_pos={1.0, 2.0, 3.0, 1.0};

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

GLfloat light0_dir={1.0, 2.0, 3.0, 0.0};

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

GLfloat diffise0= {1.0, 0.0, 0.0, 1.0};

GLfloat ambient0={1.0, 0.0, 0.0, 1.0};

GLfloat specular0={1.0, 1.0, 1.0, 1.0};

GlEnable(GL_LIGHTING);

GlEnable(GL_LIGHT0);

GlLightfv(GL_LIGHT0, GL_POSITION, light0_pos);

GlLightfv(GL_LIGHT0, GL_AMBIENT, ambient0);

GlLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse0);

GlLightfv(GL_LIGHT0, GL_SPECULAR, specular0);

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

GLfloat global_ambient={0.1, 0.1, 0.1, 1.0};

GlLightModelfv(GL_LIGHT_MODEL_AMBIENT, global_ambient);

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

K= 1/(a+ b*d+ c*d^2)

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

GlLightf(GL_LIGHT0, GL_CONSTANT_ATTENATION, a);

Для преобразование точечного источника в прожектор нужно задать направление луча прожектора (GL_SPOT_DIRECTION), показатель функции распределения интенсивности (GL_SPOT_EXPONENT) и угол рассеяния луча (GL_SPOT_CUTTOF). Эти параметры устанавливаются с помощью функций glLightf() и glLightfv().

Параметры, устанавливаемые для источников света по умолчанию приведены в таблице 3.

Параметры, устанавливаемые для источников света по умолчанию

Таблица 3

Имя параметра Значение по умолчанию Содержание
GL_AMBIENT (0.0, 0.0, 0.0, 1.0) ambient RGBA intensity of light
GL_DIFFUSE (1.0, 1.0, 1.0, 1.0) diffuse RGBA intensity of light
GL_SPECULAR (1.0, 1.0, 1.0, 1.0) specular RGBA intensity of light
GL_POSITION (0.0, 0.0, 1.0, 0.0) (x, y, z, w) position of light
GL_SPOT_DIRECTION (0.0, 0.0, -1.0) (x, y, z) direction of spotlight
GL_SPOT_EXPONENT 0.0 spotlight exponent
GL_SPOT_CUTOFF 180.0 spotlight cutoff angle
GL_CONSTANT_ATTENUATION 1.0 constant attenuation factor
GL_LINEAR_ATTENUATION 0.0 linear attenuation factor
GL_QUADRATIC_ATTENUATION 0.0 quadratic attenuation factor

В OpenGL используется модель освещения Фонга, в соответствии, с которой цвет точки определяется несколькими факторами: свойствами материала и текстуры, величиной нормали в этой точке, а также положением источника света и наблюдателя. Для корректного расчета освещенности в точке надо использовать единичные нормали, однако, команды типа glScale..(), могут изменять длину нормалей. Чтобы это учитывать, используется уже упоминавшийся режим нормализации нормалей, который включается вызовом команды glEnable(GL_NORMALIZE).

OpenGL предусматривает задание трех параметров, определяющих общие законы применения модели освещения. Эти параметры передаются в функцию glLightModel() и некоторые ее модификации. Для задания глобальных параметров освещения используются команды:

void glLightModel(GLenum pname, GLenum param)

void glLightModelv(GLenum pname, const GLtype *params)

Аргумент pname определяет, какой параметр модели освещения будет настраиваться и может принимать следующие значения:

GL_LIGHT_MODEL_LOCAL_VIEWER - является ли точка наблюдения локальной или удаленной. OpenGL вычисляет зеркальные отражения с помощью "промежуточного вектора" h=s+v, описанного ранее. Истинные направления s и v, различаются для каждой вершины сетки. Если источник света является направленным, то вектор s-величина постоянная, а v все же изменяется от вершины к вершине. Скорость визуализации возрастает, если сделать и вектор v постоянным для всех вершин. По умолчанию OpenGL использует значение v=(0,0,1), при этом вектор указывает в сторону положительной оси z в координатах камеры. В то же время можно принудительно заставить графический конвейер вычислять истинное значение вектора v для каждой вершины с помощью выполнения оператора:

GlLightModel(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);

Параметр param должен быть булевским и задает положение наблюдателя. Если он равен FALSE, то направление обзора считается параллельным оси z, вне зависимости от положения в видовых координатах. Если же он равен TRUE, то наблюдатель находится в начале видовой системы координат. Это может улучшить качество освещения, но усложняет его расчет. Значение по умолчанию: FALSE.;

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

В OpenGL нет понятия "внутри" и "снаружи", он может различать только "лицевые грани" и "нелицевые грани". Грань является лицевой (front face), если ее вершины расположены в списке против часовой стрелки, в том порядке, каком их видит глаз. Можно заменить этот порядок на обратный с помощью функции glFrontFace(GL_CW),в которой обусловлено, что грань является лицевой только в том случае, если ее вершины занесены в список в порядке по часовой стрелке. Для объекта, ограничивающего некоторое пространство, все грани, которые видит глаз, являются лицевыми, и OpenGL правильно рисует и закрашивает их. Нелицевые грани также рисуются в OpenGL, но, в конце концов, они скрываются за более близкими лицевыми гранями. Можно ускорить работу процессора, если запретить OpenGL визуализацию нелицевых граней. При этом используется следующий код:

glCullFace(GL_BACK);

glEnable(GL_CULL_FACE);.

Иначе обстоит дело на Error: Reference source not found, б, где показан параллелепипед с одной удаленной гранью. Как и раньше, стрелками показан порядок, в котором вершины каждой грани пересылаются в графический конвейер. Три из видимых граней являются нелицевыми. По умолчанию OpenGL не может правильно закрасить грани. Для правильного закрашивания нелицевых граней нужно проинструктировать OpenGL с помощью оператора glLightModel (GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE). При выполнении этой команды OpenGL изменяет направления нормалей всех нелицевых граней таким образом, чтобы они указывали на наблюдателя, после чего закрашивание осуществляется корректно. Замена величины GL_TRUE на GL_FALSE отключает эту опцию. Грани, нарисованные с помощью OpenGL, не отбрасывает теней, поэтому все нелицевые грани получают от источника такой же свет, даже если между ними и источником света находится какая-нибудь другая грань;

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

GLfloat amb = {0.2, 0.3, 0.1, 1.0};

glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb);

Этот код придает источнику фонового света цвет (0.2, 0.3, 0.1). Значение по умолчанию составляет (0.2, 0.2, 0.2, 0.1), так что фоновый свет присутствует всегда, если только умышленно не изменить его. Задание фоновому источнику ненулевого значения обеспечивает видимость объектов сцены, даже если вы не активизировали ни одной функции освещения;

GL_LIGHT_MODEL_COLOR_CONTROL отделение зеркальной составляющей цвета. Для обычных расчетов освещенности фоновая, диффузная, зеркальная и эмиссионная составляющие вычисляются и просто складываются друг с другом. По умолчанию отображение текстур применяется после освещения, так что зеркальные блики могут появиться приглушенными, или текстурирование будет выглядеть по-другому. При следующем вызове – glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR) OpenGL отделяет вычисление зеркального цвета из приложения. После этого освещение генерирует два цвета для каждой вершины: первоначальный цвет, состоящий из неотраженных составляющих освещенности, и второй цвет, являющийся суммой зеркальных составляющих освещенности. При отображении текстур только первый цвет комбинируется с цветами текстуры. После выполнения операции текстурирования второй цвет добавляется к итоговой комбинации первого и текстурного компонентов цвета. Объекты, для которых выполнено освещение и текстурирование с отделением зеркального цвета, обычно более видимы и имеют более заметные зеркальные блики. Для возвращения к установкам по умолчанию необходимо сделать вызов glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SINGLE_COLOR). После этого опять первоначальный цвет будет состоять из всех составляющих цвета: рассеянной, диффузной, эмиссионной и зеркальной. Составляющие освещения не прибавляются после текстурирования.

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

Свойства материала

Для задания параметров текущего материала используются команды

void glMaterial (GLenum face, GLenum pname, GLtype param)
void glMaterialv (GLenum face, GLenum pname, GLtype *params)

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

GL_AMBIENT параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют рассеянный цвет материала (цвет материала в тени).

Значение по умолчанию: (0.2, 0.2, 0.2, 1.0).

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

Значение по умолчанию:(0.8, 0.8, 0.8, 1.0).

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

GL_SHININESS параметр params должен содержать одно целое или вещественное значение в диапазоне от 0 до 128, которое определяет степень зеркального отражения материала.

Значение по умолчанию: 0.

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

Значение по умолчанию: (0.0, 0.0, 0.0, 1.0).

GL_AMBIENT_AND_DIFFUSE эквивалентно двум вызовам команды glMaterial…() со значением pname GL_AMBIENT и GL_DIFFUSE и одинаковыми значениями params.

Из этого следует, что вызов команды glMaterial() возможен только для установки степени зеркального отражения материала. В большинстве моделей учитывается диффузный и зеркальный отраженный свет; первый определяет естественный цвет объекта, а второй - размер и форму бликов на его поверхности.

Параметр face определяет тип граней, для которых задается этот материал и может принимать значения GL_FRONT, GL_BACK или GL_FRONT_AND_BACK.

Если в сцене материалы объектов различаются лишь одним параметром, рекомендуется сначала установить нужный режим, вызвав glEnable() c параметром GL_COLOR_MATERIAL, а затем использовать команду

void glColorMaterial (GLenum face, GLenum pname)

где параметр face имеет аналогичный смысл, а параметр pname может принимать все перечисленные значения. После этого, значения выбранного с помощью pname свойства материала для конкретного объекта (или вершины) устанавливается вызовом команды glColor…(), что позволяет избежать вызовов более ресурсоемкой команды glMaterial…() и повышает эффективность программы.

Добавить в сцену источник света можно с помощью команд

void glLight (GLenum light, GLenum pname, GLfloat param)
void glLight (GLenum light, GLenum pname, GLfloat *params)

Параметр light однозначно определяет источник,и выбирается из набора специальных символических имен вида GL_LIGHTi, где i должно лежать в диапазоне от 0 до GL_MAX_LIGHT, которое не превосходит восьми.

Оставшиеся два параметра имеют аналогичный смысл, что и в команде glMaterial…(). Рассмотрим их назначение (вначале описываются параметры для первой команды, затем для второй):

GL_SPOT_EXPONENT параметр param должен содержать целое или вещественное число от 0 до 128, задающее распределение интенсивности света. Этот параметр описывает уровень сфокусированности источника света.

Значение по умолчанию: 0 (рассеянный свет).

GL_SPOT_CUTOFF параметр param должен содержать целое или вещественное число между 0 и 90 или равное 180, которое определяет максимальный угол разброса света. Значение этого параметра есть половина угла в вершине конусовидного светового потока, создаваемого источником.

Значение по умолчанию: 180 (рассеянный свет).

GL_AMBIENT параметр params должен содержать четыре целых или вещественных значения цветов RGBA, которые определяют цвет фонового освещения.

Значение по умолчанию: (0.0, 0.0, 0.0, 1.0).

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

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

Значение по умолчанию: (1.0, 1.0, 1.0, 1.0)для LIGHT0 и (0.0, 0.0, 0.0, 1.0) для остальных.

GL_POSITION параметр params должен содержать четыре целых или вещественных, которые определяют положение источника света. Если значение компоненты w равно 0.0, то источник считается бесконечно удаленным и при расчете освещенности учитывается только направление на точку (x,y,z), в противном случае считается, что источник расположен в точке (x,y,z,w).

Значение по умолчанию: (0.0, 0.0, 1.0, 0.0).

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

Значение по умолчанию: (0.0, 0.0, -1.0, 1.0).

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

Для использования освещения сначала надо установить соответствующий режим вызовом команды glEnable (GL_LIGHTNING), а затем включить нужный источник командой glEnable(GL_LIGHTn).

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

В OpenGL используется модель освещения Фонга, в соответствии с которой цвет точки определяется несколькими факторами: свойствами материала и текстуры, величиной нормали в этой точке, а также положением источника света и наблюдателя. Для корректного расчета освещенности в точке надо использовать единичные нормали, однако команды типа glScale…(), могут изменять длину нормалей. Чтобы это учитывать, используется уже упоминавшийся режим нормализации нормалей, который включается вызовом команды glEnable(GL_NORMALIZE).

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

void glLightModel (GLenum pname, GLenum param)
void glLightModelv (GLenum pname, const GLtype *params)

Аргумент pname определяет, какой параметр модели освещения будет настраиваться и может принимать следующие значения:

GL_LIGHT_MODEL_LOCAL_VIEWER параметр param должен быть булевским и задает положение наблюдателя. Если он равен FALSE, то направление обзора считается параллельным оси -z, вне зависимости от положения в видовыx координатах. Если же он равен TRUE, то наблюдатель находится в начале видовой системы координат. Это может улучшить качество освещения, но усложняет его расчет.

Значение по умолчанию: FALSE.

GL_LIGHT_MODEL_TWO_SIDE параметр param должен быть булевским и управляет режимом расчета освещенности как для лицевых, так и для обратных граней. Если он равен FALSE, то освещенность рассчитывается только для лицевых граней. Если же он равен TRUE, расчет проводится и для обратных граней. Значение по умолчанию: FALSE.

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

Значение по умолчанию:(0.2, 0.2, 0.2,1.0).

В этом уроке мы научимся создавать вершинное освещение.
Исходные файлы main.cpp, main.h, init.h взяты из урока "Начало - инициализация".

Обьявим глобальную переменную позиции света в файле main.h :

extern float g_LightPosition[ 4 ] ; // Позиция источника света

Итак, в первую очередь нам нужно инициализировать освещение.
Сделаем мы это в файле init.cpp :

// Сразу переходим к функции InitializeOpenGL().
// В конец функции, перед закрывающейся скобкой, дописываем новый код.

// Лучше всего устанавливать цвет пространства в цвет "темноты" - отсутствия освещения.
// Так как мы будем использовать белый как цвет освещения, установим цвет
// пространства в черный:

glClearColor(0 , 0 , 0 , 1 ) ;

// Ниже создадим два массива цветов. Первый, ambience - цвет "рассеянного света", когда
// на полигон не падает прямое освещение. Если мы не установим этот цвет, не освещенные полигоны
// будут совершенно черными, и мы их не увидим. Цвет освещения мы сделаем белый, а рассеянный
// свет установим в половину этого значения.
// Второй массив, diffuse, это цвет направленного света. Мы выбрали белый. Первые три
// цифры массивы - R,G,B, последняя - значение альфа. Пока что не беспокойтесь о нём.
// Для рассеянного света выберем глубоко серый цвет, для дифузного - среднее между
// белым и черным.

float ambience[ 4 ] = { 0.3f , 0.3f , 0.3f , 1.0 } ; // Цвет мирового света
float diffuse[ 4 ] = { 0.5f , 0.5f , 0.5f , 1.0 } ; // Цвет позиционного света

// Чтобы установить мировое освещене, нужно передать OpenGL наши массивы.
// OpenGL даёт нам возможность использовать несколько источников света. Общее количество
// источников зависит от переменной GL_MAX_LIGHTS. Для первого используемого источника будем
// использовать дефайн OpenGL GL_LIGHT0. После указания используемого источника передаём
// OpenGL флаг говорящий, что мы устанавливаем значение ambience, и массив ambience-цветов.
// Точно то же делаем с diffuse и GL_DIFFUSE.

// Устанавливаем цвет рассеянного цвета (без направленного света)
glLightfv( GL_LIGHT0, GL_AMBIENT, ambience ) ;
// И диффузный цвет (цвет света)
glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse ) ;

// Далее нам нужно установить позицию источника света. У нас может быть много простых
// источников в разных местах, но в этом уроке будет использоваться только один.
// Для последующих источников нужно использовать GL_LIGHT1, GL_LIGHT2, GL_LIGHT3 и т.д...
// Мы сможем изменять позицию источника клавишами "+" и "-". Позиция по умолчанию - (0,1,0,1).
// Это расположит источник прямо над центральной пирамидой. Последнее значение в массиве
// g_LightPosition сообщает OpenGL, нужен ли нам ПОЗИЦИОННЫЙ или НАПРАВЛЕННЫЙ свет.
// Если значение = 1, цвет будет позиционным, иначе он будет направлен от камеры.
// Если мы установим направленный свет, он будет освещать всё "по дороге" от нашего "глаза".
// Если же свет будет позиционным, он "повиснет" в координатах x,y,z и будет освещать
// всё вокруг себя. Это то, что нам нужно, так что установим значение в единицу.

// После инициализации источника света нам нужно включить его:
glEnable( GL_LIGHT0 ) ;

// Но недостаточно включить один источник; кроме этого нужно включить само
// освещение в OpenGL:
glEnable(GL_LIGHTING) ;

// Следующая строка позволяет закрашивать полигоны цветом при включенном освещении:
glEnable(GL_COLOR_MATERIAL) ;

Помните, если мы используем функции glColor*() вместе с освещением,
они будут проигнорированы, пока мы не включим GL_COLOR_MATERIALS.

В init.cpp мы инициализировали освещение в функции InicializeOpenGL().

Теперь используем освещение в main.cpp

// В начале файла, после включения хидеров, добавим переменные:
float g_LightPosition[ 4 ] = { 0 , 1 , 0 , 1 } ; // Позиция источника света
float g_bLight = true ; // Включен ли свет

float rotY = 0.0f ; // Вращение по Y

// Напишем функцию, создающую пирамиду:
void CreatePyramid(float x, float y, float z, int width, int height)
{

GlBegin(GL_TRIANGLES) ;

// Ниже мы добавим что-то новое: НОРМАЛИ. Значение, насколько сильно нужно
// осветить конкретный полигон, OpenGL рассчитывает из его нормалей. Что такоей нормаль?
// Нормаль - это направление полигона. Вы заметите, что мы присваиваем заднему полигону
// нормаль (0,1,-1). Это значит, что полигон направлен в обратную сторону по оси Z (внутрь
// экрана). Запомните, нормали - это не координаты, а только направления.
// Функция glNormal3f() позволяют нам указать нормаль для вершин, переданных за ней.
// Сейчас мы напишем нормали вручную, но вы можете вернутся к урокам камеры, где есть
// ничто иное, как функция для рассчета нормалей.

// Задняя сторона
glNormal3f(0 , 1 , - 1 ) ; // Полигон направлен назад и вверх

// Передняя сторона
glNormal3f(0 , 1 , 1 ) ;
glColor3ub(255 , 0 , 0 ) ; glVertex3f(x, y + height, z) ;

// Левая сторона
glNormal3f(- 1 , 1 , 0 ) ;
glColor3ub(255 , 0 , 0 ) ; glVertex3f(x, y + height, z) ;
glColor3ub(255 , 0 , 255 ) ; glVertex3f(x - width, y - height, z + width) ;
glColor3ub(0 , 255 , 255 ) ; glVertex3f(x - width, y - height, z - width) ;

// Передняя правая сторона
glNormal3f(1 , 1 , 0 ) ;
glColor3ub(255 , 0 , 0 ) ; glVertex3f(x, y + height, z) ;
glColor3ub(255 , 0 , 255 ) ; glVertex3f(x + width, y - height, z - width) ;
glColor3ub(0 , 255 , 255 ) ; glVertex3f(x + width, y - height, z + width) ;

GlEnd() ;

// Теперь отрендерим дно пирамиды

GlBegin(GL_QUADS) ;

// Эти вершины образуют дно пирамиды
glNormal3f(0 , - 1 , 0 ) ;
glColor3ub(0 , 0 , 255 ) ; glVertex3f(x - width, y - height, z + width) ;
glColor3ub(0 , 0 , 255 ) ; glVertex3f(x + width, y - height, z + width) ;
glColor3ub(0 , 0 , 255 ) ; glVertex3f(x + width, y - height, z - width) ;
glColor3ub(0 , 0 , 255 ) ; glVertex3f(x - width, y - height, z - width) ;
glEnd() ;
}

//////////////////////////////////////////////////////////////////////////
// Внесём изменения в обработку клавиш, блок WM_KEYDOWN функции WinProc():

case WM_KEYDOWN:
switch (wParam)
{
// Ниже мы позволяем пользователю увеличивать и уменьшать позицию Y источника света
// клавишами "+" и "-". После изменения позиции необходимо сообщить об этом
// OpenGL вызовом glLightfv(). Мы передаём номер источника (GL_LIGHT0) и флаг
// GL_POSITION, плюс саму позицию.
// Нажатием "L" свет включается и выключается.

case VK_ESCAPE:
PostQuitMessage(0 ) ;
break ;

case VK_ADD: // Если нажата ПЛЮС
g_LightPosition[ 1 ] += 0.1f ; // Увеличиваем значение Y
// Убедимся, что не превысили 5
if (g_LightPosition[ 1 ] > 5 ) g_LightPosition[ 1 ] = 5 ;
break ;

case VK_SUBTRACT: // Если МИНУС
g_LightPosition[ 1 ] -= 0.1f ; // Уменьшим значение Y
// Убедиммся, что оно не меньше -5
if (g_LightPosition[ 1 ] < - 5 ) g_LightPosition[ 1 ] = - 5 ;
break ;

case "L" :

G_bLight = ! g_bLight; // Выключим свет

if (g_bLight) // Включим свет
glEnable(GL_LIGHTING) ;
else
glDisable(GL_LIGHTING) ; // Выключим свет
break ;
}
break ;

////////////////////////////////////////////////////////////////////////

// И, наконец, изменим функцию RenderScene():
void RenderScene()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT) ;
glLoadIdentity() ;

// Обновим позицию света ДО вызова glLookAt(), чтобы свет обновился
// корректно. Опустите функцию вниз, чтобы увидеть, что иначе случится.
// Если источник не двигается, вызывать эту функцию заново не нужно.
glLightfv( GL_LIGHT0, GL_POSITION, g_LightPosition ) ;

GluLookAt(0 , 0 , 6 , 0 , 0 , 0 , 0 , 1 , 0 ) ;

GlRotatef(rotY, 0.0f , 1.0f , 0.0f ) ; // Вращаем пирамиду вокруг оси Y

// Это создаёт 3д пирамиду в центре (0,0,0)
CreatePyramid(0 , 0 , 0 , 1 , 1 ) ;

// Создадим пирамиды вокруг:

CreatePyramid(3 , 0 , - 3 , 1 , 1 ) ;
CreatePyramid(- 3 , 0 , - 3 , 1 , 1 ) ;
CreatePyramid(0 , 0 , 5 , 1 , 1 ) ;

// Увеличиваем впращение.

RotY += 0.6f ;

SwapBuffers(g_hDC) ;
}

Итак, вот шаги, которые мы проделали:

1) Установили ambience-цвет мира:
glLightfv(GL_LIGHT0, GL_AMBIENT, ambienceArray);

2) Установили diffuse-цвет источника света:
glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseArray);

3) Установили позицию источника света:
glLightfv(GL_LIGHT0, GL_POSITION, positionArray);

4) Включили источник света:
glEnable(GL_LIGHT0);

5) И освещение в OpenGL:
glEnable(GL_LIGHTING);

Понравилась статья? Поделиться с друзьями: