Использование текстур

advertisement
Использование текстур
Наложение
текстуры
на
поверхность
объектов
повышает
ее
реалистичность сцены, однако надо учитывать, что этот процесс требует
вычислительных затрат.
Для
работы
с
текстурой
следует
выполнить
следующую
последовательность действий:
1. выбрать изображение и преобразовать его к нужному формату;
2. передать изображение в OpenGL;
3. определить, как текстура будет наноситься на объект и как она будет
с ним взаимодействовать;
4. связать текстуру с объектом.
При создании образа текстуры в памяти следует учитывать следующие
требования:
Размеры текстуры, как по горизонтали, так и по вертикали должны
представлять собой степени двойки. Это необходимо для компактного
размещения
текстуры
в
памяти
и
способствует
ее
эффективному
использованию. Поэтому после загрузки текстуры надо преобразовать.
Во-вторых, после растеризации объект может оказаться по размерам
меньше наносимой на него текстуры. Поэтому вводится понятие уровней
детализации текстуры. (mipmap) Каждый уровень детализации задает
изображение, которое является уменьшенной в два раза копией оригинала.
Основной проблемой при работе с текстурами является их загрузка в
память. OpenGL не содержит готовых механизмов работы с графическими
файлами,
поэтому
можно
пользоваться
библиотеками
разработчиков, либо писать алгоритм загрузки текстуры вручную.
сторонних
Использование функции auxDIBImageLoad() библиотеки Glaux
Текстуру можно наложить на объект любой сложности. Для этого надо
разрешить
автоматически
генерировать
координаты
текстуры
-
glEnable(GL_TEXTURE_GEN_S) и glEnable(GL_TEXTURE_GEN_T).
Алгоритм генерации координат устанавливается с помощью функции
glTexGeni().
Первый параметр функции указывает тип координаты, для которой
будет установлен алгоритм:
GL_S -горизонтальная координата,
GL_T - вертикальная.
Второй
параметр
этой
функции
должен
быть
GL_TEXTURE_GEN_MODE – устанавливает режим генерации.
Третий параметр - алгоритмов генерации координат текстур.
GL_OBJECT_LINEAR
GL_EYE_LINEAR.
Добавляем ссылку на библиотеку для работы со сторонними файлами:
#include <stdio.h>
AUX_RGBImageRec *image1; //экземпляр текстуры 1
void Textur(){
glEnable(GL_AUTO_NORMAL);// разрешает расчет векторов нормалей
glEnable(GL_TEXTURE_2D); // делаем работу с текстурами доступной
image1=auxDIBImageLoad("sun.bmp"); //загрузка изображения из файла
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image4->sizeX, image4->sizeY,
GL_RGB, GL_UNSIGNED_BYTE, image4-> data);
/* Устанавливаем параметры самой текстуры (превращаем данные
изображения, загруженные из файла в текстуру, используемую в OpenGL):
Первый параметр говорит, что именно нужно строить, второй — кол-во
компонент цвета (1,2,3 или 4), третий и четвёртый — высота и ширина
изображения, пятый — тип цветовой схемы изображения, шестой – тип
данных, седьмой — указатель на данные изображения загружаемые из
файла.*/
glEnable(GL_TEXTURE_GEN_S);
//доступна
генерация
текстур
по
горизонтали
glEnable(GL_TEXTURE_GEN_T);// и вертикали
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP );
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE,GL_SPHERE_MAP );
auxSolidSphere(2);
}
Использование пользовательской функции для загрузки изображения
Для того чтобы загрузить файл, нужно знать его структуру. Структура
несжатого*.bmp файла крайне проста, она состоит из двух заголовков и
массива пикселей. Заголовки
файла
представляют собой
структуры
содержащие информацию о содержимом файла (размеры изображения, метод
сжатия, кол-во байтов на пиксел и т.д.). При работе с файлами *.bmp нужно
учесть, что цвет пиксела хранится в формате BGR.
Для удобства работы с изображением после его загрузки создаём
структуру следующего вида:
typedef struct BMPIMG
{unsigned char *imageData; /* указатель на первый байт массива пикселов
изображения, содержащих три компоненты цвета в формате RGB*/
unsigned long width; /* ширина изображения. (в пикселах)
unsigned long height; /* высота изображения. (в пикселах)
}BITMAPIMAGE, *PBITMAPIMAGE;
Для загрузки спроектируем функцию, которая в случае удачного
завершения возвращает значение true.
bool LoadBMPIMG (const char *fName, PBITMAPIMAGE bmi)
/*Первый параметр — путь к файлу, второй — указатель на структуру
нашей текстуры*/
{
BITMAPFILEHEADER bmph; /*структура заголовка файла bmp*/
BITMAPINFOHEADER infh; /*структура информационного заголовка файла
bmp*/
unsigned char BMPheader[2] = {66, 77}; /*Код типа файла для проверки на
соответствие формату bmp*/
FILE *file = fopen(fName, "rb"); /*открыть файл (fName,) для чтения; *file —
дескриптор файла*/
fread(&bmph,1,sizeof(BITMAPFILEHEADER), file); /* прочитать заголовок*/
fread(&infh,
1,
sizeof(BITMAPINFOHEADER),
file);
/*прочитать
информационный заголовок файла*/
int bPP = infh.biBitCount/8; /* расчёт кол-ва байт на пиксел (файл содержит
только информацию о кол-ве бит на пиксел)*/
int imgSize = infh.biWidth*infh.biHeight*bPP; /* размер изображения в
байтах: Ширина * Высота * Кол-во байт на пиксел*/
bmi->imageData = (unsigned char *)malloc(imgSize); /* Выделим память для
хранения данных изображения*/
int size = fread(bmi->imageData, 1, imgSize, file);/* читать данные из файла
в выделенную память*/
/*организуем цикл для замены BGR to RGB - обмениваем R и B компоненты
цвета*/
for(unsigned int w = 0; w < imgSize; w += bPP)
{
bmi->imageData[w] ^= bmi->imageData[w+2] ^= bmi->imageData[w] ^=
bmi->imageData[w+2];
fclose(file); /*изображение загружено, файл можно закрыть*/
bmi->width = infh.biWidth;/*Отправляем в нашу структуру данные о
ширине*/
bmi->height = infh.biHeight; /* и высоте файла*/
}
В
результате
работы
этой
функции
поле
imageData
структуры
BITMAPIMAGE будет содержать данные текстуры, которые может
использовать OpenGL.
Использование загруженного изображения как текстуры
Для этого в программе нужно выполнить следующие операции:

объявить структуру BMPIMG для загрузки в неё изображения (см.
выше);

объявить переменную для хранения номера (идентификатора)
текстуры. Это число будет использоваться для обращения к текстуре, если их
несколько;

разрешить использование текстур;

сделать загружаемую текстуру активной;

загрузить изображение функцией LoadBMPIMG();

задать выравнивание по байту (в данном примере с форматом bmp
используется выравнивание по байту, если вы работаете с другим форматом
оно может быть другим);

установить параметры вывода текстуры;

установить параметры фильтрации текстуры.
BMPIMG bmpimg1; /*объявление экземпляра структуры (переменная, с
которой мы будем работать в программе)*/
GLuint bmpimg1_ID;/ *объявление переменной для хранения идентификатора
структуры*/
После команды ShowWindow() или в функции, где планируется использовать
текстуру:
glEnable(GL_TEXTURE_2D); /*разрешаем работу с текстурами*/
glGenTextures(1, &bmpimg1_ID); /* генерируем идентификатор
Эта функция принимает 2 параметра. Первый — кол-во идентификаторов,
которые нужно сгенерировать, второй адрес массива (или переменной, как в
этом случае) для хранения идентификаторов.*/
glBindTexture(GL_TEXTURE_2D,
получения
bmpimg1_ID);/*После
идентификатора, делаем эту текстуру активной.*/
LoadBMPIMG ("texbmp2.bmp", &bmpimg1); /*Загружаем данные из файла в
нашу структуру данных изображения.*/
glPixelStorei(GL_UNPACK_ALIGNMENT,
1);
/*Устанавливаем
выравнивание по байту. Функция glPixelStorei используется для установки
режима хранения изображения в памяти, у неё много параметров задающих
различные
режимы,
нам
для
работы
нужен
только
один
GL_UNPACK_ALIGNMENT — выравнивание при распаковке.*/
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, bmpimg1.width, bmpimg1.height,
GL_RGB, GL_UNSIGNED_BYTE, bmpimg1.imageData); /* Устанавливаем
параметры
самой
текстуры
(превращаем
данные
изображения
загруженные из файла в текстуру используемую в OpenGL):
Первый параметр говорит, что именно нужно строить, второй — кол-во
компонент цвета (1,2,3 или 4), третий и четвёртый — высота и ширина
изображения, пятый — тип цветовой схемы изображения, шестой —
указатель на данные изображения загружаемые из файла.*/
После выполнения этой команды можно спокойно очистить структуру
bmpimg1.imageData, или использовать её для загрузки следующей текстуры,
функция gluBuild2DMipmaps превращает данные изображения в текстуру,
которая хранится в видео памяти. Так что совсем не обязательно
использовать разные структуры для разных файлов, для загрузки можно
использовать одну и ту же структуру, разными должны быть только
идентификаторы текстуры.
Последними устанавливаются параметры фильтрации текстуры.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_LINEAR);
Параметры фильтрации — это алгоритм, который используется для
увеличения или уменьшения изображения при масштабировании сцены или
при наложении текстуры на объект не соответствующий её размерам.
Алгоритм масштабирования задаётся отдельно для увеличения и уменьшения
изображения.
Обычно
используют
параметры
GL_LINEAR
или
GL_NEAREST. При значении GL_NEAREST будет использоваться один
(ближайший), а при значении GL_LINEAR четыре ближайших элемента
текстуры. Использование режима GL_NEAREST повышает скорость
наложения текстуры, однако при этом снижается качество, так как в отличие
от GL_LINEAR интерполяция не производится.
Наложение текстуры на полигон
При наложении текстуры главное - правильно указать координаты
текстуры относительно вершин полигона. Координаты текстуры задаётся
функцией glTexCoord2d(0.0f, 0.0f); указывающей положение текстуры на
полигоне. Координаты задаётся в диапазоне от 0 до 1. Значения задаются
пропорционально размерам текстуры, если задано значение меньше 1, это
означает, что будет выводиться одна четвертая изображения, если больше 1,
то будет увеличено кол-во повторов изображения на полигоне.
Пример:
glEnable(GL_TEXTURE_2D);
glColor3f(1,1,1);
glBindTexture(GL_TEXTURE_2D, bmpimg1_ID);
glBegin(GL_QUADS);
glTexCoord2d(0.0f, 0.0f); glVertex3f( -10.0, -10.0, -1);
glTexCoord2d(0.0f, 1.0f); glVertex3f( -10.0, 10.0, -1);
glTexCoord2d(1.0f, 1.0f); glVertex3f( 10.0, 10.0, -1);
glTexCoord2d(1.0f, 0.0f); glVertex3f( 10.0, -10.0, -1);
glEnd();
glDisable(GL_TEXTURE_2D);
Перед
тем
как
накладывать
текстуры
нужно
разрешить
их
использование, а по окончании запретить, иначе OpenGL будет пытаться
наложить текущую текстуру на все остальные объекты.
В примере текстура накладывается на квадрат, перед указанием
вершины квадрата указывается координата текстуры. При указании
координат, главное не перепутать где какая вершина.
Для имитации движущейся текстуры (например, волна), при построении
фигуры можно организовать следующий цикл:
static float a = 0,i = 0,di=0.1;
glBegin(GL_QUADS);
a+=0.01;
for(i=-20; i<0;i+=di)
{glTexCoord2d(i /20.0,
glTexCoord2d(i /20.0,
0.0 ); glVertex3f(i,
1.0); glVertex3f(i,
-10,sin(a+i) -5);
10,sin(a+i) -5);
glTexCoord2d((i + di)/20.0, 1.0); glVertex3f(i+di, 10,sin(a+i+dx)-5);
glTexCoord2d((i + di)/20.0, 0.0); glVertex3f(i+di, -10,sin(a+i+dx)-5);
}
glEnd();
Для придания большей реалистичности можно воспользоваться
эффектом тумана или прописать нормали к поверхности.
Нормали
Вектор нормали (нормаль) – вектор, перпендикулярный поверхности.
Для плоских поверхностей это направление одинаково для всех точек, но для
кривых поверхностей оно может быть различным. В любой точке
поверхности есть два перпендикулярных противоположно направленных
вектора. Нормалью считается направленный наружу вектор. Векторы
нормали определяют только направление, и их длина не имеет значения.
Нормали задаются для вершин полигонов командой:
glNormal3f(GLfloat x, GLfloat y, GLfloat z);
которая вызывается внутри блока glBegin/glEnd перед описанием
соответствующей вершины.
Они могут быть любой длины, но перед вычислением освещенности
все
они
преобразуются
к
единичной
длине
функцией
glEnable(GL_NORMALIZE), в которой каждая составляющая вектора x, y, z
делится на длину нормали √𝑥 2 +𝑦 2 +𝑧 2 .
Пример (перетекающая поверхность)
float x,z,a
void L()
{a+=0.01;
glEnable(GL_NORMALIZE);
for (z=-5;z<=5;z+=0.5)
for (x=0;x<=2*pi*3;x+=0.5)
{glBegin(GL_POLYGON);
glColor3f(0.5,0.5,0.3);
glNormal3f(x, sin(x+a),z);
glVertex3f(x,sin(x+a),z);
glNormal3f(x+0.5,sin(x+0.5+a),z);
glVertex3f(x+0.5,sin(x+0.5+a),z);
glNormal3f(x+0.5,sin(x+0.5+a),z+0.5);
glVertex3f(x+0.5,sin(x+0.5+a),z+0.5);
glNormal3f(x,sin(x+a),z+0.5);
glVertex3f(x,sin(x+a),z+0.5);
glEnd();}
glDisable(GL_NORMALIZE); }
Для сглаживания поверхности, построенной из полигонов используетс
функция glEnable(GL_POINT_SMOOTH);
Однако применение этого режима уменьшает скорость работы
механизма визуализации OpenGL, так как нормализация векторов имеет
заметную вычислительную сложность (взятие квадратного корня и т.п.).
Поэтому лучше сразу задавать единичные нормали.
Пример построения поверхности из треугольников
void SetNormalVector(float x1,float y1,float z1,
float x2,float y2,float z2,
float x3,float y3,float z3, int vert)
{
float x,y,z,vector;
x = ((y2-y1)*(z3-z1)-(y3-y1)*(z2-z1));
y = -((x2-x1)*(z3-z1)-(x3-x1)*(z2-z1));
z = ((x2-x1)*(y3-y1)-(x3-x1)*(y2-y1));
vector=sqrt(x*x+y*y+z*z);
glNormal3f(x/vector*vert,y/vector*vert,z/vector*vert);
}
static float i = 0, j = 0;
static float di = 0.1, dj = 0.1;
/* Функция построения поверхности */
void func1(void)
{
for(i=-10; i < 9; i+=di)
for(j=-10; j < 9; j+=dj)
{
glBegin(GL_TRIANGLES);
SetNormalVector(i, (sin(i)+sin(j))/2, j,
i+di, (sin(i+di)+sin(j))/2, j,
i+di, (sin(i+di)+sin(j+dj))/2, j+dj, -1);
glVertex3f(i, (sin(i)+sin(j))/2, j);
glVertex3f(i+di, (sin(i+di)+sin(j))/2, j);
glVertex3f(i+di, (sin(i+di)+sin(j+dj))/2, j+dj);
SetNormalVector(i, (sin(i)+sin(j))/2, j,
i, (sin(i)+sin(j+dj))/2, j+dj,
i+di, (sin(i+di)+sin(j+dj))/2, j+dj, 1);
glVertex3f(i, (sin(i)+sin(j))/2, j);
glVertex3f(i, (sin(i)+sin(j+dj))/2, j+dj);
glVertex3f(i+di, (sin(i+di)+sin(j+dj))/2, j+dj);
glEnd();
} }
Download