Создание 3D игр на C++
Урок 10
Списки Изображения

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

Списки изображений
В ходе работы над программой часто приходится сталкиваться с тем, что какой-то фрагмент сцены выводится постоянно без каких-либо изменений. Самый лучший пример такого фрагмента - это арена в нашей игре. Стены и пол будут всегда постоянны, и их относительное расположение никогда не будет меняться. Т.о. совершенно бессмысленно каждый раз заново пересчитывать матрицу видового преобразования перед выводом каждой отдельной стены, когда можно запомнить арену целиком и каждый раз лишь вновь выводить ее. Для этой цели и служат списки изображений (Display Lists, в разных источниках Списки Отображения или Дисплейные списки).
Список изображения представляет из себя набор координат вертексов, их векторов нормали, свойств материала, цветов, одним словом, он целиком хранит в себе информацию о той или иной законченной модели. Использование списков изображения имеет два огромных преимущества:
1. Выигрыш в скорости
При использовании списков изображения модель уже сохранена в виде массива вертексов и не нуждается в пересчете матрицы видового преобразования. Все вычисления были уже проделаны однажды и вновь вычислять ничего не придется. Если не использовать списки изображения, то при каждом вызове glTranslate* или glRotate* затрачивается немало времени на вычисление новой матрицы видового преобразования.
2. Простота использования
В вашем проекте вам придется однажды создать списки изображений для каждого объекта, а потом их вызывать. В противном случае вам пришлось бы загромождать код отрисовки огромным количеством вызовов команд вывода вертексов, изменения цвета, изменения матрицы видового преобразования etc.

Каждый список изображения имеет свой уникальный ID. Для создания списка изобрадения надо найти незанятый ID. Для этого надо завести переменную типа GLuint (unsigned short в стандартной трактовке C++ Builder), в которую будет занесен этот самый ID, и вызвать функцию
GLunit glGenLists (GLunit n);
Где n - это количетсво списков изображения, которое вам необходимо. Функция возвращает свободный ID, после которого следующие n-1 также свободны, то есть после выполнения такого кода
GLunit k;
k = glGenLists (3);
Можно быть уверенным, что списки, ID которых равен k, k+1 и k+2 будут свободны. Для начала создания списка изображения надо вызвать команду
glNewList (GLuint ID, GLenum mode);
Где mode может быть либо GL_COMPILE, либо GL_COMPILE_AND_EXECUTE. Разница в том, что в первом случае список будет просто сохранен, а во втором еще и сразу выведен на сцену.
Для завершения списка вызывается команда glEndList ();
Список создается так же, как и если бы вы просто рисовали на сцене, то есть между вызовами glNewList и glEndList должны идти команды изменения мировых координат, вывода трехмерных объектов, настройки материалов, выбора текстур и всего, что может быть нужно для создания сцены.

Обратите так же внимание на следующий момент. Если вы создадите список изображения таким образом:
GLunit k;
k = glGenLists (1);
glNewList (k, GL_COMPILE);

GLfloat TheColor [3] = {1, 0, 0}; // Красный
glColor3fv (TheColor); // Установить цвет из массива TheColor
DrawSphere (); // Какая-то функция для вывода сферы

glEndList ();
То есть список, в котором выводится сфера цвета TheColor, потом измените значение TheColor на {1, 1, 1}, то есть белый, и вызовете список, то сфера будет выведена КРАСНОГО цвета, так как в списке изображения сохраняются не ссылки на цвета, а сами цвета. Аналогично с любыми ссылками на переменные во время создяния списка изображения. Сохраняются только значения из переменных, то есть последующие их изменение никак не повлияет на сам список.

Для вывода списка изображения используется команда glCallList (ID).

Теперь применим списки изображений для вывода нашего уровня.
Доработке подвергнется только модуль col_map.
Откройте файл col_map.cpp и после загрузки заголовочных файлов допишите:

GLuint TheMap;

