Свет и материалы в OpenGL
Группы атрибутов
Обратите внимание, что в функции RenderScene() мы применили механизм сохранения настроек в стеке атрибутов с последующим их восстановлением, чтобы изменения настроек в одном упражнении не сбивали настройки для выполнения другого упражнения нашего приложения. Для сохранения атрибутов используются функции
void glPushAttrib(GLbitfield mask); void glPopAttrib();
Атрибуты сохраняются группами, определяемыми значением аргумента mask. Эти значения приведены ниже в таблице. Минимальная глубина стека равна 16. Группы атрибутов можно объединять поразрядной логической операцией OR (|), что позволяет сохранять в стеке практически любую комбинацию атрибутов.
Функция glPopAttrib() восстанавливает значения параметров состояния, сохраненных последним вызовом функции glPushAttrib(). Каждый бит значения маски соответствует определенному параметру состояния OpenGL. Вся маска указывает на целый набор родственных атрибутов, подлежащих сохранению.
Например, используемый нами параметр GL_LIGHTING_BIT объединяет все параметры состояния OpenGL, связанные с освещением, что включает в себя цвет текущего материала, фоновый, рассеянный и отраженный цвет, самосвечение (излучение), список активных источников света и направление распространения света. При вызове функции glPopAttrib() все эти настройки восстанавливаются.
Второй способ задания нескольких материалов (ColorSphere)
Другой, упрощенный, но более быстрый способ задания свойств многих материалов, называется согласованием цветов. Он заключается в использовании для изменения свойств материала функции glColorMaterial(), которая имеет следующий синтаксис:
void glColorMaterial(GLenum face, GLenum mode);
Эта функция заставляет использовать для рисования освещенных объектов текущий цвет рисования, установленный функцией glColor*() для рисования неосвещенных объектов. При изменении текущего цвета (командой glColor*() ) указанные свойства материала также незамедлительно меняются.
Аргумент face определяет, к какой стороне объекта применять задаваемое свойство материала, и может принимать значения: GL_FRONT, GL_BACK или GL_FRONT_AND_BACK (по умолчанию). Аргумент mode выбирается из списка перечислений: GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_AMBIENT_AND_DIFFUSE (по умолчанию) или GL_EMISSION. В каждый момент времени активным является только один из режимов. Функция glColorMaterial() не работает при использовании освещения в режиме индексирования цветов, а только в режиме RGBA.
Для того, чтобы включить в OpenGL поддержку режима согласования цветов, нужно выполнить команду glEnable(GL_COLOR_MATERIAL). Это позволяет изменять текущий цвет рисования объекта командой glColor*(), но не отменяет возможности одновременного использования команды glMaterial*() с нужными параметрами для настройки нужных свойств материала иным способом. После того, как потребность в использовании режима согласования цветов отпадет, нужно обязательно отключить его командой glDisable(GL_COLOR_MATERIAL). Это позволит избежать ошибочных изменений свойств материала, а также снизить временные затраты на выполнение программы.
Функцию glColorMaterial() выгодно использовать в том случае, когда для большинства вершин на сцене требуется подстраивать значение лишь одного параметра материала. Если изменяемых параметров несколько, то нужно применять функцию glMaterial*().
Проиллюстрируем сказанное на примере моделирования сферы с изменяющимся цветом материала. Для этого воспользуемся кодом файла LightSphere.h рисования освещенной сферы из ранее выполненного упражнения.
-
Добавьте к приложению новый заголовочный файл с именем ColorSphere.h.
-
Скопируйте в новый файл содержимое из файла LightSphere.h, модифицируйте его, чтобы код окончательно выглядел так
// ColorSphere.h // Упражнение 5: "5) Способ согласования цветов" //********************************************************** // Прототипы void SetLightColorSphere(); void RenderSceneColorSphere(); void ChangeSizeColorSphere(int, int); // Глобальный массив свойств материала GLfloat diffuseMaterial[] = {0.5, 0.5, 0.5, 1.0}; GLfloat mat_shininess[] = {25.0}; // Размер блика void SetLightColorSphere() { GLfloat mat_specular[] = {1.0, 1.0, 1.0, 1.0};// Цвет блика GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};// Расположение источника glClearColor(0.0, 0.0, 0.0, 1.0); glShadeModel(GL_SMOOTH); glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuseMaterial);// Начальная установка диффузного материала glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_DEPTH_TEST); // Режим согласования цветов назначаем для рассеянного glColorMaterial(GL_FRONT, GL_DIFFUSE); // Включаем поддержку согласования цветов glEnable(GL_COLOR_MATERIAL); } //********************************************************** void RenderSceneColorSphere() { // Сбрасываем буфер цвета и буфер глубины glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix();// Сохранить матрицу преобразования модели glRotatef(xRot, 1.0f, 0.0f, 0.0f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); glRotatef(zRot, 0.0f, 0.0f, 1.0f); // Для анимации xRot += 1.0f; yRot += 1.0f; zRot += 1.0f; xRot = (GLfloat)((const int)xRot % 360); yRot = (GLfloat)((const int)yRot % 360); zRot = (GLfloat)((const int)zRot % 360); // Рисуем сферу glutSolidSphere(1.0, 20, 16); glPopMatrix();// Восстановить матрицу преобразования модели // Переключить буфер glutSwapBuffers(); // Прокачка сообщений glFlush(); } //********************************************************** void ChangeSizeColorSphere(int width, int height) { // Индивидуальные настройки освещения SetLightColorSphere(); // Предотвращаем деление на нуль if(height == 0) height = 1; // Устанавливаем поле просмотра с размерами окна glViewport(0, 0, width, height); // Устанавливает матрицу преобразования в режим проецирования glMatrixMode(GL_PROJECTION); glLoadIdentity(); // Устанавливаем размеры ортогонального отсекающего объема GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;// Для соблюдения пропорций if (width <= height) glOrtho (-1.5, 1.5, -1.5 / aspectRatio, 1.5 / aspectRatio, -10.0, 10.0); else glOrtho (-1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -10, 10); // Восстановливает матрицу преобразования в исходный режим вида glMatrixMode(GL_MODELVIEW); glLoadIdentity(); }Листинг 23.16. Код файла ColorSphere.h
-
Добавьте к файлу LightAndMaterial.cpp возможность запуска пятого упражнения ColorSphere
// LightAndMaterial.cpp : Defines the entry point for the console application. // //********************************************************** // Подключение стандартного файла с библиотекой OpenGL #include "stdafx.h" //********************************************************** // Прототипы функций void ExecuteMenu(int); // Контекстное меню первого уровня void TimerFunc(int); // Обработчик события таймера void SpecialKeys(int, int, int); // Обработка нажатия клавиш void RenderScene(void); // Функция рендеринга void ChangeSize(int, int); // Функция установки отсекающего объема // Глобальная переменная выбранного варианта основного меню int choice = 1; // Глобальные переменные для создания вращения // в градусах GLfloat xRot = 0.0f; GLfloat yRot = 0.0f; GLfloat zRot = 0.0f; GLint w, h; // Ширина и высота экрана //********************************************************** // Подключение файлов с упражнениями #include "ColorCube.h" // Упражнение 1: "1) Куб цвета" #include "Jet.h" // Упражнение 2: "2) Самолет без освещения" #include "LightSphere.h" // Упражнение 3: "3) Рисование освещенной сферы" #include "MultiMaterial.h" // Упражнение 4: "4) Рисование с разными материалами" #include "ColorSphere.h" // Упражнение 5: "5) Способ согласования цветов" //********************************************************** // Функция обратного вызова обработки выбора пользователя void ExecuteMenu(int choice) { // Сбрасываем углы вращения прежнего варианта xRot = yRot = zRot = 0; // Выключаем освещение glDisable(GL_LIGHTING); // Выключаем режим согласования цветов glDisable(GL_COLOR_MATERIAL); // Запоминаем выбор в глобальную переменную ::choice = choice; switch(::choice) { case 1: ChangeSizeColorCube(w, h); break; case 2: ChangeSizeJet(w, h); break; case 3: ChangeSizeLightSphere(w, h); break; case 4: ChangeSizeMultiMaterial(w, h); break; case 5: ChangeSizeColorSphere(w, h); break; } // Вызвать принудительно визуализацию glutPostRedisplay(); } //********************************************************** // Функция обратного вызова для рисования сцены void RenderScene(void) { // Сохранить прежние настройки OpenGL в стеке атрибутов glPushAttrib(GL_LIGHTING_BIT); switch(::choice) { case 1: RenderSceneColorCube(); break; case 2: RenderSceneJet(); break; case 3: RenderSceneLightSphere(); break; case 4: RenderSceneMultiMaterial(); break; case 5: RenderSceneColorSphere(); break; } // Восстановить прежние настройки OpenGL из стека атрибутов glPopAttrib(); } //********************************************************** // Вызывается библиотекой GLUT при изменении размеров окна void ChangeSize(int width, int height) { w = width; h = height; switch(::choice) { case 1: ChangeSizeColorCube(width, height); break; case 2: ChangeSizeJet(width, height); break; case 3: ChangeSizeLightSphere(width, height); break; case 4: ChangeSizeMultiMaterial(width, height); break; case 5: ChangeSizeColorSphere(width, height); break; } } //********************************************************** // Обработчик события таймера void TimerFunc(int value) { glutPostRedisplay(); // Перерисовка сцены glutTimerFunc(30, TimerFunc, 1); // Заряжаем новый таймер } //********************************************************** // Управление с клавиатуры стрелками // для задания новых значений матрицы поворота void SpecialKeys(int key, int x, int y) { if(key == GLUT_KEY_UP) // Стрелка вверх xRot -= 5.0f; if(key == GLUT_KEY_DOWN)// Стрелка вниз xRot += 5.0f; if(key == GLUT_KEY_LEFT)// Стрелка влево yRot -= 5.0f; if(key == GLUT_KEY_RIGHT)// Стрелка вправо yRot += 5.0f; xRot = (GLfloat)((const int)xRot % 360); yRot = (GLfloat)((const int)yRot % 360); // Для упражнения 5 смены материала if(key == GLUT_KEY_F1) // Меняем красный { diffuseMaterial[0] += 0.1; *diffuseMaterial = *diffuseMaterial > 1.0 ? 0.0 : *diffuseMaterial; glColor4fv(diffuseMaterial); } if(key == GLUT_KEY_F2) // Меняем зеленый { diffuseMaterial[1] += 0.1; *(diffuseMaterial + 1) = diffuseMaterial[1] > 1.0 ? 0.0 : diffuseMaterial[1]; glColor4fv(diffuseMaterial); } if(key == GLUT_KEY_F3) // Меняем синий { diffuseMaterial[2] += 0.1; *(diffuseMaterial + 2) = diffuseMaterial[2] > 1.0 ? 0.0 : diffuseMaterial[2]; glColor4fv(diffuseMaterial); } // Вызвать принудительно визуализацию с помощью RenderScene() glutPostRedisplay(); } //********************************************************** void main(int argc, char* argv[]) { glutInit(&argc, argv); // Двойная буферизация, цветовая модель RGB, буфер глубины glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(300, 300); // Начальные размеры окна glutCreateWindow("Свет и материалы"); // Заголовок окна glutDisplayFunc(RenderScene); // Обновление сцены при разрушении окна glutReshapeFunc(ChangeSize); // При изменении размера окна glutTimerFunc(100, TimerFunc, 1); // Создали таймер glutSpecialFunc(SpecialKeys); // Для управления с клавиатуры // Создание меню и добавление опций выбора glutCreateMenu(ExecuteMenu); glutAddMenuEntry("1) Куб цвета", 1); glutAddMenuEntry("2) Самолет без освещения", 2); glutAddMenuEntry("3) Рисование освещенной сферы", 3); glutAddMenuEntry("4) Рисование с разными материалами", 4); glutAddMenuEntry("5) Способ согласования цветов (F1, F2, F3)", 5); glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем // Конец создания меню glutMainLoop(); // Цикл сообщений графического окна }Листинг 23.17. Измененный код файла LightAndMaterial.cpp для запуска 5 упражнения
Обратите внимание, что мы ввели возможность изменения рассеянного цвета материала прямо по ходу выполнения приложения с помощью функциональных клавиш: F1 - Red, F2 - Green, F3 - Blue.
-
Запустите приложение и выберите пятое упражнение. Если менять цвета закрепленными функциональными клавишами, то будет меняться цвет рассеянного отражения. Картина будет примерно такой
-
Попытайтесь разобраться с кодом
Здесь требуется внесение небольших изменений в третье упражнение в связи с вновь открывшимися обстоятельствами. Если вы после упражнения 5 сразу запустите упражнение 3, то диффузный цвет сферы в упражнении 3 будет таким же, как для упражнения 5. Это происходит потому, что мы в упражнении 3 не задаем явно цвет диффузной составляющей материала, поэтому этот цвет имеет текущие настройки, измененные в упражнении 5.
Эх!, подпреснуть бы вас, дорогие граждане студенты, пусть бы сами искали исправление, да ладно! - главное, чтобы внимательно разобрались с кодом и все бы кругом по-обсмыслили (конечно, - по желанию).
-
Добавьте в функцию SetLight() файла LightSphere.h только одну строку кода, явно определяющую свойство диффузной составляющей отражения.
void SetLight() { GLfloat mat_specular[] = {1.0, 1.0, 1.0, 1.0};// Цвет блика GLfloat mat_shininess[] = {50.0}; // Размер блика (обратная пропорция) GLfloat light_position[] = {1.0, 1.0, 1.0, 0.0};// Расположение источника GLfloat white_light[] = {1.0, 1.0, 1.0, 1.0};// Цвет и интенсивность // освещения, генерируемого источником GLfloat lmodel_ambient[] = {0.5, 0.5, 0.5, 1.0};// Параметры фонового освещения glClearColor(0.0, 0.0, 0.0, 1.0); glShadeModel(GL_SMOOTH); glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular); glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess); glMaterialfv(GL_FRONT, GL_DIFFUSE, lmodel_ambient); glLightfv(GL_LIGHT0, GL_POSITION, light_position); glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light); glLightfv(GL_LIGHT0, GL_SPECULAR, white_light); glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glEnable(GL_DEPTH_TEST); }Листинг 23.18. Добавление явного значения свойства материала диффузного отражения в LightSphere.h
-
Попереключайтесь с третьего упражнения на пятое и наоборот. Все восстанавливается индивидуально для каждого упражнения.
И еще одно замечание. Попробуйте, для сравнения, выключить в любом из упражнений, начиная со второго, в функции SetLight*() освещение сцены, для чего закомментрируйте строку кода
glEnable(GL_LIGHTING);
Мы сразу же получим плоский (постный) цвет без всяких эффектов освещения, что делает сцену совершенно убогой. Например, сравните два рисунка - наверное есть за что бороться!