NeHe Tutorials Народный учебник по OpenGL
Урок 27. OpenGL

Shadows

 

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

 

На этом уроке предполагается, что Вы имеет уже достаточно много знаний об OpenGL. Вы должны знать, как работает буфер трафарета, и основы инициализации OpenGL. Иначе освежите свои знания и почитайте более ранние уроки. Функции типа CreateGLWindow и WinMain не будут объясняться в этом уроке. Дополнительно, необходимо знать азы 3D, поэтому возьмите в руки хороший учебник по трехмерной графике! (Я использовал мои лекции по математике с первого курса университета - я знал, что они мне пригодятся! :)

 

Сначала мы определим значение БЕСКОНЕЧНОСТИ, которое задает, как далеко, простираются теневые объемы от многоугольников (это объясним позже). Если Вы используете большую или меньшую систему координат, скорректируете это значение соответственно.

 

// определим Of "БЕСКОНЕЧНОСТЬ" для вычисления вектора расширения теневого объема

#define INFINITY  100

 

Затем - определение структур объектов.

 

Структура Point3f содержит значения координат точки в 3D пространстве. Она может использоваться для вершин или векторов.

 

// Структура описания вершины в объекте

struct Point3f

{

  GLfloat x, y, z;

};

 

Структура Plane содержит 4 значения, которые необходимы для формирования уравнения плоскости. Эти плоскости задают лицевые стороны объектов (их грани).

 

// Структура описания плоскости, в формате: ax + by + cz + d = 0

struct Plane

{

  GLfloat a, b, c, d;

};

 

Структура Face содержит всю информацию, необходимую для задания треугольника для отбрасывания тени. Индексы задают порядок задания вершин.

 

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

 

Уравнение плоскости (planeEquation) описывает плоскость, в которой этот треугольник находится в 3D.

 

Индексы соседей (neighbourIndices) - индексы в массиве граней объекта. Это позволяет Вам определить, как грань соединена с другими гранями на каждой стороне треугольника.

 

Параметр видимости (visible) используется, чтобы определить, является ли грань "видимой" источнику света, который отбрасывает тени.

 

// Структура описания грани объекта

struct Face

{

  int vertexIndices[3]; // Индекс каждой вершины в объекте, которые задают треугольник грани

  Point3f normals[3];   // Нормаль каждой вершины

  Plane planeEquation;  // Уравнение плоскости с треугольником

  int neighbourIndices[3]; // Индекс каждой грани, которая является соседом в этом объекте

  bool visible;         // Свет видит эту грань?

};

 

Наконец, структура ShadowedObject содержит все вершины и грани объекта. Память для каждого из массивов выделяется динамически, когда объект загружается.

 

struct ShadowedObject

{

  int nVertices;

  Point3f *pVertices; // Динамически выделяется

 

  int nFaces;

  Face *pFaces;       // Динамически выделяется

};

 

Действия функции readObject понятны из ее названия. В ней происходит заполнение структуры значениями из файла, выделение памяти для вершин и граней. Индексы соседних граней инициализируются значением –1, что означает, что их нет. Они будут рассчитаны позже.

 

bool readObject( const char *filename, ShadowedObject& object )

{

  FILE *pInputFile;

  int i;

 

  pInputFile = fopen( filename, "r" );

  if ( pInputFile == NULL )

  {

    cerr << "Не могу открыть файл объекта: " << filename << endl;

    return false;

  }

 

  // Читать вершины

  fscanf( pInputFile, "%d", &object.nVertices );

  object.pVertices = new Point3f[object.nVertices];

  for ( i = 0; i < object.nVertices; i++ )

  {

    fscanf( pInputFile, "%f", &object.pVertices[i].x );

    fscanf( pInputFile, "%f", &object.pVertices[i].y );

    fscanf( pInputFile, "%f", &object.pVertices[i].z );

  }

 

  // Читать грани

  fscanf( pInputFile, "%d", &object.nFaces );

  object.pFaces = new Face[object.nFaces];

  for ( i = 0; i < object.nFaces; i++ )

  {

    int j;

    Face *pFace = &object.pFaces[i];

 

    for ( j = 0; j < 3; j++ )

      pFace->neighbourIndices[j] = -1; // Нет соседей

 

    for ( j = 0; j < 3; j++ )

    {

      fscanf( pInputFile, "%d", &pFace->vertexIndices[j] );

      pFace->vertexIndices[j]--; // В файле индексы начинают с 1, а в массиве с 0

    }

 

    for ( j = 0; j < 3; j++ )

    {

      fscanf( pInputFile, "%f", &pFace->normals[j].x );

      fscanf( pInputFile, "%f", &pFace->normals[j].y );

      fscanf( pInputFile, "%f", &pFace->normals[j].z );

    }

  }

  return true;

}

 

