Глава 15. Наложение текстур в OpenGL

Текстура в OpenGL это рисунок или битовая карта (Bitmap), которая накладывается на грани поверхностей трехмерных объектов. Текстуры создают иллюзию рельефа на поверхности и придают объектам соответствующую раскраску. Например, если на поверхность сферы наложить изображение карты мира, то получится модель земного шара. Если же рисунок изменить на чередующиеся черные и зеленые полосы, то сфера уже будет больше похожа на арбуз. Безусловно, текстуры играют важнейшую роль при моделировании трехмерных объектов.

Размеры текстуры в OpenGL по ширине и высоте в пикселах должны быть степенью двух. Это требование связано с применением оптимизированных алгоритмов рисования текстур при разложении их в растр. Максимальный размер текстуры ограничен. В каждой реализации OpenGL его можно узнать вызовом функции

 

glGetIntegerv(GL_MAX_TEXTURE_SIZE,@GLMaxTex),

 

где переменная GLMaxTex типа GLInt после вызова будет содержать значение максимального размера текстуры в пикселах. Разрешение и запрещение наложения двумерных текстур достигается командами glEnable и glDisable c параметром GL_TEXTURE_2D.

Загрузка образа текстуры

Для загрузки битовой карты в качестве текущей текстуры используется функция glTexImage2D. Эта функция загружает образ битовой карты в оперативную память с помощью указателя. Прежде чем изучать параметры этой функции рассмотрим текст пользовательской функции LoadBmpTexture, написанной на Object Pascal, которая загружает текстуру из файла на диске.

 

function LoadBmpTexture(xSize,ySize: GLInt;Name: string): pointer;

type

TRGB = record

r,g,b: GLUByte;

end;

PBits = ^TBits;

TBits = Array [0..0] of GLUbyte;

var

i, j: Integer;

bitmap: TBitmap;

Size: GLInt;

Bits: PBits;

begin

bitmap := TBitmap.Create;

bitmap.LoadFromFile(Name); // загружаем текстуру из файла

Size:= xSize*ySize*SizeOf(TRGB);

GetMem(Bits,Size);

 

// заполнение битового массива

For i:= 0 to xSize-1 do

For j:= 0 to ySize-1 do

begin

bits[(i*xSize+j)*3+0]:= GetRValue(bitmap.Canvas.Pixels[j,xSize-1-i]);

bits[(i*xSize+j)*3+1]:= GetGValue(bitmap.Canvas.Pixels[j,xSize-1-i]);

bits[(i*xSize+j)*3+2]:= GetBValue(bitmap.Canvas.Pixels[j,xSize-1-i]);

end;

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,

xSize, ySize,// здесь задается размер текстуры

0, GL_RGB, GL_UNSIGNED_BYTE, bits);

Result:=bits;

//FreeMem(bits);

bitmap.Free;

end;//function LoadBmpTexture

 

Как видим, внутри функции LoadBmpTexture вызывается функция загрузки текстуры glTexImage2D. Особенность загрузки текстур состоит в том, что в образе битовой карты информация о пикселах хранится не в виде R-G-B, как это принято в формате изображений DIB, а наоборот B-G-R, то есть первой идет компонента синего цвета, затем зеленого и далее красного. Удобство функции LoadBmpTexture также состоит в том, что метод загрузки картинки из файла объекта TBitmap автоматически преобразует любые форматы DDB в универсальный формат DIB, так что нам остается только правильно поменять местами расположение байтов тройки RGB, что и происходит в двойном цикле:

 

For i:= 0 to xSize-1 do

For j:= 0 to ySize-1 do

begin

bits[(i*xSize+j)*3+0]:= GetRValue(bitmap.Canvas.Pixels[j,xSize-1-i]);

bits[(i*xSize+j)*3+1]:= GetGValue(bitmap.Canvas.Pixels[j,xSize-1-i]);

bits[(i*xSize+j)*3+2]:= GetBValue(bitmap.Canvas.Pixels[j,xSize-1-i]);

end;

 

Рассмотрим параметры функции glTexImage2D, как она использована в программе.

glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA,

xSize, ySize, 0, GL_RGB,

GL_UNSIGNED_BYTE, bits

);

