Создание 3D игр на C++
Часть1. Урок 7
Создаем трехмерную сцену. Шаг 3 - Текстурирование


Сегодня мы научимся накладывать на объекты текстуры, и на этом наше обучение рисованию с помощью OpenGL подойдет временно к концу. Со следующего урока мы вернемся к нашему 3D Action и попытаемся применить полученные знания.
Пока что же придется довольствоваться нашим кубиком. Наложим мы на него картинку кирпичной стены. Сейчас и всегда брать текстуры мы будем из файлов с расширением .bmp. Если у вас нет картинки с изображением кирпичной стены, вы можете скачать ее отсюда:
wall.bmp


Загрузка текстур
Так как текстуры хранятся в bmp файлах, что разумно бы было их загружать в объект класса Graphics::TBitmap. Так мы, в общем-то, и поступим. Но OpenGL совсем не понятен формат TBitmap. Ему необходимо передавать последовательность байтов, кодирующих цвета точек. Каждая точка кодируется четырьмя байтами. При этом четыре последовательных байта передают, соответственно, красную, зеленую, синюю и альфа составляющие цвета. Последняя говорит о прозрачности цвета (это нам предстоит использовать в будущем). Самый простой (хоть и несколько накладный) способ конвертировать формат TBitmap в массив точек - это просто в цикле брать пиксели из массива TBitamp->Pixels.
Теперь ознакомимся с функциями, необходимыми для инициализации текстур.

glPixelStorei(GL_UNPACK_ALIGNMENT, 4);

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

Дальше нам надо создать переменные, которые будут хранить ссылки на текстуры.
Откройте наш проект с кубиком и в файле Unit1.h напишите:

GLuint Texture [1];

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

glGenTextures(1, &Texture [0]);

Затем мы должны сделать нашу текстуру активной. Вся работа, будь то иницалищация или уже накладывание готовой текстуры, производится только с текущей текстурой.

glBindTexture(GL_TEXTURE_2D, Texture [0]);

Зетем мы устанавливаем параметры текстуры:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

Функция glTexParameteri устанавливает различные атрибуты текстуры. Первый параметр должен быть GL_TEXTURE_2D, второй - это какой атрибут надо манять, третий - это значение, кооторое надо атрибуту присвоить.
Мы меняем четыре атрибута:
GL_TEXTURE_WARP_S и GL_TEXTURE_WARP_T - почти всегда устанавливаются в одинаковые значение. Означают, как должна повести себя текстура, если ее накладывают на объект, который превышает ее по размерам. Если значение GL_REPEAT, то текстура будет повторяться. Если значение GL_CLAMP, то текстура будет наложена однажды, а на оставшуюся не затронутой часть объекта будет размножена ее граница (то есть крайний ряд пикселей).
GL_TEXTURE_MAG_FILTER и GL_TEXTURE_MIN_FILTER говорят о качестве наложенных текстур. MAG_FILTER может быть только GL_NEAREST или GL_LINEAR. MIN_FILTER имеет еще несколько возсожных значений, но мы пока не будет на них заострять внимание. Разберем только первые два. GL_LINEAR значит, что каждая точка готовго изображния будет определена как среднее взвешенное четырех точек, наиболее близких к этой точке на экране. GL_NEAREST будет определять точки на экране одной точкой текстуры. Это дает большую скорость, но заметно отобирает в качестве, придавая текстурам ступенчатость. Мы будем использовать GL_LINEAR.

Теперь определим саму функцию:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, bits);

Первый параметр, опять же, должен быть GL_TEXTURE_2D. Второй - 0, третий - GL_RGBA (это говорит о том, что четыре последовательных байта содержат R, G, B и A компоненты цвета), пятый и шестой - это ширина и высота текстуры, седьмой - это, опять же, GL_RGBA, восьмой - тип каждого элемента в массиве текстуры, а восьмой - это ссылка на сам массив.

Данные операции нам предстоит выполнять над каждой текстурой. В конце инициализации пишем заключительную строку:

glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

Теперь все это объединим. Напишем функцию, которая будет нам загружать текстуру стены из файла Wall.bmp. У меня эта текстура имеет размер 64*64. Вы можете взять текстуру любого другого размера, просто измените значения переменных w и h:
//==============================================================================
// Загрузка текстур
void __fastcall TForm1::InitTex ()
{
   int w=64, h=64;

   Graphics::TBitmap *bitmap;
   bitmap = new Graphics::TBitmap;


   bitmap->LoadFromFile ("wall.bmp");

   GLubyte bits[128*128][4];
   for(int i = 0; i < h; i++)
   {
     for(int j = 0; j < w; j++)
       {
           bits[j+w*i][0]= (GLbyte)GetRValue(bitmap->Canvas->Pixels[j][i]);
           bits[j+w*i][1]= (GLbyte)GetGValue(bitmap->Canvas->Pixels[j][i]);
           bits[j+w*i][2]= (GLbyte)GetBValue(bitmap->Canvas->Pixels[j][i]);
           bits[j+w*i][3]= (GLbyte)255;
       }
   }
   glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
   glGenTextures(1, &Texture [0]);
   glBindTexture(GL_TEXTURE_2D, Texture [0]);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, bits);


   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
}
Наложение текстур

Это значительно более простая операция. Все, что нам надо - это выбрать текущую текстуру, как мы это прежде делали:

glBindTexture (GL_TEXTURE_2D, Texture [0]);

Но до этого надо включить наложение текстур:

glEnable (GL_TEXTURE_2D);

После этого, при рисовании вертекса надо указывать координаты текстуры. Координата задается двумя переменными: x и y. При этом, в вертексе с координатами текстуры 0,0 будет располагаться левый верхний угол текстуры, а в вертексе с координатами 1,1 будет располагаться правый нижний угол. В начале мы установили режим, благодаря которому при использовании координат меньше 0 и больше 1 текстура будет размножаться.
Устанавливаюстя координаты с помощью функции

glTexCoord2f (GLfloat x, GLfloat y);

Разумеется, при использовании текстур можно полностью отказаться от использования цветов, убрав все вызовы glColor*
Вот как нам надо доработать отображение нашего кубика:
   if (IsWire) glBegin (GL_LINE_STRIP);
   else glBegin (GL_QUADS);

   {  // Нижняя стенка
      glNormal3f (0, 0, -1);
      glTexCoord2f (0, 0);
      glVertex3f (-1, -1, -1);
      glTexCoord2f (1, 0);
      glVertex3f (-1, 1, -1);
      glTexCoord2f (1, 1);
      glVertex3f (1, 1, -1);
      glTexCoord2f (0, 1);
      glVertex3f (1, -1, -1);
   }
   glEnd ();

   if (IsWire) glBegin (GL_LINE_STRIP);
   else glBegin (GL_QUADS);
   {  // Верхняя стенка
      glNormal3f (0, 0, 1);
      glTexCoord2f (0, 0);
      glVertex3f (-1, -1, 1);
      glTexCoord2f (1, 0);
      glVertex3f (-1, 1, 1);
      glTexCoord2f (1, 1);
      glVertex3f (1, 1, 1);
      glTexCoord2f (0, 1);
      glVertex3f (1, -1, 1);
   }
   glEnd ();

   if (IsWire) glBegin (GL_LINE_STRIP);
   else glBegin (GL_QUADS);
   {  // Правая стенка
      glNormal3f (1, 0, 0);
      glTexCoord2f (0, 0);
      glVertex3f (1, -1, -1);
      glTexCoord2f (1, 0);
      glVertex3f (1, 1, -1);
      glTexCoord2f (1, 1);
      glVertex3f (1, 1, 1);
      glTexCoord2f (0, 1);
      glVertex3f (1, -1, 1);
   }
   glEnd ();

   if (IsWire) glBegin (GL_LINE_STRIP);
   else glBegin (GL_QUADS);
   {  // Левая стенка
      glNormal3f (-1, 0, 0);
      glTexCoord2f (0, 0);
      glVertex3f (-1, -1, -1);
      glTexCoord2f (1, 0);
      glVertex3f (-1, 1, -1);
      glTexCoord2f (1, 1);
      glVertex3f (-1, 1, 1);
      glTexCoord2f (0, 1);
      glVertex3f (-1, -1, 1);
   }
   glEnd ();

   if (IsWire) glBegin (GL_LINE_STRIP);
   else glBegin (GL_QUADS);
   {  // Передняя стенка
      glNormal3f (0, 1, 0);
      glTexCoord2f (0, 0);
      glVertex3f (-1, 1, -1);
      glTexCoord2f (1, 0);
      glVertex3f (1, 1, -1);
      glTexCoord2f (1, 1);
      glVertex3f (1, 1, 1);
      glTexCoord2f (0, 1);
      glVertex3f (-1, 1, 1);
   }
   glEnd ();

   if (IsWire) glBegin (GL_LINE_STRIP);
   else glBegin (GL_QUADS);
   {  // Задяя стенка
      glNormal3f (0, -1, 0);
      glTexCoord2f (0, 0);
      glVertex3f (-1, -1, -1);
      glTexCoord2f (1, 0);
      glVertex3f (1, -1, -1);
      glTexCoord2f (1, 1);
      glVertex3f (1, -1, 1);
      glTexCoord2f (0, 1);
      glVertex3f (-1, -1, 1);
   }
   glEnd ();
Кроме того, из конца инициализации лучше выкинуть строку IsWire = true, а в начало добавить
::SetCursorPos (Screen->Width/2, Screen->Height/2);
Чтоб кубик показывался закрашенным, и при этом мышка сразу появлялась в центре (тогда сцена не повернется сразу, и кубик будет видно).

tex.zip

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