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

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

Модель самолета и его тени (JetShadow)

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

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

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

typedef GLfloat GLTVector3[3];     
typedef GLfloat GLTVector4[4]; 
typedef GLfloat GLTMatrix[16];
  
//**********************************************************
// Вычисляет матрицу преобразования тени.
// Входными параметрами являются координаты трех точек
// на плоскости (не лежащих на одной прямой) 
// и четырехмерный вектор положения источника света
// Возвращаемое значение находится в destMat
void gltMakeShadowMatrix(GLTVector3 vPoints[3], GLTVector4 vLightPos, GLTMatrix destMat)
{
  GLTVector4 vPlaneEquation;
  GLfloat dot;
  
  gltGetPlaneEquation(vPoints[0], vPoints[1], vPoints[2], vPlaneEquation);
  
  // Вычисляет скалярное произведение направляющего вектора плоскости
  // и вектора положения источника света
  dot =  vPlaneEquation[0]*vLightPos[0] + 
      vPlaneEquation[1]*vLightPos[1] + 
      vPlaneEquation[2]*vLightPos[2] + 
      vPlaneEquation[3]*vLightPos[3];
  
  // Формируем матрицу проекции
  // Первый столбец
  destMat[0] = dot - vLightPos[0] * vPlaneEquation[0];
  destMat[4] = 0.0f - vLightPos[0] * vPlaneEquation[1];
  destMat[8] = 0.0f - vLightPos[0] * vPlaneEquation[2];
  destMat[12] = 0.0f - vLightPos[0] * vPlaneEquation[3];
  
  // Второй столбец
  destMat[1] = 0.0f - vLightPos[1] * vPlaneEquation[0];
  destMat[5] = dot - vLightPos[1] * vPlaneEquation[1];
  destMat[9] = 0.0f - vLightPos[1] * vPlaneEquation[2];
  destMat[13] = 0.0f - vLightPos[1] * vPlaneEquation[3];
  
  // Третий столбец
  destMat[2] = 0.0f - vLightPos[2] * vPlaneEquation[0];
  destMat[6] = 0.0f - vLightPos[2] * vPlaneEquation[1];
  destMat[10] = dot - vLightPos[2] * vPlaneEquation[2];
  destMat[14] = 0.0f - vLightPos[2] * vPlaneEquation[3];
  
  // Четвертый столбец
  destMat[3] = 0.0f - vLightPos[3] * vPlaneEquation[0];
  destMat[7] = 0.0f - vLightPos[3] * vPlaneEquation[1];
  destMat[11] = 0.0f - vLightPos[3] * vPlaneEquation[2];
  destMat[15] = dot - vLightPos[3] * vPlaneEquation[3];
}
  
//**********************************************************
// Далее идет код вспомогательных функций
//**********************************************************
// Возвращает коэффициенты уравнения плоскости по трем точкам
void gltGetPlaneEquation(GLTVector3 vPoint1, GLTVector3 vPoint2, GLTVector3 vPoint3, GLTVector3 vPlane)
{
  // Вычислить вектор нормали
  gltGetNormalVector(vPoint1, vPoint2, vPoint3, vPlane);
  
  vPlane[3] = -(vPlane[0] * vPoint3[0] + vPlane[1] * vPoint3[1] + vPlane[2] * vPoint3[2]);
}
  
//**********************************************************
// Вычислить нормаль по трем точкам
void gltGetNormalVector(const GLTVector3 vP1, const GLTVector3 vP2, const GLTVector3 vP3, GLTVector3 vNormal)
{
    GLTVector3 vV1, vV2;
  
  gltSubtractVectors(vP2, vP1, vV1);
  gltSubtractVectors(vP3, vP1, vV2);
  
  gltVectorCrossProduct(vV1, vV2, vNormal);
  gltNormalizeVector(vNormal);
}
  
//**********************************************************
// Вычесть один вектор из другого
void gltSubtractVectors(const GLTVector3 vFirst, const GLTVector3 vSecond, GLTVector3 vResult) 
{
  vResult[0] = vFirst[0] - vSecond[0];
  vResult[1] = vFirst[1] - vSecond[1];
  vResult[2] = vFirst[2] - vSecond[2];
}
  
