OpenGL-纹理

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
ground.bmp
wall.bmp
wall.bmp
运行结果:
在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值