Direct3D8下载入显示MD2 Model的demo的设计文档

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

Model demo主要演示了模型的载入和渲染,本demo所使用的模型是由ID Software所定义的MD2格式,MD2格式在《Quake II》游戏中所被广泛使用。

MD2的文件由两部分组成,一部分是 文件头(fileheader) ;另一部分是 文件数据(filedata) 。文件头包含有很多重要的数据、如:文件数据的大小,文件格式的版本号等等。因此每个文件的文件头的大小都应该是一致的。这也就是为什么我们一般都用一个结构体来描述这些文件头。与之相反的是每个文件的文件数据都不同的。因为文件数据包含了模型的 顶点(vertices)数三角形(triangles)面数纹理映射坐标(texture coordinate) 等等。下图表示了MD2模型文件的大略格式。

因此我们可以使用一个结构体来表示MD2文件头的组织。如下:

 1//MD2的文件头数据,这个数据结构的成员顺序是要严格定义不能调乱的 
 2struct MD2_FileHeader 
 3{ 
 4    int m_nIdentify; //识别号 
 5    int m_nVersion; //版本号 
 6    int m_nSkinWidth; //模型皮肤纹理的宽度 
 7    int m_nSkinHeight; //模型皮肤纹理的高度 
 8    int m_nFrameSize; //每帧的大小,以字节为单位 
 9    int m_nSkinNum; //与模型相关联的皮肤数量 
10    int m_nVertexNum; //模型的顶点数 
11    int m_nUVNum; //纹理坐标的数量 
12    int m_nTriangleNum; //模型中三角形面片的数量 
13    int m_nOpenGLCmdNum; //OpenGL的命令数目 
14    int m_nFrameNum; //帧数目 
15    int m_nSkinsOffset; //皮肤数据在文件中的偏移地址 
16    int m_nTexCoordOffset; //纹理映射坐标数据在文件中的偏移量 
17    int m_nTrianglesOffset; //三角形面片数据在文件中的偏移量 
18    int m_nFrameOffset; //帧数据在文件中的偏移量 
19    int m_nOpenGLCmdOffset; //OpenGL命令在文件中的偏移量 
20    int m_nEndOffset; //文件结束位置在文件中的偏移量 
21}; 

下面是结构体中的各成员变量的具体含义:

 1int m_nFrameSize; /*指定每一帧(frame)的大小。所谓的“帧”是“动画”(animation)的基本元素。<br>
 2这个帧和动画片中的每一张静态的图片的含义是类似。当我们在程序中以一定的时间间隔连续而且循环播放一系列<br>
 3静态的帧的时候,就能形成动画。所以,在MD2文件中,一个“帧”就存储了模型在一个特殊位置(或者说动作)时的<br>
 4顶点和三角形面片的信息。典型的MD2文件最多可由199帧。共21个动画组成。一个帧包含了组成这帧的三角形面片中<br>
 5的所有顶点,注意每一帧的三角形面片数都是相同的。而且我们要利用三角形面片数来给这些顶点分配存储空间。*/
 6
 7int m_nVertexNum /* 指定了该模型所拥有的顶点总数。它是指各帧所拥有的顶点总数之和。*/
 8
 9int m_nTriangleNum /* 指定了该模型所有的三角形面片数*/
10
11int m_nOpenGLCmdNum /* 指定了该模型所有的OpenGL命令数目。*/
12
13int m_nFrameNum /* 指定了该模型所有的帧的数量。*/

事实上,MD2中的帧都是 “关键帧”(keyframe) 。所谓的关键帧就是指按照一定的时间间隔所取的离散的帧。因为如果不是用关键帧的话,每个模型甚至可能会需要200到300帧才能组成一个平滑的动画。这样一来模型文件的体积将极速膨胀。所以我们只是需要保存关键帧,中间的过渡帧我们可以利用 线性插值(linear interpolation) 的方法。在需要渲染的时候计算出来。下面的图示就很好地表示了关键帧的概念:

整个平滑的动画需要20帧,MD2模型只是需要存储编号为0,5,10,15,20的这些帧,剩下的帧在需要渲染的时候可以实时计算。每一个模型都是由“帧数目”ד顶点数目”个顶点所组成的。所以我们要定义一个结构体来存储顶点数据。如下:

1struct MD2_TriVertex
2{ 
3    unsigned char m_X , m_Y , m_Z; 
4    unsigned char m_LightNormalIndex; 
5}; 

使用unsigned char类型来存储顶点的X,Y,Z坐标的话,那么顶点的范围值就只能从0到255,但是相对于使用float类型来存储数据,我们能够有效地压缩数据,使得文件的体积大为减小。接着我们需要定义一个结构体来存储顶点的纹理映射坐标。如下:

1struct MD2_UVTexCoord
2{ 
3    short int m_nU , m_nV; 
4}; 

如顶点数据一样,纹理坐标也是使用了压缩的方法,在MD2文件中,纹理坐标使用short int类型的数据,但是为了使用这些坐标,我们首先要把从文件中读取出的数据做一下转换才能使用。这是因为,纹理坐标是从0到1的浮点实数,而如果直接使用shortint类型的数的话,用坐标除以纹理的高宽是只能得到0或者是1的。所以转换的伪代码如下:

1实际纹理U坐标 =  (float)m_nU / MD2_FileHeader.m_nSkinWidth;
2实际纹理V坐标 =  (float)m_nV / MD2_FileHeader.m_nSkinHeight;

定义结构体如下,可以存储MD2模型中的“帧”的相关数据。

 1struct MD2_AliasFrame 
 2{
 3    float m_fScaleX; 
 4    float m_fScaleY; 
 5    float m_fScaleZ; 
 6    float m_fTranslateX; 
 7    float m_fTranslateY; 
 8    float m_fTranslateZ; 
 9    char m_szName[16]; 
10    MD2_TriVertex m_TriVtx[1]; 
11}; 

这样,每一帧的数据都能以这种数据结构方式存储。所以一个典型的模型将会有199个帧对象。当解压了每一个顶点数据之后,将会使用结构体的放缩系数m_fScaleX; m_fScaleY;m_fScaleZ(实际上就是放缩矩阵)和平移系数m_fTranslateX;m_fTranslateY; m_fTranslateZ(实质上是平移矩阵)把模型放缩和平移到合适的大小和位置。

1int m_szName

存储了该帧的名字。最后,变量m_TriVtx[1]是本帧的第一个顶点位置,其他的顶点将跟在这个顶点之后。所以每个动画都含有N帧,每帧含有个MD2_FileHeader.m_nVertexNum个顶点。综上。动画,帧和顶点的关系如下图:

每个顶点还需要和它所映射的纹理坐标关联起来。但是我们是采用一个三角形面片的方式来存储的,方式如下:

1struct MD2_Triangle 
2{ 
3    short int m_nIndexA; 
4    short int m_nIndexB; 
5    short int m_nIndexC; 
6    short int m_nIndexA_UV; 
7    short int m_nIndexB_UV; 
8    short int m_nIndexC_UV; 
9}; 

但是要注意,这结构数据成员仅仅是指这些顶点或者是纹理坐标的索引,而并不是指顶点或纹理坐标本身。顶点数据和纹理数据必须存储在的数据中,或者是你在渲染模型时解压到程序员指定的数据区域中去。有了上述的数据结构,就可以着手进行程序设计了。具体的设计方面可以看代码