第25课

变形和从文件中加载3D物体:

在这一课中,你将学会如何从文件加载3D模型,并且平滑的从一个模型变换为另一个模型。

欢迎来到这激动人心的一课,在这一课里,我们将介绍模型的变形。需要注意的是各个模型必须要有相同的顶点,才能一一对应,并应用变形。
在这一课里,我们同样要教会你如何从一个文件中读取模型数据。
文件开始的部分和前面一样,没有任何变化。
我们结下来添加几个旋转变量,用来记录旋转的信息。并使用cx,cy,cz设置物体在屏幕上的位置。
变量key用来记录当前的模型,step用来设置相邻变形之间的中间步骤。如step为200,则需要200次,才能把一个物体变为另一个物体。
最后我们用一个变量来设置是否使用变形。
GLfloat		xrot,yrot,zrot,								// X, Y & Z 轴的旋转角度
		xspeed,yspeed,zspeed,							// X, Y & Z 轴的旋转速度
		cx,cy,cz=-15;								// 物体的位置

int		key=1;									// 物体的标识符
int		step=0,steps=200;								// 变换的步数
bool		morph=FALSE;								// 是否使用变形
下面的结构定义一个三维顶点
typedef struct					
{
	float	x, y, z;							
} VERTEX;							
下面的结构使用顶点来描述一个三维物体
typedef	struct										// 物体结构
{
 int		verts;									// 物体中顶点的个数
 VERTEX		*points;									// 包含顶点数据的指针
} OBJECT;										
maxver用来记录各个物体中最大的顶点数,如一个物体使用5个顶点,另一个物体使用20个顶点,那么物体的顶点个数为20。
结下来定义了四个我们使用的模型物体,并把相邻模型变形的中间状态保存在helper中,sour保存原模型物体,dest保存将要变形的模型物体。
int		maxver;									// 最大的顶点数
OBJECT		morph1,morph2,morph3,morph4,						// 我们的四个物体
		helper,*sour,*dest;		 					// 帮助物体,原物体,目标物体
WndProc()函数没有变化
下面的函数用来为模型分配保存顶点数据的内存空间
void objallocate(OBJECT *k,int n)
{											
	k->points=(VERTEX*)malloc(sizeof(VERTEX)*n);					// 分配n个顶点的内存空间
}										
下面的函数用来释放为模型分配的内存空间
void objfree(OBJECT *k)			
{
	free(k->points);								
}
下面的代码用来读取文件中的一行。
我们用一个循环来读取字符,最多读取255个字符,当遇到'\n'回车时,停止读取并立即返回。
void readstr(FILE *f,char *string)							// 读取一行字符
{
	do										
	{
		fgets(string, 255, f);						// 最多读取255个字符
	} while ((string[0] == '/') || (string[0] == '\n'));				// 遇到回车则停止读取
	return;									// 返回
}
下面的代码用来加载一个模型文件,并为模型分配内存,把数据存储进去。
void objload(char *name,OBJECT *k)							// 从文件加载一个模型
{
	int	ver;								// 保存顶点个数
	float	rx,ry,rz;								// 保存模型位置
	FILE	*filein;								// 打开的文件句柄
	char	oneline[255];							// 保存255个字符

	filein = fopen(name, "rt");							// 打开文本文件,供读取
											
	readstr(filein,oneline);							// 读入一行文本
	sscanf(oneline, "Vertices: %d\n", &ver);					// 搜索字符串"Vertices: ",并把其后的顶点数保存在ver变量中
	k->verts=ver;								// 设置模型的顶点个数
	objallocate(k,ver);							// 为模型数据分配内存
下面的循环,读取每一行(即每个顶点)的数据,并把它保存到内存中?/td>
	for (int i=0;i<ver;i++)								// 循环所有的顶点
	{
		readstr(filein,oneline);							// 读取一行数据
		sscanf(oneline, "%f %f %f", &rx, &ry, &rz);					// 把顶点数据保存在rx,ry,rz中
		k->points[i].x = rx;							// 保存当前顶点的x坐标
		k->points[i].y = ry;							// 保存当前顶点的y坐标
		k->points[i].z = rz;							// 保存当前顶点的z坐标
	}
	fclose(filein);									// 关闭文件

	if(ver>maxver) maxver=ver;								// 记录最大的顶点数
}										
下面的函数根据设定的间隔,计算第i个顶点每次变换的位移
VERTEX calculate(int i)									// 计算第i个顶点每次变换的位移
{
	VERTEX a;								
	a.x=(sour->points[i].x-dest->points[i].x)/steps;				
	a.y=(sour->points[i].y-dest->points[i].y)/steps;				
	a.z=(sour->points[i].z-dest->points[i].z)/steps;				
	return a;									
}											
ReSizeGLScene()函数没有变化
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
下面的函数完成初始化功能,它设置混合模式为半透明
int InitGL(GLvoid)			
{
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);						// 设置半透明混合模式
	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);					// 设置清除色为黑色
	glClearDepth(1.0);								// 设置深度缓存中值为1
	glDepthFunc(GL_LESS);							// 设置深度测试函数
	glEnable(GL_DEPTH_TEST);							// 启用深度测试
	glShadeModel(GL_SMOOTH);							// 设置着色模式为光滑着色
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);			
下面的代码用来加载我们的模型物体
	maxver=0;									// 初始化最大顶点数为0
	objload("data/sphere.txt",&morph1);						// 加载球模型
	objload("data/torus.txt",&morph2);						// 加载圆环模型
	objload("data/tube.txt",&morph3);						// 加载立方体模型