//**********************************************************
// Вычислить векторное произведение двух векторов
void gltVectorCrossProduct(const GLTVector3 vU, const GLTVector3 vV, GLTVector3 vResult)
{
  vResult[0] = vU[1]*vV[2] - vV[1]*vU[2];
  vResult[1] = -vU[0]*vV[2] + vV[0]*vU[2];
  vResult[2] = vU[0]*vV[1] - vV[0]*vU[1];
}
  
//**********************************************************
// Привести вектор к единичной длине (нормировать)
void gltNormalizeVector(GLTVector3 vNormal)
{ 
  GLfloat fLength = 1.0f / gltGetVectorLength(vNormal);
  gltScaleVector(vNormal, fLength); 
}
  
//**********************************************************
// Умножить вектор на скаляр
void gltScaleVector(GLTVector3 vVector, const GLfloat fScale)
{ 
  vVector[0] *= fScale; vVector[1] *= fScale; vVector[2] *= fScale; 
}
Листинг 23.31. Код функции gltMakeShadowMatrix, формирующей матрицу тени

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

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

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


  • Заполните этот файл следующим кодом для упражнения 10
// JetShadow.h
// Упражнение 10: "10) Модель самолета и его тени"
  
#include <math.h>
  
//**********************************************************
// Прототипы
void JetShadow(int nShadow);
void RenderSceneJetShadow(void);
void SetLightJetShadow();
void ChangeSizeJetShadow(int width, int height);
  
//**********************************************************
// Вспомогательный код
typedef GLfloat GLTVector3[3];     
typedef GLfloat GLTVector4[4]; 
typedef GLfloat GLTMatrix[16];
void gltMakeShadowMatrix(GLTVector3 vPoints[3], GLTVector4 vLightPos, GLTMatrix destMat);
void gltGetPlaneEquation(GLTVector3 vPoint1, GLTVector3 vPoint2, GLTVector3 vPoint3, GLTVector3 vPlane);
  
//**********************************************************
// Глобальные переменные
GLTMatrix shadowMat;
GLfloat   lightPosJetShadow[] = { -75.0f, 150.0f, -50.0f, 0.0f };
  