Аналогично, действия killObject очевидны – в ней удаляется выделенная память, и обнуляются указатели. Обратите внимание, что, строка добавлена в KillGLWindow для вызова этой функции с указанным объектом.

 

void killObject( ShadowedObject& object )

{

  delete[] object.pFaces;

  object.pFaces = NULL;

  object.nFaces = 0;

 

  delete[] object.pVertices;

  object.pVertices = NULL;

  object.nVertices = 0;

}

 

Теперь интереснее, рассмотрим функцию setConnectivity. Эта функция вычисляет все соседние грани для всех граней в данном объекте. Вот ее псевдокод:

 

Для каждой грани (A) в объекте

  Для каждой стороны в грани A

    Если мы не знаем эти грани еще как соседи

      Для каждой грани (B) в объекте (не включая грань A)

        Для каждой стороны в B

          Если сторона в A совпадает со стороной B, тогда они соседи по этой стороне

            Задать свойство связанности каждой из граней A и B,

            Затем перейти на следующую сторону в A

 

Последние две строки связаны со следующим кодом. Получаем индексы двух смежных вершин грани A и грани B, которые находятся на одной стороне грани. Так как у грани 3 вершины, то (edgeA+1) %3 дает индекс следующей вершины. Затем надо проверить, совпадают ли вершины (порядок следования индексов вершин может быть различным).

 

  int vertA1 = pFaceA->vertexIndices[edgeA];

  int vertA2 = pFaceA->vertexIndices[( edgeA+1 )%3];

 

  int vertB1 = pFaceB->vertexIndices[edgeB];

  int vertB2 = pFaceB->vertexIndices[( edgeB+1 )%3];

 

  // Проверить если они соседи, т.е. одинаковые стороны

  if (( vertA1 == vertB1 && vertA2 == vertB2 ) || ( vertA1 == vertB2 && vertA2 == vertB1 ))

  {

    pFaceA->neighbourIndices[edgeA] = faceB;

    pFaceB->neighbourIndices[edgeB] = faceA;

    edgeFound = true;

    break;

  }

 

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

 

// Нарисовать объект, просто нарисовать каждую треугольную грань

void drawObject( const ShadowedObject& object )

{

  glBegin( GL_TRIANGLES );

  for ( int i = 0; i < object.nFaces; i++ )

  {

    const Face& face = object.pFaces[i];

 

    for ( int j = 0; j < 3; j++ )

    {

      const Point3f& vertex = object.pVertices[face.vertexIndices[j]];

 

      glNormal3f( face.normals[j].x, face.normals[j].y, face.normals[j].z );

      glVertex3f( vertex.x, vertex.y, vertex.z );

    }

  }

  glEnd();

}

 

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

 

void calculatePlane( const ShadowedObject& object, Face& face )

{

  // Упростить обращение к вершинам объектов

  const Point3f& v1 = object.pVertices[face.vertexIndices[0]];

  const Point3f& v2 = object.pVertices[face.vertexIndices[1]];

  const Point3f& v3 = object.pVertices[face.vertexIndices[2]];

 

  face.planeEquation.a = v1.y*(v2.z-v3.z) + v2.y*(v3.z-v1.z) + v3.y*(v1.z-v2.z);

  face.planeEquation.b = v1.z*(v2.x-v3.x) + v2.z*(v3.x-v1.x) + v3.z*(v1.x-v2.x);

  face.planeEquation.c = v1.x*(v2.y-v3.y) + v2.x*(v3.y-v1.y) + v3.x*(v1.y-v2.y);

  face.planeEquation.d = -( v1.x*( v2.y*v3.z - v3.y*v2.z ) +

        v2.x*(v3.y*v1.z - v1.y*v3.z) +

        v3.x*(v1.y*v2.z - v2.y*v1.z) );

}

 