Первый параметр в этой функции может принимать только значение символической константы GL_TEXTURE_2D. Второй параметр указывает значение уровня детализации текстуры. Уровень детализации задает уменьшение текстуры в заданное число раз с применением алгоритмов Mipmap. По умолчанию используется значение 0. Третий параметр задает количество цветовых компонент текстуры, может быть от 1 до 4. Четвертый и пятый параметры задают ширину и высоту текстуры. Ширина должна удовлетворять выражению 2n+2 x Border, где Border = 0 или 1, ширина границы текстуры, задается в следующем, шестом параметре. Седьмой параметр задает формат пикселов, может принимать следующие значения: GL_COLOR_INDEX, GL_RED, GL_GREEN, GL_BLUE, GL_ALPHA, GL_RGB, GL_RGBA, GL_LUMINANCE, GL_LUMINANCE_ALPHA. Предпоследний, восьмой параметр определяет значение типа данных для каждой пиксельной компоненты: GL_UNSIGNED_BYTE, GL_BYTE, GL_BITMAP, GL_UNSIGNED_SHORT, GL_SHORT, GL_UNSIGNED_INT, GL_INT, GL_FLOAT. Последний параметр представляет собой указатель на начало области данных текстуры. В нашем случае это переменная bits, описана как указатель на массив байт. Возможно, вас смутил тип данных

 

TBits = Array [0..0] of GLUbyte;

 

Действительно, такое описание выглядит как ошибка в программе. Однако, это сделано специально. Данная функция должна компилироваться с выключенной опцией на проверку допустимых диапазонов {$R-}. Тогда блок памяти bits в программе мы можем рассматривать как массив байт произвольной длины, и использовать обычный синтаксис Object Pascal для доступа к его элементам. Конечно, контроль выхода за пределы области памяти такого массива мы должны полностью брать на себя.

После загрузки образа текстуры в память следует указать грань или грани трехмерных объектов, на которые будет осуществляться наложение текстуры. Здесь мы подходим к определению координат текстуры. Необходимо установить соответствие между вершинами граней трехмерного объекта и местом на текстуре, которое каждой вершине соответствует. Начало системы координат текстуры расположено в левом нижнем углу прямоугольного рисунка текстуры. Ось s, подобно оси абсцисс направлена слева направо по нижней кромке рисунка, а ось t, аналогично оси ординат направлена снизу вверх. Границы прямоугольника изображения текстуры задают диапазон изменения значений координат s и t от 0 до 1.

Рассмотрим пример создания прямоугольника в трехмерном пространстве и наложение на него изображения корабля из файла Ship.bmp размером 64х64 пиксела. Здесь используется возможность создания так называемых дисплейных списков OpenGL, которые позволяют объединить длинные последовательности команд OpenGL под одним названием и запускать их на выполнение. Дисплейные списки позволяют между командами OpenGL выполнять и другие операторы, но, в отличие от обычных процедур и функций, запоминаются в списке только команды OpenGL. При последующем выполнении команд дисплейного списка обычные операторы не выполняются.

 

 

Const

Quad: GLInt=1;//идентификатор дисплейного списка

Ship: pointer;

. . .

glNewList(Quad,GL_COMPILE);//создаем новый дисплейный список

Ship:= LoadBmpTexture(64,64,'Ship.bmp');//загружаем текстуру в //память

glBegin (GL_QUADS);//задаем тип примитива - четырехугольники

glTexCoord2d (0.0, 0.0);//левый нижний угол текстуры

glVertex3f (-8.0, -8.0, 15.0);

glTexCoord2d (1.0, 0.0); //правый нижний угол текстуры

glVertex3f (8.0, -8.0, 22.0);

glTexCoord2d (1.0,0.8);//верхняя часть изображения будет слегка //приплюснута

glVertex3f (8.0, 0.0, 15.0);

glTexCoord2d (0.0, 1.0);//левый верхний угол текстуры

glVertex3f (-8.0, 8.0, 15.0);

glEnd;

glEndList;//конец создания списка

 

Рисование с использованием дисплейного списка происходит с помощью команды glCallList(Quad).

В случае Quadric объектов рассмотрим пример наложения изображения корабля на сферу.

 

//устанавливаем изображение корабля в качестве текущей текстуры

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,

64, 64, //размеры текстуры

0, GL_RGB, GL_UNSIGNED_BYTE, Ship);

 

glEnable(GL_TEXTURE_2D);//разрешаем использование текстур

gluQuadricTexture(Sphere,GL_TRUE);//разрешаем наложение текстуры на //объект Sphere

 

gluQuadricDrawStyle (Sphere, GLU_FILL);//сплошная закраска сферы

gluSphere(Sphere, 15.0, 24, 24 );//рисуем сферу с наложением //текстуры

 

Рисунок проецируется на сферу аналогично тому, как прямоугольная карта земного шара “заворачивала” бы глобус. То есть верхняя и нижняя кромки изображения текстуры после проецирования на сферу оказываются стянутыми в точку.