Глава 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 );//рисуем сферу с наложением //текстуры
Рисунок проецируется на сферу аналогично тому, как прямоугольная карта земного шара “заворачивала” бы глобус. То есть верхняя и нижняя кромки изображения текстуры после проецирования на сферу оказываются стянутыми в точку.