Direct3D8下载入显示MD2 Model的demo的设计文档
Table of Contents
请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
Model demo主要演示了模型的载入和渲染,本demo所使用的模型是由ID Software所定义的MD2格式,MD2格式在《Quake II》游戏中所被广泛使用。
MD2的文件由两部分组成,一部分是 文件头(fileheader) ;另一部分是 文件数据(filedata) 。文件头包含有很多重要的数据、如:文件数据的大小,文件格式的版本号等等。因此每个文件的文件头的大小都应该是一致的。这也就是为什么我们一般都用一个结构体来描述这些文件头。与之相反的是每个文件的文件数据都不同的。因为文件数据包含了模型的 顶点(vertices)数 , 三角形(triangles)面数 , 纹理映射坐标(texture coordinate) 等等。下图表示了MD2模型文件的大略格式。
因此我们可以使用一个结构体来表示MD2文件头的组织。如下:
//MD2的文件头数据,这个数据结构的成员顺序是要严格定义不能调乱的
struct MD2_FileHeader
{
int m_nIdentify; //识别号
int m_nVersion; //版本号
int m_nSkinWidth; //模型皮肤纹理的宽度
int m_nSkinHeight; //模型皮肤纹理的高度
int m_nFrameSize; //每帧的大小,以字节为单位
int m_nSkinNum; //与模型相关联的皮肤数量
int m_nVertexNum; //模型的顶点数
int m_nUVNum; //纹理坐标的数量
int m_nTriangleNum; //模型中三角形面片的数量
int m_nOpenGLCmdNum; //OpenGL的命令数目
int m_nFrameNum; //帧数目
int m_nSkinsOffset; //皮肤数据在文件中的偏移地址
int m_nTexCoordOffset; //纹理映射坐标数据在文件中的偏移量
int m_nTrianglesOffset; //三角形面片数据在文件中的偏移量
int m_nFrameOffset; //帧数据在文件中的偏移量
int m_nOpenGLCmdOffset; //OpenGL命令在文件中的偏移量
int m_nEndOffset; //文件结束位置在文件中的偏移量
};
下面是结构体中的各成员变量的具体含义:
int m_nFrameSize; /*指定每一帧(frame)的大小。所谓的“帧”是“动画”(animation)的基本元素。<br>
这个帧和动画片中的每一张静态的图片的含义是类似。当我们在程序中以一定的时间间隔连续而且循环播放一系列<br>
静态的帧的时候,就能形成动画。所以,在MD2文件中,一个“帧”就存储了模型在一个特殊位置(或者说动作)时的<br>
顶点和三角形面片的信息。典型的MD2文件最多可由199帧。共21个动画组成。一个帧包含了组成这帧的三角形面片中<br>
的所有顶点,注意每一帧的三角形面片数都是相同的。而且我们要利用三角形面片数来给这些顶点分配存储空间。*/
int m_nVertexNum /* 指定了该模型所拥有的顶点总数。它是指各帧所拥有的顶点总数之和。*/
int m_nTriangleNum /* 指定了该模型所有的三角形面片数*/
int m_nOpenGLCmdNum /* 指定了该模型所有的OpenGL命令数目。*/
int m_nFrameNum /* 指定了该模型所有的帧的数量。*/
事实上,MD2中的帧都是 “关键帧”(keyframe) 。所谓的关键帧就是指按照一定的时间间隔所取的离散的帧。因为如果不是用关键帧的话,每个模型甚至可能会需要200到300帧才能组成一个平滑的动画。这样一来模型文件的体积将极速膨胀。所以我们只是需要保存关键帧,中间的过渡帧我们可以利用 线性插值(linear interpolation) 的方法。在需要渲染的时候计算出来。下面的图示就很好地表示了关键帧的概念:
整个平滑的动画需要20帧,MD2模型只是需要存储编号为0,5,10,15,20的这些帧,剩下的帧在需要渲染的时候可以实时计算。每一个模型都是由“帧数目”ד顶点数目”个顶点所组成的。所以我们要定义一个结构体来存储顶点数据。如下:
struct MD2_TriVertex
{
unsigned char m_X , m_Y , m_Z;
unsigned char m_LightNormalIndex;
};
使用unsigned char类型来存储顶点的X,Y,Z坐标的话,那么顶点的范围值就只能从0到255,但是相对于使用float类型来存储数据,我们能够有效地压缩数据,使得文件的体积大为减小。接着我们需要定义一个结构体来存储顶点的纹理映射坐标。如下:
struct MD2_UVTexCoord
{
short int m_nU , m_nV;
};
如顶点数据一样,纹理坐标也是使用了压缩的方法,在MD2文件中,纹理坐标使用short int类型的数据,但是为了使用这些坐标,我们首先要把从文件中读取出的数据做一下转换才能使用。这是因为,纹理坐标是从0到1的浮点实数,而如果直接使用shortint类型的数的话,用坐标除以纹理的高宽是只能得到0或者是1的。所以转换的伪代码如下:
实际纹理U坐标 = (float)m_nU / MD2_FileHeader.m_nSkinWidth;
实际纹理V坐标 = (float)m_nV / MD2_FileHeader.m_nSkinHeight;
定义结构体如下,可以存储MD2模型中的“帧”的相关数据。
struct MD2_AliasFrame
{
float m_fScaleX;
float m_fScaleY;
float m_fScaleZ;
float m_fTranslateX;
float m_fTranslateY;
float m_fTranslateZ;
char m_szName[16];
MD2_TriVertex m_TriVtx[1];
};
这样,每一帧的数据都能以这种数据结构方式存储。所以一个典型的模型将会有199个帧对象。当解压了每一个顶点数据之后,将会使用结构体的放缩系数m_fScaleX; m_fScaleY;m_fScaleZ(实际上就是放缩矩阵)和平移系数m_fTranslateX;m_fTranslateY; m_fTranslateZ(实质上是平移矩阵)把模型放缩和平移到合适的大小和位置。
int m_szName
存储了该帧的名字。最后,变量m_TriVtx[1]是本帧的第一个顶点位置,其他的顶点将跟在这个顶点之后。所以每个动画都含有N帧,每帧含有个MD2_FileHeader.m_nVertexNum个顶点。综上。动画,帧和顶点的关系如下图:
每个顶点还需要和它所映射的纹理坐标关联起来。但是我们是采用一个三角形面片的方式来存储的,方式如下:
struct MD2_Triangle
{
short int m_nIndexA;
short int m_nIndexB;
short int m_nIndexC;
short int m_nIndexA_UV;
short int m_nIndexB_UV;
short int m_nIndexC_UV;
};
但是要注意,这结构数据成员仅仅是指这些顶点或者是纹理坐标的索引,而并不是指顶点或纹理坐标本身。顶点数据和纹理数据必须存储在的数据中,或者是你在渲染模型时解压到程序员指定的数据区域中去。有了上述的数据结构,就可以着手进行程序设计了。具体的设计方面可以看代码