Вы восстановили свое дыхание? Отлично! Потому что далее идет код отбрасывания тени! Функция castShadow делает все необходимые настройки OpenGL, и вызывает doShadowPass для двухпроходной визуализации.

 

Сначала, мы определяем, какая поверхность обращена к свету. Это можно понять при помощи определения, с какой стороны поверхности находится свет. Подставим позицию света в уравнение плоскости. Если мы получили значение больше чем 0, тогда совпадает с направлением нормали плоскости, и свет видим. Если нет, то свет не видим. (Снова, обратитесь к хорошему учебнику по математике, если Вам, что-то не понятно).

 

void castShadow( ShadowedObject& object, GLfloat *lightPosition )

{

  // Определим, какие плоскости видят свет.

  for ( int i = 0; i < object.nFaces; i++ )

  {

    const Plane& plane = object.pFaces[i].planeEquation;

 

    GLfloat side = plane.a*lightPosition[0]+

      plane.b*lightPosition[1]+

      plane.c*lightPosition[2]+

      plane.d;

 

    if ( side > 0 )

      object.pFaces[i].visible = true;

    else

      object.pFaces[i].visible = false;

  }

 

Следующий раздел задает настройки OpenGL для визуализации теней.

 

Сначала, мы помещаем в стек все атрибуты, которые будут изменены. Это делает восстановление их намного проще.

 

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

 

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

 

Буфер трафарета включен, так как именно в него и будут рисоваться тени.

 

  glPushAttrib( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |

                GL_ENABLE_BIT | GL_POLYGON_BIT | GL_STENCIL_BUFFER_BIT );

  glDisable( GL_LIGHTING );    // Выключим свет

  glDepthMask( GL_FALSE );     // Выключим запись в буфер глубины

  glDepthFunc( GL_LEQUAL );

  glEnable( GL_STENCIL_TEST ); // Включим тест буфера трафарета

  glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); // Не рисовать в буфер цвета

  glStencilFunc( GL_ALWAYS, 1, 0xFFFFFFFFL );

 

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

 

  // Первый проход увеличение значения трафарета в тени

  glFrontFace( GL_CCW );

  glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );

  doShadowPass( object, lightPosition );

  // Второй проход уменьшение значения трафарета в тени

  glFrontFace( GL_CW );

  glStencilOp( GL_KEEP, GL_KEEP, GL_DECR );

  doShadowPass( object, lightPosition );

 

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

 

                  

Рисунок 1: Первый проход              Рисунок 2: Второй проход

 

В конце этой функции, выводится со смешением один прямоугольник на весь экран, для того чтобы отбросить тень. Чем более темным Вы сделаете этот прямоугольник, тем более темными будут тени. Поэтому, чтобы изменить свойства тени, измените значения цветовых компонент в glColor4f. При более высоких значениях альфа-канала тени будут более темными. Или Вы можете сделать их красными, зелеными, фиолетовыми...!

 

  glFrontFace( GL_CCW );

  // Включить визуализацию цветового буфера для всех компонент

  glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );

 

  // Нарисовать теневой прямоугольник на весь экран

  glColor4f( 0.0f, 0.0f, 0.0f, 0.4f );

  glEnable( GL_BLEND );

  glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );

  glStencilFunc( GL_NOTEQUAL, 0, 0xFFFFFFFFL );

  glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );

  glPushMatrix();

  glLoadIdentity();

  glBegin( GL_TRIANGLE_STRIP );

    glVertex3f(-0.1f, 0.1f,-0.10f);

    glVertex3f(-0.1f,-0.1f,-0.10f);

    glVertex3f( 0.1f, 0.1f,-0.10f);

    glVertex3f( 0.1f,-0.1f,-0.10f);

  glEnd();

  glPopMatrix();

  glPopAttrib();

}

 

