Опубликован: 13.07.2010 | Уровень: специалист | Доступ: платный
Самостоятельная работа 22:

Свет и материалы в OpenGL

Кристалл и прожектор (CrystalAndSpot)

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


Фактически при создании прожекторных эффектов допускаются углы от 0 до 90 градусов. Прожектор излучает конус света, а объекты вне этого конуса не освещаются.

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

  • Добавьте к проекту новый заголовочный файл с именем CrystalAndSpot.h


  • Заполните этот файл следующим кодом для упражнения 9
// CrystalAndSpot.h
// Упражнение 9: "9) Рисование кристалла и прожектора"
  
//**********************************************************
// Прототипы
void CrystalAndSpot();
void SetLightCrystalAndSpot();
void RenderSceneCrystalAndSpot();
void ChangeSizeCrystalAndSpot(int, int);
  
//**********************************************************
// Настройки источника света
GLfloat   light_positionCrystalAndSpot[] = { 0.0f, 0.0f, 75.0f, 1.0f };
GLfloat  specular[] = { 1.0f, 1.0f, 1.0f, 1.0f};
GLfloat  ambientLight[] = { 0.5f, 0.5f, 0.5f, 1.0f};
GLfloat  spotDir[] = { 0.0f, 0.0f, -1.0f };
  
//**********************************************************
// Флаги для спецификаций
#define MODE_FLAT 1
#define MODE_SMOOTH  2
#define MODE_VERYLOW 3
#define MODE_MEDIUM  4
#define MODE_VERYHIGH 5
  
int iShade = MODE_FLAT;    // Модель затенения
int iTess = MODE_VERYLOW;  // Тесселяция
  
//**********************************************************
// Рисуем сцену
void CrystalAndSpot()
{
  if(iShade == MODE_FLAT)
    glShadeModel(GL_FLAT);
  else // iShade == MODE_SMOOTH;
    glShadeModel(GL_SMOOTH);
  
  // Сохраняем в стеке матрицу преобразований
  glPushMatrix();  
  
    // Поворот системы координат
    glRotatef(yRot, 0.0f, 1.0f, 0.0f);
    glRotatef(xRot, 1.0f, 0.0f, 0.0f);
  
    // Ориентируем прожектор
    glLightfv(GL_LIGHT0, GL_POSITION, light_positionCrystalAndSpot);
    glLightfv(GL_LIGHT0, GL_SPOT_DIRECTION, spotDir);
  
    // Устанавливаем цвет рисования конуса прожектора красный
    glColor3ub(255, 0, 0);  
  
    // Перемещаем точку рисования конуса в позицию источника света
    glTranslatef(light_positionCrystalAndSpot[0], light_positionCrystalAndSpot[1], light_positionCrystalAndSpot[2]);
    glutSolidCone(6.0f, 10.0f, 15, 15);  // Рисуем конус
  
    // Сохраняем атрибуты освещения в стеке атрибутов
    glPushAttrib(GL_LIGHTING_BIT);
  
      // Выключаем временно освещение, чтобы нарисовать желтый фонарик в конусе
      glDisable(GL_LIGHTING);
      glColor3ub(255, 255, 0);
      glutSolidSphere(5.0f, 15, 15);
  
    // Восстанавливаем атрибуты освещения из стека атрибутов
    glPopAttrib();
  
  // Восстанавливаем из стека матрицу преобразований
  glPopMatrix();
  
  // Усстанавливаем текущий цвет рисования сферы синий
  glColor3ub(0, 0, 255);
  
  if(iTess == MODE_VERYLOW)  // Низкая тесселяция
    glutSolidSphere(40.0f, 7, 7);
  else 
    if(iTess == MODE_MEDIUM)// Средняя тесселяция
      glutSolidSphere(40.0f, 15, 15);
    else //  iTess == MODE_VERYHIGH  // Высокая тесселяция
      glutSolidSphere(40.0f, 50, 50);
  
  // Переключить буфер
  glutSwapBuffers();
}
  
//**********************************************************
// Функция обратного вызова для рисования сцены
void RenderSceneCrystalAndSpot()
{
  // Сбрасываем буфер цвета и буфер глубины
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  // Рисуем сцену
  CrystalAndSpot();
  
  // Прокачка сообщений
  glFlush();
}
  
//**********************************************************
// Настройка света и материала
void SetLightCrystalAndSpot()
{
  // Модель освещения
  glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientLight);
  
  // Настройка источника света
  glLightfv(GL_LIGHT0, GL_DIFFUSE, ambientLight);
  glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
  glLightfv(GL_LIGHT0, GL_POSITION, light_positionCrystalAndSpot);
  // Спецификация источника как прожектора
  glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 60.0f);
  
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  
  // Включение режима согласования цветов
  glEnable(GL_COLOR_MATERIAL);
  
  // Установка свойств материала в зависимости от значения glColor()
  glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
  
  // Настройка материала
  glMaterialfv(GL_FRONT, GL_SPECULAR, specular);  // Настройка отражения
  glMateriali(GL_FRONT, GL_SHININESS, 128);    // Яркий блик
}
  
