1.启用纹理和载入纹理
二维纹理的启用和载入:
glEnable(GL_TEXTURE_2D); // 启用二维纹理
glDisable(GL_TEXTURE); // 禁用二维纹理
载入纹理:
glTextImage2D // 载入一个二维纹理 九个参数
1. 参数为指定目标 ,可以使用 GL_TEXTURE_2D
2. .多重细节层次
3. OpenGL 1.0 中:
使用 RGB 颜色表示 参数设置为3
使用RGBA 颜色表示 参数设置为4
后来的版本中使用:GL_RGB 或 GL_RGBA 表示以上情况
4.5. 是二维纹理像素的宽度和高度
尽量使用2的整数次方的纹理。
无法满足需求时候,使用gluScaleImage 把函数图像缩放至所指定大小
6.是纹理边框的大小
7.8.9 与glDrawPixels 函数的最后三个参数的使用方法相同。含义参考glReadPixels
函数
2.纹理坐标
只要指定每一个顶点在纹理图像中所对应的像素位置,OPenGL就会自动计算顶点
以外的其他点在纹理图像中所对应的像素位置。
以二维纹理为例,规定纹理最左下角的坐标为(0,0),最右上角的坐标为(1,1),于是纹理中
的每一个像素的位置都可以用两个浮点数来表示(三维纹理会用三个浮点数表示)。
使用glTextCoord* 系列函数来指定纹理坐标。用法与使用glVertex*系列函数指定坐标十
分相似。例如glTexCoord2f(0.0f,0.0f); 指定使用(0,0) 纹理坐标。
纹理坐标也可以进行坐标转换。
使用glMatrixMode(GL_TEXTURE) 就可以切换到纹理矩阵。另外还有透视矩阵
GL_PROJECTION 和模型视图矩阵 GL_MODELVIEW, 然后glRotate*,glScale*,
glTranslate* 等操作矩阵的函数就可以用来处理 对纹理坐标进行转换。
3. 纹理参数
使用glTexParameter* 系列函数来设置纹理参数。
通常四个参数:
GL_TEXTURE_MAG_FILTER: 指当纹理图像被使用到一个大于它的形状上(例如
将一副256 * 256 的纹理图像应用到一个 512 * 512 的正方形)。
GL_TEXTURE_MIN_FILTER:指当纹理图象被使用到一个小于(或等于)它的形状
上时(即有可能纹理图象中的多个像素被应用到实际绘制时的一个像素。
GL_TEXTURE_WRAP_S: 指当纹理坐标的第一维坐标值大于 1.0 或小于 0.0 时.
GL_TEXTURE_WRAP_T: 指当纹理坐标的第二维坐标值大于 1.0 或小于 0.0 时
4. 纹理对象
载入一幅纹理所需要的时间是比较多的。因此应该尽量减少载入纹理的次数。如果只有
一幅纹理,则应该在第一次绘制前就载入它,以后就不需要再次载入了。这点与
glDrawPixels 函数很不相同。每次使用 glDrawPixels 函数,都
需要把像素数据重新载入一次,因此用 glDrawPixels 函数来反复绘制图象的效率是较
低的(如果只绘制一次,则不会有此问题),使用纹理来反复绘制图象是可取的做法。
//在每次绘制时要使用两幅或更多幅的纹理时,这个办法就行不通了。你可能会编写下面的代码:
glTexImage2D( /* ... */ ); // 载入第一幅纹理
// 使用第一幅纹理
glTexImage2D( /* ... */ ); // 载入第二幅纹理
// 使用第二幅纹理
// 当纹理的数量增加时,这段代码会变得更加复杂。
/*
在绘制动画时,由于每秒钟需要将画面绘制数十次,因此如果使用上面的代码,就会反复载入纹理,这对计算机是非常大的
负担,以目前的个人计算机配置来说,根本就无法让动画能够流畅的运行。因此,需要有一种机制,能够在不同的纹理之间
进行快速的切换。
纹理对象正是这样一种机制。我们可以把每一幅纹理(包括纹理的像素数据、纹理大小等信息,也包括了前面所讲的纹理参
数)放到一个纹理对象中,通过创建多个纹理对象来达到同时保存多幅纹理的目的。这样一来,在第一次使用纹理前,把所
有的纹理都载入,然后在绘制时只需要指明究竟使用哪一个纹理对象就可以了。
*/
// 使用纹理对象和使用显示列表有相似之处:使用一个正整数来作为纹理对象的编号。在使用前,可以调用 glGenTextures 来分配纹理对象。该函数有两种比较常见的用法:
GLuint texture_ID;
glGenTextures(1, &texture_ID); // 分配一个纹理对象的编号
// 或者:
GLuint texture_ID_list[5];
glGenTextures(5, texture_ID_list); // 分配 5 个纹理对象的编号
/*
零是一个特殊的纹理对象编号,表示“默认的纹理对象”,在分配正确的情况下,
glGenTextures 不会分配这个编号。与 glGenTextures 对应的是 glDeleteTextures,用于销毁一个纹理对象。
*/
/*
在分配了纹理对象编号后
使用 glBindTexture 函数来指定“当前所使用的纹理对象
然后就可以使用 glTexImage*系列函数来指定纹理像素
使用 glTexParameter*系列函数来指定纹理参数
使用 glTexCoord*系列函数来指定纹理坐标
如果不使用 glBindTexture 函数
那么 glTexImage*、glTexParameter*、glTexCoord*系列函数默认在一个
编号为 0 的纹理对象上进行操作
glBindTexture 函数有两个参数,第一个参数是需要使用纹理的目标,因为
我们现在只学习二维纹理,所以指定为 GL_TEXTURE_2D,第二个参数是所使
用的纹理的编号
*/
/*
使用多个纹理对象,就可以使 OpenGL 同时保存多个纹理。在使用时只需要调用
glBindTexture 函数,在不同纹理之间进行切换,而不需要反复载入纹理,因此
动画的绘制速度会有非常明显的提升。典型的代码如下所示:
*/
// 在程序开始时:分配好纹理编号,并载入纹理
glGenTextures( /* ... */ );
glBindTexture(GL_TEXTURE_2D, texture_ID_1);
// 载入第一幅纹理
glBindTexture(GL_TEXTURE_2D, texture_ID_2);
// 载入第二幅纹理
// 在绘制时,切换并使用纹理,不需要再进行载入
glBindTexture(GL_TEXTURE_2D, texture_ID_1); // 指定第一幅纹理
// 使用第一幅纹理
glBindTexture(GL_TEXTURE_2D, texture_ID_2); // 指定第二幅纹理
// 使用第二幅纹理
示例代码
/*
如果要运行的话,除
了要保证有一个名称为 dummy.bmp,图象大小为 1*1 的 24 位 BMP 文
件,还要把本课开始的两幅纹理图片保存到正确位置 一
幅名叫 ground.bmp,另一幅名叫 wall.bmp
*/
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#define WindowWidth 400
#define WindowHeight 400
#define WindowTitle "OpenGL纹理测试"
#define BMP_Header_Length 54
// 函数 grap 抓取窗口中的像素
// 假设窗口宽度为 WindowWidth 高度 WindowHeight
void grab(void)
{
FILE* pDummyFile;
FILE* pWritingFile;
GLubyte* pPixelData;
GLubyte BMP_Header[BMP_Header_Length];
GLint i, j;
GLint PixelDataLength;
// 计算像素数据的实际长度
i = WindowWidth * 3; //得到每一行的像素数据长度
while (i % 4 != 0) // 补充数据,直到i 四的倍数
{
++i;
}
// 计算补充后的数据长度
PixelDataLength = i * WindowHeight;
// 分配内存 和打开文件
pPixelData = (GLubyte*)malloc(PixelDataLength);
if(pPixelData == 0) // 内存分配失败则推出程序
{
exit(0);
}
pDummyFile = fopen("dummy.bmp","rb"); // 打开 dummy.bmp 读 二进制形式
if (pDummyFile == 0)
{
exit(0);
}
pWritingFile = fopen("grab.bmp","wb"); // 打开grap.bmp 文件
if (pWritingFile == 0)
{
exit(0);
}
// 读取像素
// glPixelStorei 参数1 表示设置像素对齐 参数2 表示对齐的值
glPixelStorei(GL_UNPACK_ALIGNMENT,4);
// 从帧缓冲区读取像素到 当前 GL_PIXEL_PACK_BUFFER 或内存
// 左下角 坐标 (0,0)
// WindowWidth 宽度 WindowHeight 高度
// GL_BGR_EXT 读入的数据格式
// GL_UNSIGNED_BYTE 保存的 数据格式
// 数据被保存到这个地址
glReadPixels(0,0,WindowWidth,WindowHeight,GL_BGR_EXT,GL_UNSIGNED_BYTE,pPixelData);
// 把dummy.bmp 的文件复制为新文件的 文件头
// fread 函数每次从stream 中读取1 个单元,每个单元大小为 size个字节
fread(BMP_Header,sizeof(BMP_Header), 1, pDummyFile);
fwrite(BMP_Header,sizeof(BMP_Header), 1, pWritingFile);
// 偏移 18 个字节
fseek(pWritingFile,0x0012,SEEK_SET);
// i 和 j 一共8个字节
i = WindowWidth;
j = WindowHeight;
fwrite(&i, sizeof(i), 1, pWritingFile);
fwrite(&j,sizeof(j),1,pWritingFile);
// 写入像素数据
fseek(pWritingFile,0,SEEK_END);
fwrite(pPixelData,PixelDataLength,1,pWritingFile);
// 释放内存和关闭文件
fclose(pDummyFile);
fclose(pWritingFile);
free(pPixelData);
}
int power_of_two(int n)
{
/*
检查一个整数是为 2 的整数次方
*/
if (n < 0)
{
return 0;
}
return (n & (n - 1)) == 0;
}
// 读取宽度信息
GLuint load_texture(const char* file_name)
{
GLint width, height, total_bytes;
GLubyte* pixels = 0;
GLuint last_texture_ID, texture_ID = 0;
// 打开文件,如果失败,返回
FILE* pFile = fopen(file_name,"rb");
if (pFile == 0)
{
return 0;
}
// 读取文件中图像的宽度和高度
fseek(pFile,0x0012,SEEK_SET);
fread(&width,4,1,pFile);
fread(&height,4,1,pFile);
fseek(pFile,BMP_Header_Length,SEEK_SET);
// 计算每行像素所占字节数 并根据次数据计算总像素字节数
{
GLint line_bytes = width * 3;// 乘以 3 因为 rgb 颜色
while (line_bytes % 4 != 0)
{
++line_bytes;
}
total_bytes = line_bytes * height;
}
// 根据总像素字节数分配内存
pixels = (GLubyte*)malloc(total_bytes);
if (pixels == 0)
{
fclose(pFile);
return 0;
}
// 读取像素数据
// 从 pFile 读取 到缓冲区
if (fread(pixels,total_bytes,1,pFile) <= 0)
{
free(pixels);
fclose(pFile);
return 0;
}
{
// 获取最大支持纹理
GLint max;
// 这样max 就是当前 OpenGL所支持的最大纹理
glGetIntegerv(GL_MAX_TEXTURE_SIZE,&max);
if ( !power_of_two(width) || !power_of_two(height) || width > max || height > max)
{
const GLint new_width = 256;
const GLint new_height = 256; // 规定缩放后新的大小为边长的正方形
GLint new_line_bytes, new_total_bytes;
GLubyte* new_pixels = 0;
// 计算每行需要的字节数和总字节数
new_line_bytes = new_width * 3;
while (new_line_bytes % 4 == 0)
{
++new_line_bytes;
}
new_total_bytes = new_line_bytes * new_height;
// 分配内存
new_pixels = (GLubyte*)malloc(new_total_bytes);
if (new_pixels == 0)
{
free(pixels);
fclose(pFile);
return 0;
}
// 进行像素缩放
gluScaleImage(GL_RGB,width,height,GL_UNSIGNED_BYTE, pixels,
new_width, new_height, GL_UNSIGNED_BYTE, new_pixels);
// 释放原来的像素数据,把 pixels 指向新的像素数据,并重新设置 width 和 height
free(pixels);
pixels = new_pixels;
width = new_width;
height = new_height;
}
}
// 分配一个新的纹理编号
glGenTextures(1, &texture_ID);
if (texture_ID == 0)
{
free(pixels);
fclose(pFile);
return 0;
}
// 绑定新的纹理,载入纹理并设置纹理参数
// 在绑定前,先获得原来绑定的纹理编号,以便在最后进行恢复
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture_ID);
// 指定当前所使用的纹理对象
glBindTexture(GL_TEXTURE_2D, texture_ID);
// 指定纹理参数
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
// 指定纹理像素 载入纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0,
GL_BGR_EXT, GL_UNSIGNED_BYTE, pixels);
// 恢复纹理编号
glBindTexture(GL_TEXTURE_2D, last_texture_ID);
// 之前为 pixels 分配的内存可在使用 glTexImage2D 以后释放
// 因为此时像素数据已经被 OpenGL 另行保存了一份(可能被保存到专门的图形硬件中)
free(pixels);
return texture_ID;
}
GLuint texGround;
GLuint texWall;
void display(void)
{
// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// 设置视角
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(75, 1, 1, 21);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(1, 5, 5, 0, 0, 0, 0, 0, 1);
// 使用“地”纹理绘制土地
glBindTexture(GL_TEXTURE_2D, texGround);
glBegin(GL_QUADS);
// 指定纹理坐标
glTexCoord2f(0.0f, 0.0f); glVertex3f(-8.0f, -8.0f, 0.0f);
glTexCoord2f(0.0f, 5.0f); glVertex3f(-8.0f, 8.0f, 0.0f);
glTexCoord2f(5.0f, 5.0f); glVertex3f(8.0f, 8.0f, 0.0f);
glTexCoord2f(5.0f, 0.0f); glVertex3f(8.0f, -8.0f, 0.0f);
glEnd();
// 使用“墙”纹理绘制栅栏
// 使用“墙”纹理绘制栅栏
glBindTexture(GL_TEXTURE_2D, texWall);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 1.0f); glVertex3f(6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
glEnd();
// 旋转后再绘制一个
glRotatef(-90, 0, 0, 1);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f); glVertex3f(-6.0f, -3.0f, 0.0f);
glTexCoord2f(0.0f, 1.0f); glVertex3f(-6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 1.0f); glVertex3f(6.0f, -3.0f, 1.5f);
glTexCoord2f(5.0f, 0.0f); glVertex3f(6.0f, -3.0f, 0.0f);
glEnd();
// 交换缓冲区,并保存像素数据到文件
glutSwapBuffers();
grab();
}
int main(int argc, char* argv[])
{
// GLUT 初始化
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(WindowWidth, WindowHeight);
glutCreateWindow(WindowTitle);
glutDisplayFunc(&display);
// 在这里做一些初始化
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
texGround = load_texture("ground.bmp");
texWall = load_texture("wall.bmp");
// 开始显示
glutMainLoop();
return 0;
}
纹理图片:
ground.bmp
wall.bmp
运行结果: