Создание 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
В следующем уроке мы создадим собственный формат уровней и научим нашу игру рисовать их и ходить по ним.