Отлично, далее рассмотрим, как рисуются теневые четырехугольники. Как это работает? Что происходит, когда вы проходите каждую грань, и если она видимая, тогда вы проверяете все ее стороны. Если у стороны нет соседа, или соседняя грань не видима, тогда эта сторона отбрасывает тень. Если Вы как следует, обдумаете эти два варианта, тогда Вы поймете, что так оно и есть на самом деле. Рисуя четырехугольник (как два треугольника), который включает в себя точки стороны, и точки проекции стороны в “бесконечность”, Вы получаете тень, отбрасываемую этой стороной.

 

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

 

Код реализации этого алгоритма более прост, чем рассуждения о нем. Вот начальный фрагмент кода, который надо повторить по всем объектам для получения тени. В конце этого фрагмента мы получаем номер края - j, и номер ее соседней грани - neighbourIndex.

 

void doShadowPass( ShadowedObject& object, GLfloat *lightPosition )

{

  for ( int i = 0; i < object.nFaces; i++ )

  {

    const Face& face = object.pFaces[i];

 

    if ( face.visible )

    {

      // Для каждого края

      for ( int j = 0; j < 3; j++ )

      {

        int neighbourIndex = face.neighbourIndices[j];

 

Затем, идет проверка, видима ли соседняя грань, если нет, то этот край отбрасывает тень.

 

        // Если нет соседа, или сосед не виден, тогда край отбрасывает тень

        if ( neighbourIndex == -1 || object.pFaces[neighbourIndex].visible == false )

        {

 

В следующем сегменте кода вычисляются две вершины текущего края - v1 и v2. Затем вычисляются вершины v3 и v4, которые получаются из проекции вдоль вектора между источником света и первым краем. Они масштабируются к БЕСКОНЕЧНОСТИ, которая была задана как очень большое значение.

 

          // Взять точки на стороне

          const Point3f& v1 = object.pVertices[face.vertexIndices[j]];

          const Point3f& v2 = object.pVertices[face.vertexIndices[( j+1 )%3]];

 

          // Вычислить две вершины на расстоянии

          Point3f v3, v4;

 

          v3.x = ( v1.x-lightPosition[0] )*INFINITY;

          v3.y = ( v1.y-lightPosition[1] )*INFINITY;

          v3.z = ( v1.z-lightPosition[2] )*INFINITY;

 

          v4.x = ( v2.x-lightPosition[0] )*INFINITY;

          v4.y = ( v2.y-lightPosition[1] )*INFINITY;

          v4.z = ( v2.z-lightPosition[2] )*INFINITY;

 

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

 

          // Нарисовать четырехугольник (как полоску из треугольников)

          glBegin( GL_TRIANGLE_STRIP );

            glVertex3f( v1.x, v1.y, v1.z );

            glVertex3f( v1.x+v3.x, v1.y+v3.y, v1.z+v3.z );

            glVertex3f( v2.x, v2.y, v2.z );

            glVertex3f( v2.x+v4.x, v2.y+v4.y, v2.z+v4.z );

          glEnd();

        }

      }

    }

  }

}

 

На этом, секция отбрасывания тени завершена. Но мы еще не закончили! Что там в drawGLScene? Начнем с простых вещей: очистка буферов, позиционирование источника света, и отрисовка сферы:

 

bool drawGLScene()