第四个模型不从文件读取,我们在(-7,-7,-7)-(7,7,7)之间随机生成模型点,它和我们载如的模型都一样具有486个顶点。
	objallocate(&morph4,486);							// 为第四个模型分配内存资源
	for(int i=0;i<486;i++)							// 随机设置486个顶点
	{
		morph4.points[i].x=((float)(rand()%14000)/1000)-7;			
		morph4.points[i].y=((float)(rand()%14000)/1000)-7;			 
		morph4.points[i].z=((float)(rand()%14000)/1000)-7;			
	}
初始化中间模型为球体,并把原和目标模型都设置为球
	objload("data/sphere.txt",&helper);
	sour=dest=&morph1;								

	return TRUE;									// 初始化完成,成功返回
}
下面是具体的绘制代码,向往常一样我们先设置模型变化,以便我们更好的观察。
void DrawGLScene(GLvoid)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);				// 清空缓存
	glLoadIdentity();								// 重置模型变换矩阵
	glTranslatef(cx,cy,cz);							// 平移和旋转
	glRotatef(xrot,1,0,0);								
	glRotatef(yrot,0,1,0);							
	glRotatef(zrot,0,0,1);								

	xrot+=xspeed; yrot+=yspeed; zrot+=zspeed;					// 根据旋转速度,增加旋转角度

	GLfloat tx,ty,tz;								// 顶点临时变量
	VERTEX q;									// 保存中间计算的临时顶点
接下来我们来绘制模型中的点,如果启用了变形,则计算变形的中间过程点。
	glBegin(GL_POINTS);								// 点绘制开始
		for(int i=0;i<morph1.verts;i++)						// 循环绘制模型1中的每一个顶点
		{									
			if(morph) q=calculate(i); else q.x=q.y=q.z=0;				// 如果启用变形,则计算中间模型
			helper.points[i].x-=q.x;					
			helper.points[i].y-=q.y;					
			helper.points[i].z-=q.z;					
			tx=helper.points[i].x;						// 保存计算结果到x,y,z变量中
			ty=helper.points[i].y;						
			tz=helper.points[i].z;					
为了让动画开起来流畅,我们一共绘制了三个中间状态的点。让变形过程从蓝绿色向蓝色下一个状态变化。
			glColor3f(0,1,1);						// 设置颜色
			glVertex3f(tx,ty,tz);					// 绘制顶点
			glColor3f(0,0.5f,1);					// 把颜色变蓝一些
			tx-=2*q.x; ty-=2*q.y; ty-=2*q.y;				// 如果启用变形,则绘制2步后的顶点
			glVertex3f(tx,ty,tz);						
			glColor3f(0,0,1);						// 把颜色变蓝一些
			tx-=2*q.x; ty-=2*q.y; ty-=2*q.y;				// 如果启用变形,则绘制2步后的顶点
			glVertex3f(tx,ty,tz);						
		}									
	glEnd();									// 绘制结束