//**********************************************************
void ChangeSizeCrystalAndSpot(int width, int height)
{
  // Устанавливаем индивидуальные настройки освещения
  SetLightCrystalAndSpot();
  
    glEnable(GL_DEPTH_TEST);  // Включить тест глубины
    glEnable(GL_CULL_FACE);    // Отображать только лицевую сторону
    glFrontFace(GL_CCW);    // Считать лицевым обход против часовой стрелки
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// Цвет фона окна
  
  // Предотвращаем деление на нуль
  if(height == 0)
    height = 1;
  
  // Устанавливаем поле просмотра с размерами окна
  glViewport(0, 0, width, height);
  
  // Устанавливает матрицу преобразования в режим проецирования
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
  // Устанавливаем размеры отсекающего объема перспективы 
  GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;  // Для соблюдения пропорций
  gluPerspective(35.0f, aspectRatio, 1.0f, 500.0f);    // Отсекающая перспектива
    
  // Восстановливает матрицу преобразования в исходный режим вида
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  // Отодвинем сцену в отрицательную сторону оси 0z
  glTranslatef(0.0f, 0.0f, -250.0f);
}
Листинг 23.28. Код файла CrystalAndSpot.h
  • Добавьте в файл LightAndMaterial.cpp код, подключающий новое упражнение
// 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);  // Функция установки отсекающего объема
void CrystalAndSpotExecuteMenu(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) Способ согласования цветов"
#include "JetNight.h"    // Упражнение 6: "6) Самолет с нулевым освещением"
#include "JetLight.h"    // Упражнение 7: "7) Самолет с равномерным освещением"
#include "JetShiny.h"    // Упражнение 8: "8) Самолет с зеркальным отражением"
#include "CrystalAndSpot.h"  // Упражнение 9: "9) Рисование кристалла и прожектора"
  
//**********************************************************
// Функция обратного вызова обработки выбора пользователя
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;
    case 6:
      ChangeSizeJetNight(w, h);
      break;
    case 7:
      ChangeSizeJetLight(w, h);
      break;
    case 8:
      ChangeSizeJetShiny(w, h);
      break;
  }
    
  // Вызвать принудительно визуализацию
  glutPostRedisplay();
}
  
//****************************************************
// Функция обратного вызова обработки выбора пользователя в подменю
void CrystalAndSpotExecuteMenu(int choice)
{
  xRot = yRot = 0;
  ChangeSizeCrystalAndSpot(w, h);
  
  switch(choice){
    case 0:
      iShade = MODE_FLAT;
      break;
    case 1: 
      iShade = MODE_SMOOTH;
      break;
    case 2: 
      iTess = MODE_VERYLOW;
      break;
    case 3:
      iTess = MODE_MEDIUM;
      break;
    case 4:
        default:
            iTess = MODE_VERYHIGH;
  }
  
  // Устанавливаем режим и вызываем принудительно 
  // функцию визуализации, где размещен
  // вызов функции рисования кристалла и прожектора
  ::choice = 9;
  
  // Вызвать принудительно визуализацию
  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;
    case 6:
      RenderSceneJetNight();
      break;
    case 7:
      RenderSceneJetLight();
      break;
    case 8:
      RenderSceneJetShiny();
      break;
    case 9:
      RenderSceneCrystalAndSpot();
      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;
    case 6:
      ChangeSizeJetNight(width, height);
      break;
    case 7:
      ChangeSizeJetLight(width, height);
      break;
    case 8:
      ChangeSizeJetShiny(width, height);
      break;
    case 9:
      ChangeSizeCrystalAndSpot(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);    // Для управления с клавиатуры
    
  // Создаем подменю для рисования кристалла и прожектора
  int CrystalAndSpot_menu = glutCreateMenu(CrystalAndSpotExecuteMenu);
  glutAddMenuEntry("Плоское затенение поверхностей", 0);
  glutAddMenuEntry("Гладкое (градиентное) затенение", 1);
  glutAddMenuEntry("Низкая тесселяция (крупные грани)", 2);
  glutAddMenuEntry("Средняя тесселяция (больше граней)", 3);
  glutAddMenuEntry("Высокая аппроксимация (много граней)", 4);
  
  // Создание меню и добавление опций выбора
  glutCreateMenu(ExecuteMenu);
  glutAddMenuEntry("1) Куб цвета", 1);
  glutAddMenuEntry("2) Самолет без освещения", 2);
  glutAddMenuEntry("3) Рисование освещенной сферы", 3);
  glutAddMenuEntry("4) Рисование с разными материалами", 4);
  glutAddMenuEntry("5) Способ согласования цветов (F1, F2, F3)", 5);
  glutAddMenuEntry("6) Самолет с нулевым освещением", 6);
  glutAddMenuEntry("7) Самолет с равномерным освещением", 7);
  glutAddMenuEntry("8) Самолет с зеркальным отражением", 8);
  glutAddSubMenu("9) Рисование кристалла и прожектора", CrystalAndSpot_menu);
  
  glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
  // Конец создания меню
  
  glutMainLoop();  // Цикл сообщений графического окна
}
Листинг 23.29. Код подключения модели самолета с зеркальным отражением в файле LightAndMaterial.cpp
  • Запустите приложение и выполните упражнение 9, результат должен быть примерно таким


  • Попытайтесь разобраться с кодом!!!

Переменные iTess и iMode контролируют, на сколько участков разбивается сфера и какое затенение применяется - плоское или гладкое. Обратите внимание, что осуществляется управление положением не объекта, а прожектора. Если специфицировать прожектор с меньшим углом конуса света, например, в функции SetLightCrystalAndSpot() таким кодом

// Спецификация источника как прожектора
  glLightf(GL_LIGHT0, GL_SPOT_CUTOFF, 20.0f);

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

Иван Циферблат
Иван Циферблат
Россия, Таганрог, 36, 2000