{

  GLmatrix16f Minv;

  GLvector4f wlp, lp;

 

  // Очистка буфера цвета, глубины и трафарета

  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

 

  glLoadIdentity(); // Сброс матрицы вида модели

  glTranslatef(0.0f, 0.0f, -20.0f); // Наезд в экран на 20 единиц

  glLightfv(GL_LIGHT1, GL_POSITION, LightPos); // Позиция Света1

  glTranslatef(SpherePos[0], SpherePos[1], SpherePos[2]);// Позиция сферы

  gluSphere(q, 1.5f, 32, 16); // Отрисовать сферу

 

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

 

  glLoadIdentity(); // Сброс матрицы

  glRotatef(-yrot, 0.0f, 1.0f, 0.0f);        // Вращение на -yrot по оси Y

  glRotatef(-xrot, 1.0f, 0.0f, 0.0f);        // Вращение на -xrot по оси X

  glTranslatef(-ObjPos[0], -ObjPos[1], -ObjPos[2]); // Сдвинуть в противоположном направлении по всем осям исходя из значений ObjPos[] (X, Y, Z)

  glGetFloatv(GL_MODELVIEW_MATRIX,Minv); // Получить матрицу

  lp[0] = LightPos[0];            // Сохранить позицию света X в lp[0]

  lp[1] = LightPos[1];            // Сохранить позицию света Y в lp[1]

  lp[2] = LightPos[2];            // Сохранить позицию света Z в lp[2]

  lp[3] = LightPos[3];            // Сохранить позицию света в lp[3]

  VMatMult(Minv, lp);             // Сохраним преобразованный вектор в массиве 'lp'

 

Теперь забабахаем работу по выводу комнаты, и объекта. Вызов castShadow выводит тень объекта.

 

  glLoadIdentity();          // Сброс матрицы

  glTranslatef(0.0f, 0.0f, -20.0f); // Наезд на 20 единиц в экран

  DrawGLRoom();              // Отрисовка комнаты

  glTranslatef(ObjPos[0], ObjPos[1], ObjPos[2]); // Позиция объекта

  glRotatef(xrot, 1.0f, 0.0f, 0.0f);        // Вращение по оси X на xrot

  glRotatef(yrot, 0.0f, 1.0f, 0.0f);        // Вращение по оси Y на yrot

  drawObject(obj);           // Процедура для рисования загруженного объекта

  castShadow(obj, lp);       // Процедура для отбрасывания тени используя силуэт объекта

 

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

 

  glColor4f(0.7f, 0.4f, 0.0f, 1.0f);// Оранжевый цвет

  glDisable(GL_LIGHTING);           // Отключить свет

  glDepthMask(GL_FALSE);            // Отключить маску глубины

  glTranslatef(lp[0], lp[1], lp[2]);// Перенос позиции света

                  // Отмечу, что мы все еще в локальной системе координат

  gluSphere(q, 0.2f, 16, 8);        // Нарисовать маленькую оранжевую сферу

  glEnable(GL_LIGHTING);            // Разрешение света

  glDepthMask(GL_TRUE);             // Разрешение маски глубины

 

В конце меняем позицию объекта и выход.

 

  xrot += xspeed;              // Увеличим xrot на xspeed

  yrot += yspeed;              // Увеличим yrot на yspeed

 

  glFlush();                // Сброс OpenGL

  return TRUE;              // Скажем OK

}

 

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

 

void DrawGLRoom()               // Нарисовать комнату

{

  glBegin(GL_QUADS);            // Рисовать прямоугольники

    // Пол

    glNormal3f(0.0f, 1.0f, 0.0f);        // Нормаль вверх

    glVertex3f(-10.0f,-10.0f,-20.0f);      // Лево Назад

    glVertex3f(-10.0f,-10.0f, 20.0f);      // Лево Перед

    glVertex3f( 10.0f,-10.0f, 20.0f);      // Право Перед

    glVertex3f( 10.0f,-10.0f,-20.0f);      // Право Назад

    // Потолок

    glNormal3f(0.0f,-1.0f, 0.0f);        // Нормаль вниз

    glVertex3f(-10.0f, 10.0f, 20.0f);      // Лево Перед

    glVertex3f(-10.0f, 10.0f,-20.0f);      // Лево Назад

    glVertex3f( 10.0f, 10.0f,-20.0f);      // Назад Право

    glVertex3f( 10.0f, 10.0f, 20.0f);      // Право Верх

    // Передняя стена

    glNormal3f(0.0f, 0.0f, 1.0f);        // Нормаль вдаль от зрителя

    glVertex3f(-10.0f, 10.0f,-20.0f);      // Лево Верх

    glVertex3f(-10.0f,-10.0f,-20.0f);      // Лево Низ

    glVertex3f( 10.0f,-10.0f,-20.0f);      // Право Низ

    glVertex3f( 10.0f, 10.0f,-20.0f);      // Право Верх

    // Задняя стена

    glNormal3f(0.0f, 0.0f,-1.0f);        // Нормаль на зрителя

    glVertex3f( 10.0f, 10.0f, 20.0f);      // Право Верх

    glVertex3f( 10.0f,-10.0f, 20.0f);      // Право Низ

    glVertex3f(-10.0f,-10.0f, 20.0f);      // Лево Низ

    glVertex3f(-10.0f, 10.0f, 20.0f);      // Лево Верх

    // Левая стена

    glNormal3f(1.0f, 0.0f, 0.0f);        // Нормаль вправо

    glVertex3f(-10.0f, 10.0f, 20.0f);      // Верх Перед

    glVertex3f(-10.0f,-10.0f, 20.0f);      // Низ Перед

    glVertex3f(-10.0f,-10.0f,-20.0f);      // Назад Низ

    glVertex3f(-10.0f, 10.0f,-20.0f);      // Назад Перед

    // Правая стена

    glNormal3f(-1.0f, 0.0f, 0.0f);        // Нормаль влево

    glVertex3f( 10.0f, 10.0f,-20.0f);      // Назад Верх

    glVertex3f( 10.0f,-10.0f,-20.0f);      // Назад Низ

    glVertex3f( 10.0f,-10.0f, 20.0f);      // Низ Перед

    glVertex3f( 10.0f, 10.0f, 20.0f);      // Верх Перед

  glEnd();              // Конец рисования

}

 