Теперь в функцию загрузки карты LoadMap допишем создание списка изображений. Создавать мы его будем сразу, как только закроем файл, то есть сразу после строки
   fclose (f);
Надо написать:
   // Создаем список изображения
   TheMap = glGenLists (1);
   glNewList (TheMap, GL_COMPILE);

      for (int i = 0; i < ObjCount; i++)
      {
         glBindTexture(GL_TEXTURE_2D, textures[Obj[i].TexIndex]);
         glBegin (GL_QUADS);
         {
            glColor3f (1, 1, 1);
            glNormal3f ((float)Obj[i].xn, (float)Obj[i].yn, (float)Obj[i].zn);
            glTexCoord2f(0.0f, 0.0f);
            glVertex3f ((float)Obj[i].x1, (float)Obj[i].y1, (float)Obj[i].z1);
            glTexCoord2f((float)Obj[i].w, 0.0f);
            glVertex3f ((float)Obj[i].x2, (float)Obj[i].y2, (float)Obj[i].z1);
            glTexCoord2f((float)Obj[i].w, (float)Obj[i].h);
            glVertex3f ((float)Obj[i].x2, (float)Obj[i].y2, (float)Obj[i].z2);
            glTexCoord2f(0.0f, (float)Obj[i].h);
            glVertex3f ((float)Obj[i].x1, (float)Obj[i].y1, (float)Obj[i].z2);
         }
         glEnd ();
      }

      // Пол
      glBindTexture(GL_TEXTURE_2D, textures[MapFloor + 3]);
      glBegin (GL_QUADS);
      {
          glNormal3f (0, 1, 0);
          glTexCoord2f(0.0f, 0.0f);
          glVertex3f (-MapWidth, MapHeight, 0);
          glTexCoord2f(MapWidth*2.0f, 0.0f);
          glVertex3f (MapWidth, MapHeight, 0);
          glTexCoord2f(MapWidth*2.0f, MapHeight*2.0f);
          glVertex3f (MapWidth, -MapHeight, 0);
          glTexCoord2f(0.0f, MapHeight*2.0f);
          glVertex3f (-MapWidth, -MapHeight, 0);
      }
      glEnd ();

   glEndList ();
Вот так. Теперь в функции DrawMap бывший вывод карты изменим на вызов списка изображения. Функция должна принять такой вид:
//==============================================================================
// *** DrawMap ***
// Прорисовка карты
//==============================================================================
void DrawMap ()
{
   SkyX += float (SkyDX) / 100.0f * SPF;
   SkyY += float (SkyDY) / 100.0f * SPF;

   glClearColor (0, 0, 0, 1);
   glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   glLoadIdentity ();

   gluLookAt (0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0);
   glRotatef (-_Players[CurrPlr].Pitch, 1, 0, 0);
   glRotatef (_Players[CurrPlr].Yaw, 0, 0, 1);
   glTranslatef (-_Players[CurrPlr].X, -_Players[CurrPlr].Y, -_Players[CurrPlr].Z-1.5f);

   glCallList (TheMap);

   // Небо
   glBindTexture(GL_TEXTURE_2D, textures[MapSky + 6]);
   glBegin (GL_QUADS);
   {
       glNormal3f (0, 1, 0);
       glTexCoord2f(SkyX, SkyY);
       glVertex3f (-MapWidth, MapHeight, MapZ);
       glTexCoord2f(1.0f+SkyX, SkyY);
       glVertex3f (MapWidth, MapHeight, MapZ);
       glTexCoord2f(1.0f+SkyX, 1.0f+SkyY);
       glVertex3f (MapWidth, -MapHeight, MapZ);
       glTexCoord2f(SkyX, 1.0f+SkyY);
       glVertex3f (-MapWidth, -MapHeight, MapZ);
   }
   glEnd ();

   glFlush ();
   SwapBuffers(hDC);
}
Обратите внимание, что мы не поместили небо в список изображения. Причина очень проста: небо у нас динамическое (оно изменяется с ходом времени), а список изображения способен хранить в себе только статические изображения.