NeHe OpenGL教程第二十九课,DancingWind翻译

Nehe SDK

第29课

Blitter 函数:

类似于DirectDraw的blit函数,过时的技术,我们有实现了它。它非常的简单,就是把一块纹理贴到另一块纹理上。

这篇文章是有Andreas Lffler所写的,它写了一份原始的教程。过了几天,Rob Fletcher发了封邮件给我,他重新改写了所有的代码,我在它的基础上把glut的框架变换为Win32的框架。

现在让我们开始吧!

下面是一个保存图像数据的结构
typedef struct Texture_Image
{
	int width;									// 宽
	int height;									// 高
	int format;									// 像素格式
	unsigned char *data;								// 纹理数据
} TEXTURE_IMAGE;
接下来定义了两个指向这个结构的指针
typedef TEXTURE_IMAGE *P_TEXTURE_IMAGE;							

P_TEXTURE_IMAGE t1;									// 指向保存图像结构的指针
P_TEXTURE_IMAGE t2;									// 指向保存图像结构的指针
下面的函数为w*h的图像分配内存
P_TEXTURE_IMAGE AllocateTextureBuffer( GLint w, GLint h, GLint f)
{
	P_TEXTURE_IMAGE ti=NULL;							
	unsigned char *c=NULL;								
	ti = (P_TEXTURE_IMAGE)malloc(sizeof(TEXTURE_IMAGE));					// 分配图像结构内存

	if( ti != NULL ) {
		ti->width  = w;								// 设置宽度
		ti->height = h;								// 设置高度
		ti->format = f;								// 设置格式
		// 分配w*h*f个字节
		c = (unsigned char *)malloc( w * h * f); 
		if ( c != NULL ) {
			ti->data = c;
		}
		else {
			MessageBox(NULL,"内存不足","分配图像内存错误",MB_OK | MB_ICONINFORMATION);
			return NULL;
		}
	}
	else
	{
		MessageBox(NULL,"内存不足","分配图像结构内存错误",MB_OK | MB_ICONINFORMATION);
		return NULL;
	}
	return ti;									// 返回指向图像数据的指针
}
下面的函数释放分配的内存
// 释放图像内存
void DeallocateTexture( P_TEXTURE_IMAGE t )
{
	if(t)
	{
		if(t->data)
		{
			free(t->data);							// 释放图像内存
		}

		free(t);									// 释放图像结构内存
	}
}
下面我们来读取*.raw的文件,这个函数有两个参数,一个为文件名,另一个为保存文件的图像结构指针。
// 读取*.RAW文件,并把图像文件上下翻转一符合OpenGL的使用格式。
int ReadTextureData ( char *filename, P_TEXTURE_IMAGE buffer)
{
	FILE *f;
	int i,j,k,done=0;
	int stride = buffer->width * buffer->format;					// 记录每一行的宽度,以字节为单位
	unsigned char *p = NULL;

	f = fopen(filename, "rb");							// 打开文件
	if( f != NULL )								// 如果文件存在
	{
如果文件存在,我们通过一个循环读取我们的纹理,我们从图像的最下面一行,一行一行的读取图像。
		for( i = buffer->height-1; i >= 0 ; i-- )				// 循环所有的行,从最下面以行开始,一行一行的读取
		{
			p = buffer->data + (i * stride );
			for ( j = 0; j < buffer->width ; j++ )			// 读取每一行的数据
			{
下面的循环读取每一像素的数据,并把alpha设为255
				for ( k = 0 ; k < buffer->format-1 ; k++, p++, done++ )
				{
					*p = fgetc(f);					// 读取一个字节
				}
				*p = 255; p++;						// 把255存储在alpha通道中
			}
		}
		fclose(f);								// 关闭文件
	}
如果出现错误,弹出一个提示框
	else						
	{
		MessageBox(NULL,"不能打开文件","图像错误",MB_OK | MB_ICONINFORMATION);
	}
	return done;									// 返回读取的字节数
}
下面的代码创建一个2D纹理,和前面课程介绍的方法相同
void BuildTexture (P_TEXTURE_IMAGE tex)
{
	glGenTextures(1, &texture;[0]);
	glBindTexture(GL_TEXTURE_2D, texture[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
	gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB, tex->width, tex->height, GL_RGBA, GL_UNSIGNED_BYTE, tex->data);
}
现在到了blitter函数的地方了,他运行你把一个图像的任意部分复制到另一个图像的任意部分,并混合。
src为原图像
dst为目标图像
src_xstart,src_ystart为要复制的部分在原图像中的位置
src_width,src_height为要复制的部分的宽度和高度
dst_xstart,dst_ystart为复制到目标图像时的起始位置
上面的意思是把原图像中的(src_xstart,src_ystart)-(src_width,src_height)复制到目标图像中(dst_xstart,dst_ystart)-(src_width,src_height)
blend设置是否启用混合,0为不启用,1为启用
alpha设置源图像中颜色在混合时所占的百分比
void Blit( P_TEXTURE_IMAGE src, P_TEXTURE_IMAGE dst, int src_xstart, int src_ystart, int src_width, int src_height,
	   int dst_xstart, int dst_ystart, int blend, int alpha)
{
	int i,j,k;
	unsigned char *s, *d;								

	// 掐断alpha的值
	if( alpha > 255 ) alpha = 255;
	if( alpha < 0 ) alpha = 0;

	// 判断是否启用混合
	if( blend < 0 ) blend = 0;
	if( blend > 1 ) blend = 1;
	d = dst->data + (dst_ystart * dst->width * dst->format);  			// 要复制的像素在目标图像数据中的开始位置 
	s = src->data + (src_ystart * src->width * src->format);			// 要复制的像素在源图像数据中的开始位置

	for (i = 0 ; i < src_height ; i++ )						// 循环每一行
	{
		s = s + (src_xstart * src->format);					// 移动到下一个像素
		d = d + (dst_xstart * dst->format);				
		for (j = 0 ; j < src_width ; j++ )					// 循环复制一行
		{
			for( k = 0 ; k < src->format ; k++, d++, s++)			// 复制每一个字节
			{
				if (blend)						// 如果启用了混合
					*d = ( (*s * alpha) + (*d * (255-alpha)) ) >> 8;	// 根据混合复制颜色
				else							
					*d = *s;						// 否则直接复制
			}
		}
		d = d + (dst->width - (src_width + dst_xstart))*dst->format;		// 移动到下一行
		s = s + (src->width - (src_width + src_xstart))*src->format;		
	}
}
初始化代码基本不变,我们使用新的函数,加载*.raw纹理。并把纹理t2的一部分blit到t1中混合,接着按常规的方法设置2D纹理。
int InitGL(GLvoid) 
{
	t1 = AllocateTextureBuffer( 256, 256, 4 );						// 为图像t1分配内存
	if (ReadTextureData("Data/Monitor.raw",t1)==0)					// 读取图像数据
	{										// 失败则弹出对话框
		MessageBox(NULL,"不能读取 'Monitor.raw' 文件","读取错误",MB_OK | MB_ICONINFORMATION);
		return FALSE;
	}

	t2 = AllocateTextureBuffer( 256, 256, 4 );						// 为图像t2分配内存
	if (ReadTextureData("Data/GL.raw",t2)==0)						// 读取图像数据
	{										// 失败则弹出对话框
		MessageBox(NULL,"不能读取 'GL.raw' 文件","读取错误 ",MB_OK | MB_ICONINFORMATION);
		return FALSE;
	}
把图像t2的(127,127)-(256,256)部分和图像t1的(64,64,196,196)部分混合
	// 把图像t2的(127,127)-(256,256)部分和图像t1的(64,64,196,196)部分混合
	Blit(t2,t1,127,127,128,128,64,64,1,127);					
下面的代码和前面一样,释放分配的空间,创建纹理
	BuildTexture (t1);								// 把t1图像加载为纹理

	DeallocateTexture( t1 );							// 释放图像数据
	DeallocateTexture( t2 );						

	glEnable(GL_TEXTURE_2D);							// 使用2D纹理

	glShadeModel(GL_SMOOTH);							// 使用光滑着色
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);					// 设置背景色为黑色
	glClearDepth(1.0);								// 设置深度缓存清楚值为1
	glEnable(GL_DEPTH_TEST);							// 使用深度缓存
	glDepthFunc(GL_LESS);							// 设置深度测试函数

	return TRUE;
}
下面的代码绘制一个盒子
GLvoid DrawGLScene(GLvoid)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);				// 清楚颜色缓存和深度缓存
	glLoadIdentity();							
	glTranslatef(0.0f,0.0f,-5.0f);

	glRotatef(xrot,1.0f,0.0f,0.0f);
	glRotatef(yrot,0.0f,1.0f,0.0f);
	glRotatef(zrot,0.0f,0.0f,1.0f);

	glBindTexture(GL_TEXTURE_2D, texture[0]);

	glBegin(GL_QUADS);
		// 前面
		glNormal3f( 0.0f, 0.0f, 1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		// 后面
		glNormal3f( 0.0f, 0.0f,-1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		// 上面
		glNormal3f( 0.0f, 1.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		// 下面
		glNormal3f( 0.0f,-1.0f, 0.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		// 右面
		glNormal3f( 1.0f, 0.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
		// 左面
		glNormal3f(-1.0f, 0.0f, 0.0f);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
	glEnd();

	xrot+=0.3f;
	yrot+=0.2f;
	zrot+=0.4f;
	return TRUE; // 一切 OK
}
KillGLWindow() 函数没有变化
CreateGLWindow函数没有变化
WinMain() 没有变化

好了,现你可以很轻松的绘制很多混合效果。如果你有什么好的建议,请告诉我。

版权与使用声明:
我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是[email protected],如果你有任何问题,都可以联系我。

引子
网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。

版权声明
所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。

发展计划
在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。

感谢
感谢我的母亲一直以来对我的支持和在生活上的照顾。
感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。

源码 RAR格式

< 28 30 >