И прежде, чем я забыл, вот код функции VMatMult, которая умножает вектор на матрицу:

 

void VMatMult(GLmatrix16f M, GLvector4f v)

{

  GLfloat res[4];           // Сохранить вычисленный результат

  res[0]=M[ 0]*v[0]+M[ 4]*v[1]+M[ 8]*v[2]+M[12]*v[3];

  res[1]=M[ 1]*v[0]+M[ 5]*v[1]+M[ 9]*v[2]+M[13]*v[3];

  res[2]=M[ 2]*v[0]+M[ 6]*v[1]+M[10]*v[2]+M[14]*v[3];

  res[3]=M[ 3]*v[0]+M[ 7]*v[1]+M[11]*v[2]+M[15]*v[3];

  v[0]=res[0];              // Результат сохранен обратно в v[]

  v[1]=res[1];

  v[2]=res[2];

  v[3]=res[3];              // Гомогенные координаты

}

 

Функция для загрузки объекта очень проста в ней только вызов readObject, и вычисление связанности и уравнений плоскости для каждой грани.

 

int InitGLObjects()          // Инициализация объектов

{

  if (!readObject("Data/Object2.txt", obj)) // Чтение Object2 в obj

  {

    return FALSE;            // Если сбой вернем False

  }

 

  setConnectivity(obj);      // Зададим связанность грань к грани

 

  for ( int i=0;i < obj.nFaces;i++)    // Цикл по всем граням

    calculatePlane(obj, obj.pFaces[i]);// Уравнение плоскости

 

  return TRUE;               // Вернем True

}

 

Наконец, вспомогательная функция KillGLObjects для уничтожения всех объектов.

 

void KillGLObjects()

{

  killObject( obj ); 

}

 

Все другие функции не требуют никакого дополнительного объяснения. Я не опустил стандартный код уроков NeHe, и все определения переменных и функцию обработки клавиатуры. Комментариев в них достаточно.

 

Я хотел бы обратить Ваше внимание на некоторое вещи:

 

Сфера не отбрасывает тень на стенку. В реальности, сфера должна также отбрасывать тень.

 

Если Вы наблюдаете, резкое падение чистоты смены кадров, пробуйте переключить в полноэкранный режим, или задать вашу цветовую глубину экрана в 32bpp.

 

Arseny L. пишет: Если Вы имеете проблемы с видеокартами TNT2 в оконном режиме, проверьте, что ваша цветовая глубина экрана не задана в 16bit. В 16bit цветном режиме, буфер трафарета эмулируется, что приводит к вялой производительности. Нет никаких проблем в 32bit режиме (я имею TNT2 Ultra, и я проверил это).

 

Я должен признаться, что это былf длинная задача написать этот урок. Это дает Вам точную оценку той работы, которую выполняет Джеф! Я надеюсь, что Вам понравился это урок, и Вы благодарите Banu, который написал первоначальный код! ЕСЛИ  что-нибудь не понятно, то пишите письма - brettporter@yahoo.com.

© Banu Cosmin (Choko) & Brett Porter

PMG  26 июля 2004 (c)  Сергей Анисимов