//**********************************************************
// Рисовать самолет
void JetShadow(int nShadow)
{
  GLTVector3 vNormal;  // Для вектора нормали
  
  // Каким цветом рисовать
  if(nShadow == 0)  // Рисовать белым цветом сам самолет
      glColor3ub(128, 128, 128);
  else        // Рисовать черным цветом тень самолета
      glColor3ub(0,0,0);
  
  // Рисуем самолет
  glBegin(GL_TRIANGLES);
  // Конус носа /////////////////////////////
    glNormal3f(0.0f, -1.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 60.0f);
    glVertex3f(-15.0f, 0.0f, 30.0f);
    glVertex3f(15.0f,0.0f,30.0f);
  
    {// Блок кода
      GLTVector3 vPoints[3] = {{ 15.0f, 0.0f,  30.0f},
                  { 0.0f,  15.0f, 30.0f},
                  { 0.0f,  0.0f,  60.0f}};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    {// Блок кода
      GLTVector3 vPoints[3] = {{ 0.0f, 0.0f, 60.0f },
                  { 0.0f, 15.0f, 30.0f },
                  { -15.0f, 0.0f, 30.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
  // Тело самолета //////////////////////////
    {
      GLTVector3 vPoints[3] = {{ -15.0f, 0.0f, 30.0f },
                  { 0.0f, 15.0f, 30.0f },
                  { 0.0f, 0.0f, -56.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    {
      GLTVector3 vPoints[3] = {{ 0.0f, 0.0f, -56.0f },
                  { 0.0f, 15.0f, 30.0f },
                  { 15.0f,0.0f,30.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    glNormal3f(0.0f, -1.0f, 0.0f);
    glVertex3f(15.0f,0.0f,30.0f);
    glVertex3f(-15.0f, 0.0f, 30.0f);
    glVertex3f(0.0f, 0.0f, -56.0f);
  
  ///////////////////////////////////////////
  // Левое крыло
  // Большой треугольник для основания крыла
    {
      GLTVector3 vPoints[3] = {{ 0.0f,2.0f,27.0f },
                  { -60.0f, 2.0f, -8.0f },
                  { 60.0f, 2.0f, -8.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    {
      GLTVector3 vPoints[3] = {{ 60.0f, 2.0f, -8.0f},
                  {0.0f, 7.0f, -8.0f},
                  {0.0f,2.0f,27.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    {
      GLTVector3 vPoints[3] = {{60.0f, 2.0f, -8.0f},
                  {-60.0f, 2.0f, -8.0f},
                  {0.0f,7.0f,-8.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
  // Другое крыло верхней секции
    {
      GLTVector3 vPoints[3] = {{0.0f,2.0f,27.0f},
                  {0.0f, 7.0f, -8.0f},
                  {-60.0f, 2.0f, -8.0f}};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
                        
  ///////////////////////////////////////////
  // Хвост 
    glNormal3f(0.0f, -1.0f, 0.0f);
    glVertex3f(-30.0f, -0.50f, -57.0f);
    glVertex3f(30.0f, -0.50f, -57.0f);
    glVertex3f(0.0f,-0.50f,-40.0f);
  
    // Верхняя левая сторона
    {
      GLTVector3 vPoints[3] = {{ 0.0f,-0.5f,-40.0f },
                  {30.0f, -0.5f, -57.0f},
                  {0.0f, 4.0f, -57.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
                        
    // Верхняя правая сторона
    {
      GLTVector3 vPoints[3] = {{ 0.0f, 4.0f, -57.0f },
                  { -30.0f, -0.5f, -57.0f },
                  { 0.0f,-0.5f,-40.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    // Оборотная нижняя часть
    {
      GLTVector3 vPoints[3] = {{ 30.0f,-0.5f,-57.0f },
                  { -30.0f, -0.5f, -57.0f },
                  { 0.0f, 4.0f, -57.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    {
      GLTVector3 vPoints[3] = {{ 0.0f,0.5f,-40.0f },
                  { 3.0f, 0.5f, -57.0f },
                  { 0.0f, 25.0f, -65.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    {
      GLTVector3 vPoints[3] = {{ 0.0f, 25.0f, -65.0f },
                  { -3.0f, 0.5f, -57.0f},
                  { 0.0f,0.5f,-40.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  
    {
      GLTVector3 vPoints[3] = {{ 3.0f,0.5f,-57.0f },
                  { -3.0f, 0.5f, -57.0f },
                  { 0.0f, 25.0f, -65.0f }};
      gltGetNormalVector(vPoints[0], vPoints[1], vPoints[2], vNormal);
      glNormal3fv(vNormal);
      glVertex3fv(vPoints[0]);
      glVertex3fv(vPoints[1]);
      glVertex3fv(vPoints[2]);
    }
  glEnd(); // О самолете
}
  
//**********************************************************
// Функция рендеринга
void RenderSceneJetShadow(void)
{
  // Очищаем буфер цвета и глубины
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  // Рисуем плоскость, имитирующую зеленую землю
  glBegin(GL_QUADS);
    glColor3ub(0,32,0);
    glVertex3f(400.0f, -150.0f, -200.0f);
    glVertex3f(-400.0f, -150.0f, -200.0f);
    glColor3ub(0,255,0);
    glVertex3f(-400.0f, -150.0f, 200.0f);
    glVertex3f(400.0f, -150.0f, 200.0f);
  glEnd();
  
  // Сохраним матрицу преобразования модели
  // перед добавлением поворотов освещенного самолета
  glPushMatrix();
  
  // Включаем освещение и ориентируем источник света
  glEnable(GL_LIGHTING);
  glLightfv(GL_LIGHT0, GL_POSITION, lightPosJetShadow);
  
  // Для поворота самолета
  glRotatef(xRot, 1.0f, 0.0f, 0.0f);
  glRotatef(yRot, 0.0f, 1.0f, 0.0f);
  
  // Нарисуем освещенный самолет
  JetShadow(0);
  
  // Восстанавливаем матрицу преобразования модели
  glPopMatrix();  
  
  // Перед рисованием тени отключаем освещение и глубину
  // Чтобы тень была чисто черной и плоской
  glDisable(GL_DEPTH_TEST);
  glDisable(GL_LIGHTING);
  glPushMatrix();
  
  // Умножение на матрицу преобразования тени
  glMultMatrixf( (GLfloat *) shadowMat );
  
  // Добавим повороты
  glRotatef(xRot, 1.0f, 0.0f, 0.0f);
  glRotatef(yRot, 0.0f, 1.0f, 0.0f);
  
  // Нарисуем тень самолета
  JetShadow(1);  
  
  // Восстановим матрицу преобразования модели
  glPopMatrix();
  
  // Рисуем источник света
  glPushMatrix();
  // Рисуем желтую сферу в месте нахождения источника света
  glTranslatef(lightPosJetShadow[0], lightPosJetShadow[1], lightPosJetShadow[2]);
  glColor3ub(255, 255, 0);  // Задаем желтый цвет
  glutSolidSphere(15.0f, 15, 15);// Рисуем сферу
  glPopMatrix();
  
  // Восстанавливаем отключенную проверку глубины
  // Отключенное освещение включится при новом заходе в эту функцию
  glEnable(GL_DEPTH_TEST);
  
  // Переключаем буферы рисования
  glutSwapBuffers();
  
  // Прокачка сообщений
  glFlush();
}
  
//**********************************************************
// Настройка света и материала
void SetLightJetShadow()
{
  // Параметры освещения
  GLfloat  ambientLight[] = { 0.3f, 0.3f, 0.3f, 1.0f };
  GLfloat  diffuseLight[] = { 0.7f, 0.7f, 0.7f, 1.0f };
  GLfloat  specular[] = { 1.0f, 1.0f, 1.0f, 1.0f};
  
  // Любые три точки на плоскости, имитирующей землю
  GLTVector3 points[3] = {{ -30.0f, -149.0f, -20.0f },
              { -30.0f, -149.0f, 20.0f },
              { 40.0f, -149.0f, 20.0f }};
  
  // Настроить и включить источник света
  glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
  glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
  glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
  glLightfv(GL_LIGHT0, GL_POSITION, lightPosJetShadow);
  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);
  
  // Вычисление матрицы проекции тени на плоскость основания (землю)
  gltMakeShadowMatrix(points, lightPosJetShadow, shadowMat);
}
  
//**********************************************************
void ChangeSizeJetShadow(int width, int height)
{
  // Устанавливаем индивидуальные настройки освещения
  // и материала
  SetLightJetShadow();
  
    glEnable(GL_DEPTH_TEST);  // Включить тест глубины
    glEnable(GL_CULL_FACE);    // Отображать только лицевую сторону
    glFrontFace(GL_CCW);    // Считать лицевым обход против часовой стрелки
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);// Цвет фона окна
  
  // Предотвращаем деление на нуль
  if(height == 0)
    height = 1;
  
  // Устанавливаем поле просмотра с размерами окна
  glViewport(0, 0, width, height);
  
  // Устанавливает матрицу преобразования в режим проецирования
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  
  // Устанавливаем размеры отсекающего объема перспективы 
  GLfloat aspectRatio = (GLfloat)width / (GLfloat)height;  // Для соблюдения пропорций
    gluPerspective(60.0f, aspectRatio, 200.0f, 500.0f);    // Отсекающая перспектива
  
  // Восстановливает матрицу преобразования в исходный режим вида
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  
  // Отодвинем сцену в отрицательную сторону оси 0z
  glTranslatef(0.0f, 0.0f, -400.0f);
}
  
//**********************************************************
//**********************************************************
// Вспомогательный код
//**********************************************************
//**********************************************************
// Вычисляет матрицу преобразования тени.
// Входными параметрами являются координаты трех точек
// на плоскости (не лежащих на одной прямой) 
// и четырехмерный вектор положения источника света
// Возвращаемое значение находится в destMat
void gltMakeShadowMatrix(GLTVector3 vPoints[3], GLTVector4 vLightPos, GLTMatrix destMat)
{
  GLTVector4 vPlaneEquation;
  GLfloat dot;
  
  gltGetPlaneEquation(vPoints[0], vPoints[1], vPoints[2], vPlaneEquation);
  
  // Вычисляет скалярное произведение направляющего вектора плоскости
  // и вектора положения источника света
  dot =  vPlaneEquation[0]*vLightPos[0] + 
      vPlaneEquation[1]*vLightPos[1] + 
      vPlaneEquation[2]*vLightPos[2] + 
      vPlaneEquation[3]*vLightPos[3];
  
  // Формируем матрицу проекции
  // Первый столбец
  destMat[0] = dot - vLightPos[0] * vPlaneEquation[0];
  destMat[4] = 0.0f - vLightPos[0] * vPlaneEquation[1];
  destMat[8] = 0.0f - vLightPos[0] * vPlaneEquation[2];
  destMat[12] = 0.0f - vLightPos[0] * vPlaneEquation[3];
  
  // Второй столбец
  destMat[1] = 0.0f - vLightPos[1] * vPlaneEquation[0];
  destMat[5] = dot - vLightPos[1] * vPlaneEquation[1];
  destMat[9] = 0.0f - vLightPos[1] * vPlaneEquation[2];
  destMat[13] = 0.0f - vLightPos[1] * vPlaneEquation[3];
  
  // Третий столбец
  destMat[2] = 0.0f - vLightPos[2] * vPlaneEquation[0];
  destMat[6] = 0.0f - vLightPos[2] * vPlaneEquation[1];
  destMat[10] = dot - vLightPos[2] * vPlaneEquation[2];
  destMat[14] = 0.0f - vLightPos[2] * vPlaneEquation[3];
  
  // Четвертый столбец
  destMat[3] = 0.0f - vLightPos[3] * vPlaneEquation[0];
  destMat[7] = 0.0f - vLightPos[3] * vPlaneEquation[1];
  destMat[11] = 0.0f - vLightPos[3] * vPlaneEquation[2];
  destMat[15] = dot - vLightPos[3] * vPlaneEquation[3];
}
  
//**********************************************************
// Возвращает коэффициенты уравнения плоскости по трем точкам
void gltGetPlaneEquation(GLTVector3 vPoint1, GLTVector3 vPoint2, GLTVector3 vPoint3, GLTVector3 vPlane)
{
  // Вычислить вектор нормали
  gltGetNormalVector(vPoint1, vPoint2, vPoint3, vPlane);
  
  vPlane[3] = -(vPlane[0] * vPoint3[0] + vPlane[1] * vPoint3[1] + vPlane[2] * vPoint3[2]);
}
Листинг 23.32. Код рисования самолета и его тени в файле JetShadow.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) Рисование кристалла и прожектора"
#include "JetShadow.h"    // Упражнение 10: "10) Модель самолета и его тени"
  
//**********************************************************
// Функция обратного вызова обработки выбора пользователя
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;
    case 10:
      ChangeSizeJetShadow(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;
    case 10:
      RenderSceneJetShadow();
      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;
    case 10:
      ChangeSizeJetShadow(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);
  glutAddMenuEntry("10) Модель самолета и его тени", 10);
  
  glutAttachMenu(GLUT_RIGHT_BUTTON);// Присоединяем 
  // Конец создания меню
  
  glutMainLoop();  // Цикл сообщений графического окна
}
Листинг 23.33. Код подключения модели самолета с тенью в файле LightAndMaterial.cpp
  • Запустите приложение и выполните упражнение 10, результат должен быть примерно таким


  • Поуправляйте самолетом клавишами-стрелками и попытайтесь разобраться с кодом!!!

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

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