Создание 3D игр на C++
Часть1. Урок 6
Создаем трехмерную сцену. Шаг 2 - Освещение
Introduction
Добавление освещения на сцену состоит из двух этапов: задание, собственно, освещения (размещение ламп), и установка материала, из которого сделаны объекты на сцене. Разные характеристики материала объекта заставляют по разному его свет отражать. Рассмотрим оба этапа:
Этап 1. Размещение ламп
Для включения освещения используется функция:
glEnable (GL_LIGHTING);
После чего идет настройка общего освещения. Для настройки имеется много параметров, мы не будем на них останавливаться, рассмотрим лишь один:
GLfloat _mainlight[] = {0.3, 0.3, 0.3, 1.0};
glLightModelfv (GL_LIGHT_MODEL_AMBIENT, _mainlight);
Где в массиве _maillight (название можно дать любое) первые три элемента говорят о RGB компонентах цвета общего освещения сцены, а последний лучше оставить единицей. При этом цвет общего освещения сцены рекомендуется делать тусклым (маленькие значения RGB), и желательно в серых тонах (R=G=B). Хотя, это полностью на вашей фантазии.
OpenGL дает нам возможность размещать на сцене одновременно до 6-ти ламп. Некоторые реализации OpenGL позволяют размещать больше, но рекомендуется использовать только первые шесть.
Для включения каждой лампы используется функция:
glEnable (GL_LIGHT#)
Где # - это номер лампы, например
glEnable (GL_LIGHT0)
После включения лампы ее надо настроить. Каждая лампа имеет следующие характеристики:
1. Позиция (POSITION).
Позиция говорит о местоположении лампы. Более близкие к лампе объекты будут больше освещены, более удаленные - меньше.
Задается позиция следующим образом:
GLfloat _lposition[] = {0.0, 0.0, 0.0, 1.0};
glLightfv (GL_LIGHT0, GL_POSITION, _lposition);
Где в массиве _lposition (назвать, разумеется, можете как угодно) первые три элемента обозначают соответсвенно x, y и z коориднаты лампы, а последний лучше оставить единицу. Насколько мне известно, он обозначает, насколько надо домножить каждую координату, но так ли это, никогда не проверял.
Вместо GL_LIGHT0 пишете нужную вам лампу. Если изменяете нулевую, то менять ничего не надо.
2. Интенсивность рассеянного света (AMBIENT).
Довольно малозначительный параметр. Говорит об интенсивности рассеянного света, который данный источник добавляет ко всей сцене, вне зависимости от удаления от источника (так я вычитал в одном интернет ресурсе. Но на практике чем дальше был на сцене объект, тем меньше на него действовала эта самая интенсивность рассеянного света).
Задается следующим образом:
GLfloat _lambient[] = {0.5, 0.5, 0.5, 1.0};
glLightfv (GL_LIGHT0, GL_AMBIENT, _lambient);
Где в массив _lambient (повторюсь: название - это абсолютно ваше дело) помещаются три элемнта - RGB составляющие цвета рассеянного света. Четвертый элемент - это альфа-компонент цвета, его прозрачность. Но какая может быть прозрачность в случае с освещением, для меня загадка, так что лучше оставить единицу.
Вместо GL_LIGHT0 пишете нужную вам лампу. Если изменяете нулевую, то менять ничего не надо.
3. "Цвет света" (DIFFUSE)
Основной параметр, говорит о цвете диффузного света, который источник добавляет к сцене. Просто говоря, этот параметр вы будете задавать желтым для прожектора, белым для лампы дневного освещения, красным или зеленым для цветомузыки итд.
Задается он так:
GLfloat _ldiffuse[] = {1.0, 1.0, 1.0, 1.0};
glLightfv (GL_LIGHT0, GL_DIFFUSE, _ldiffuse);
Где в массив _ldiffuse помещаются три элемнта - RGB составляющие "цвета света". Четвертый элемент, как и в случае и рассеянным светом, лучше оставить равным единице.
Вместо GL_LIGHT0 пишете нужную вам лампу. Если изменяете нулевую, то менять ничего не надо.
4. Цвет бликов (SPECULAR)
Этот параметр говорит о том, какого цвета будет самая освещенная часть объекта. В реальной жизни это чаще всего белый цвет. Но опытные в 3D программировании люди советуют для максимальной реалистичности устанавливать этот цвет равным тому, что вы задали для DIFFUSE ("цвета света").
Задается цвет бликов так:
GLfloat _lspecular[] = {1.0, 1.0, 1.0, 1.0};
glLightfv (GL_LIGHT0, GL_SPECULAR, _lspecular);
Параметры те же, что и в двух предыдущих случаях.
Вместо GL_LIGHT0 пишете нужную вам лампу. Если изменяете нулевую, то менять ничего не надо.
Задав эти четыре параметра вы получите свет, который светит во все стороны. Если вы хотите получить эффект прожектора, вы можете воспользоваться еще тремя параметрами. Я расскажу о них вскользь. Задаются они так же, как и в предыдущих случаях, то есть создается массив, содержащий необходимые параметры, потом вызывается функция glLightfv, которой передается номер лампы, параметр для изменения и массив.
Итак, параметры прожектора:
5. GL_SPOT_DIRECTION - в массиве три числа: x, y и z (наверное правильнее i, j и k) составляющие вектора направления прожектора.
При задании двух следующих параметров в массиве должен быть только один элемент.
6. GL_SPOT_EXPONENT - экспоненциальное распределение светового пучка. Просто говоря, если параметр равен 0, то весь свет прожектора одинаково яркий, чем больше этот параметр, тем тускнее становится свет по краям прожектора. Свет в центре прожектора всегда одинаков. Элемент массива должен содержать значение параметра.
7. GL_SPOT_CUTOFF - угол прожектора (наибольший угол между двумя образующими конуса-прожектора). Элемент массива должен содержать значение угла в градусах.
Кроме того, имеется такое понятие, как затухание света, то есть изменение яркости света по мере удаления от источника. Вычисляется яркость света так:
1 / ( kc + kl * d + kq * d * d )
Где:
d - удаление от источника света
А три числа: kc, kd и kq задаются тремя параметрами:
8. kc - GL_CONSTANT_ATTENUATION
9. kl - GL_LINEAR_ATTENUATION
10. kq - GL_QUADRATIC_ATTENUATION
Задаются эти параметры как и раньше. Массив должен содержать всего один элемент - значение параметра.
Вот, с этим немного разобрались. Теперь к теме материалов.
Этап 2. Материал.
Задание материала схоже с установкой ламп и в то же премя схоже с установкой цвета (glColor). Материал можно задать однажды, при запуске программы (если вам предстоит рисовать все объекты с одними и теми же параметрами материала), или перед рисованием каждого объекта отедльно. Материал определяется несколькими параметрами. Задаются они следующим образом: сначала вы создаете массив, содержащий все составляющие параметра (чаще всего их три, за одним только исключнием, где он будет один), а потом вызываете функцию:
glMaterialfv(GL_FRONT_AND_BACK, <параметр>, <массив>);
GL_FRONT_AND_BACK говорит о том, что материал будет задан и для передней и для задней поверхности. В подробности передних и задних поверхностей вникать не буду. Вам наврядли пригодится работать с ними по отдельности.
Итак, параметры:
1. GL_AMBIENT - этот параметр смешивается с аналогичным параметром от каждой лампы и таким образом получается значение действия на объект рассеянного освещения. В массиве - четыре числа: RGB составляющие цвета и последний - единица.
2. GL_DIFFUSE - как и в случае с GL_AMBIENT, этот параметр смешивается с аналогичным параметром от каждой лампы и таким образом получается значение цвета освещенного объекта. В массиве - четыре числа: RGB составляющие цвета и последний - единица.
3. GL_SPECULAR - зеркальный компонент цвета материала, смешаваясь с аналогичными параметрами источников освещения, дает цвет самой яркоосвещенной части объекта. В массиве - четыре числа: RGB составляющие цвета и последний - единица.
4. GL_SHININESS - эффект воздействия зеркального компонента света. В массиве один параметр: При 0.0 GL_SPECULAR полностью игнорируется, и по мере возрастания растет и воздейтсвие GL_SPECULAR.
5. GL_EMISSION - игнорирует все источники освещения и устанавливает цвет объекта в цвет, заданный в массиве четырьмя элементами: первые три - RGB составляющие цвета и последний - единица.
Кроме того, если вы хотите задать одинаковые параметры AMBIENT и DIFFUSE, то можете передать параметр
GL_AMBIENT_AND_DIFFUSE
Который установит сразу оба параметра.
Если вы хотите, чтобы команда glColor автоматически устанавливала цвет материала (параметры ambient и diffuse), то вызовите при инициализации такую команду:
glEnable (GL_COLOR_MATERIAL);
glColorMaterial (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
Обязательно в таком порядке, но не обязательно подряд.
Этап 3. Вектора нормали
Это самый муторый этап. Перед рисованием каждой поверхности вам предстоит задать ее вектор нормали, то есть вектор, перпендикулярный этой поверхности (задавать вектор нормали можно для каждой вершины. Это дает более красивый эффект). Этот вектор влияет на освещение поверхности. Например, для квадрата с вершинами (-1, -1, 0) (1, -1, 0) (1, 1, 1) (-1, 1, 0) вектором нормали будет либо (0, 0, 1), либо (0, 0, -1), в зависимости от того, какая из сторон квадрата является лицевой.
При этом, вектор нормали должен быть строго единичным. С векторами (0, 0, 1) и подобными это не составляет проблемм, а вот когда вектор под углом, например, 15 градусов, вычислить единичный вектор порой бывает проблематично. Поэтому при инициализации лучше сразу напишите
glEnable (GL_NORMALIZE);
После этого вы можете писать вектор нормали любой длины, и он будет автоматически нормализован (приведен к единичному вектору).
Задается вектор нормали командой
glNormal3f (i, j, k);
Где i, j и k - это координаты вектора.
Пример.
В качестве примера мы доработаем нашу программу, сделанную на прошлом уроке. В конструкторе, после строки
glEnable (GL_DEPTH_TEST);
Вставьте:
glEnable (GL_NORMALIZE);
// Настраиваем освещение
glEnable (GL_COLOR_MATERIAL);
glEnable (GL_LIGHTING);
glEnable (GL_LIGHT0);
// Константы
GLfloat ____white[] = {1.0, 1.0, 1.0, 1.0};
GLfloat ____black[] = {0.0, 0.0, 0.0, 1.0};
GLfloat Ambient[] = {0.5, 0.5, 0.5, 1.0};
GLfloat Shininess[] = {40.0};
GLfloat _mainlight[] = {0.3, 0.3, 0.3, 1.0};
GLfloat _lposition[] = {0.0, -3.0, 3.0, 1.0};
GLfloat _lambient[] = {0.5, 0.5, 0.5, 1.0};
GLfloat _ldiffuse[] = {1.0, 1.0, 0.8, 1.0}; // слегка желтоватое освещение
glLightModelfv (GL_LIGHT_MODEL_AMBIENT, _mainlight);
glLightfv (GL_LIGHT0, GL_POSITION, _lposition);
glLightfv (GL_LIGHT0, GL_AMBIENT, _lambient);
glLightfv (GL_LIGHT0, GL_DIFFUSE, _ldiffuse);
glLightfv (GL_LIGHT0, GL_SPECULAR, ____black);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, ____white);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, ____white);
glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, Ambient);
glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, Shininess);
glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, ____black);
glColorMaterial (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
А рисование объектов придется дополнять векторами нормалей:
if (IsWire) glBegin (GL_LINE_STRIP);
else glBegin (GL_QUADS);
{ // Нижняя стенка
glNormal3f (0, 0, -1);
glColor3f (1, 0, 0);
glVertex3f (-1, -1, -1);
glColor3f (0, 1, 0);
glVertex3f (-1, 1, -1);
glColor3f (0, 0, 1);
glVertex3f (1, 1, -1);
glColor3f (1, 1, 0);
glVertex3f (1, -1, -1);
}
glEnd ();
if (IsWire) glBegin (GL_LINE_STRIP);
else glBegin (GL_QUADS);
{ // Верхняя стенка
glNormal3f (0, 0, 1);
glColor3f (1, 0, 0);
glVertex3f (-1, -1, 1);
glColor3f (0, 1, 0);
glVertex3f (-1, 1, 1);
glColor3f (0, 0, 1);
glVertex3f (1, 1, 1);
glColor3f (1, 1, 0);
glVertex3f (1, -1, 1);
}
glEnd ();
if (IsWire) glBegin (GL_LINE_STRIP);
else glBegin (GL_QUADS);
{ // Правая стенка
glNormal3f (1, 0, 0);
glColor3f (1, 0, 0);
glVertex3f (1, -1, -1);
glColor3f (0, 1, 0);
glVertex3f (1, 1, -1);
glColor3f (0, 0, 1);
glVertex3f (1, 1, 1);
glColor3f (1, 1, 0);
glVertex3f (1, -1, 1);
}
glEnd ();
if (IsWire) glBegin (GL_LINE_STRIP);
else glBegin (GL_QUADS);
{ // Левая стенка
glNormal3f (-1, 0, 0);
glColor3f (1, 0, 0);
glVertex3f (-1, -1, -1);
glColor3f (0, 1, 0);
glVertex3f (-1, 1, -1);
glColor3f (0, 0, 1);
glVertex3f (-1, 1, 1);
glColor3f (1, 1, 0);
glVertex3f (-1, -1, 1);
}
glEnd ();
if (IsWire) glBegin (GL_LINE_STRIP);
else glBegin (GL_QUADS);
{ // Передняя стенка
glNormal3f (0, 1, 0);
glColor3f (1, 0, 0);
glVertex3f (-1, 1, -1);
glColor3f (0, 1, 0);
glVertex3f (1, 1, -1);
glColor3f (0, 0, 1);
glVertex3f (1, 1, 1);
glColor3f (1, 1, 0);
glVertex3f (-1, 1, 1);
}
glEnd ();
if (IsWire) glBegin (GL_LINE_STRIP);
else glBegin (GL_QUADS);
{ // Задяя стенка
glNormal3f (0, -1, 0);
glColor3f (1, 0, 0);
glVertex3f (-1, -1, -1);
glColor3f (0, 1, 0);
glVertex3f (1, -1, -1);
glColor3f (0, 0, 1);
glVertex3f (1, -1, 1);
glColor3f (1, 1, 0);
glVertex3f (-1, -1, 1);
}
glEnd ();
Все остальное остается без изменений.
Теперь запускайте программу и любуйтесь! Все должно работать. Если что-то не работает, вот готовый код.
light.zip