最后如果启用了变形,则增加递增的步骤参数,然后绘制下一个点。
	// 如果启用变形则把变形步数增加
	if(morph && step<=steps)step++; else { morph=FALSE; sour=dest; step=0;}
	return TRUE; // 一切OK
}
KillGLWindow() 函数基本没有变化,只是添加释放5个模型内存的代码
objfree(&morph1);								// 释放模型1内存
	objfree(&morph2);								// 释放模型2内存
	objfree(&morph3);								// 释放模型3内存
	objfree(&morph4);								// 释放模型4内存
	objfree(&helper);								// 释放模型5内存
CreateGLWindow() 函数没有变化
BOOL CreateGLWindow()		

LRESULT CALLBACK WndProc()
在WinMain()函数中,我们添加了一些键盘控制的函数
				if(keys[VK_PRIOR])						// PageUp键是否被按下
					zspeed+=0.01f;					// 按下增加绕z轴旋转的速度

				if(keys[VK_NEXT])						// PageDown键是否被按下
					zspeed-=0.01f;					// 按下减少绕z轴旋转的速度

				if(keys[VK_DOWN])						// 下方向键是否被按下
					xspeed+=0.01f;					// 按下增加绕x轴旋转的速度

				if(keys[VK_UP])						// 上方向键是否被按下
					xspeed-=0.01f;					// 按下减少绕x轴旋转的速度

				if(keys[VK_RIGHT])						// 右方向键是否被按下
					yspeed+=0.01f;					// 按下增加沿y轴旋转的速度

				if(keys[VK_LEFT])						// 左方向键是否被按下
					yspeed-=0.01f;					// 按下减少沿y轴旋转的速度
				if (keys['Q'])						// Q键是否被按下
				 cz-=0.01f;						// 是则向屏幕里移动

				if (keys['Z'])						// Z键是否被按下 
				 cz+=0.01f;						// 是则向屏幕外移动

				if (keys['W'])						// W键是否被按下
				 cy+=0.01f;						// 是则向上移动

				if (keys['S'])						// S键是否被按下
				 cy-=0.01f;						// 是则向下移动

				if (keys['D'])						// D键是否被按下 
				 cx+=0.01f;						// 是则向右移动

				if (keys['A'])						// A键是否被按下 
				 cx-=0.01f;						// 是则向左移动
1,2,3,4键用来设置变形的目标模型
				if (keys['1'] && (key!=1) && !morph)			// 如果1被按下,则变形到模型1
				{
					key=1;						
					morph=TRUE;				
					dest=&morph1;					
				}
				if (keys['2'] && (key!=2) && !morph)			// 如果2被按下,则变形到模型1
				{
					key=2;						
					morph=TRUE;					
					dest=&morph2;					
				}
				if (keys['3'] && (key!=3) && !morph)			// 如果3被按下,则变形到模型1
				{
					key=3;						
					morph=TRUE;					
					dest=&morph3;					
				}
				if (keys['4'] && (key!=4) && !morph)			// 如果4被按下,则变形到模型1
				{
					key=4;						
					morph=TRUE;					
					dest=&morph4;					
				}

我希望你能喜欢这个教程,相信你已经学会了变形动画。
Piotr Cieslak 的代码非常的新颖,希望通过这个教程你能知道如何从文件中加载三维模型。
这份教程化了我三天的时间,如果有什么错误请告诉我。

< 24 26 >