Quake2的MD2文件格式

 
                           http://tfc.duke.free.fr/old/models/md2.htm
        
Quake2 的MD2文件格式
       
 
 
介绍:
     “ 好,一个新的MD2圣经:)”,虽然说明MD2的指南满天飞,但我这次会给出一个新方式来渲染。
     什么是MD2呢?其实他是一个最早被用在Q2引擎中的一种3D模型格式,下面我会介绍如何加载并用OPENGL实时渲染。
     也许你认为没有必要再捡起这种老掉牙的格式,可我们确实有理由来了解他的真谛。首先,MD2是一个非常适合初学者的文件格式,然后呢…..
然后,他完全地免费!!!呵呵。
     文章内容包括:
     MD2 文件格式
     开发一个CMD2MODEL类
     存取一个MD2模型
     显示模型
     动画
     特别说明的是,这里的源代码是完全的免费的,如果你“不小心”看到这篇文章的话,希望你来自奇妙的C++的丛林,当然也要有在OPENGL的王国从政的经历。好,我们开始吧。
 
MD2 文件格式
     和其他的文件格式一样,MD2文件也是有两部分组成:文件头和数据区。
文件头中包含了许多重要的信息,如数据区的大小、MAGIC标志、版本号等,当然他的大小是确定的,所以在程序中我们通常有一个结构来表示。数据区就有很大的不同,由于每个文件包含的图元和纹理不一样,所以他们一般会有很大的区别。
具体结构如下图:
    
  下面是MD2的文件头定义:
// md2 header
typedef struct
{
    int     ident;              // 文件标识. 必须是"IPD2"
    int     version;            // md2 版本. 等于 8
 
    int     skinwidth;          // 纹理宽度
    int     skinheight;         //纹理高度
    int     framesize;          // 每一帧占的字节数
 
    int     num_skins;          // 纹理的数目
    int     num_xyz;            // 三维空间中点数
    int     num_st;             // 纹理的坐标数
    int     num_tris;           //三角形的数目
    int     num_glcmds;         // OPENGL命令数目
    int     num_frames;         // 文件包含的帧数
 
    int     ofs_skins;          // 纹理名称的偏移量(每个64字节)
    int     ofs_st;             // 存储纹理坐标偏移量
    int     ofs_tris;           // 存储三角形地址偏移量
    int     ofs_frames;         //存储帧的地址偏移量
    int     ofs_glcmds;         // 存储OPENGL命令的地址偏移量
    int     ofs_end;            // 文件结尾的偏移量
 
} md2_t;
那么,我会简要解释一下变量的用途:
首先,准备好灯光。。。呵呵
下面请“ ident ”又名“Magic Number”.
Q: 请问你对自己在MD2中的职责有何理解?
 
A: 咳,由于时间紧迫,我就长话短说。比较形象的说法就是:当MD2 loader要拉人下舞场(内存)时,会问你是“IPD2”吗?如果不是,那就不会有后面的探戈了。
 
     看来在接下来的节目中,时间是我们不得不考虑的因素,下面我当全职介绍:
version ”,MD2文件的版本,值等于8
 
skinwidth ”、“ skinheight ”纹理的宽度和长度。由于MD2的纹理和模型分开,所以这里的数据基本上没有什么用处。
 
framesize 制定一帧所占的字节数,那么什么是帧呢?如果你对动画的原理有所了解的话,我就没有往下些了。帧就是特定时间点的一个图片。在3D场景中,图形都是用图元表示,而这些图元需要一定的存储空间,这就是FRAMESIZE的工作。
MD2 文件大部分多有199个FRAME,这199个FRAME经过一定的组合表示21种动画。
 
 
num_skins ”MD2模型的纹理数目。
由于上面有解释,其他的我不自一一说明,详细信息请参考英文原版,我将会在文章的结尾给出链接。
 
下面我们来看看 num_frames。他指出这个MD2模体有多少帧。事实上,他们是有一定时间间隔的关键帧,由于文件要读如内存,一个动作要包含200到300帧是不怎么现实的,如果你有特别的需求,也可以这样做,呵呵。
那么既然关键帧是不连续的,那么我们怎么保证模体动画的连续性,解决的方法就是用线性插值的方式来动态生成中间的帧。如下图:
 
说完文件头,我们继续对文件余下的内容进行详细的说明和表示:
与头文件一样,我们还是用结构体来表示帧,向量,和OPENGL命令。
首先,我们介绍一下向量,我们不用复杂的类来表示他,而只是选择了一个简单的数组。
typedef float vec3_t[3];
每一个模体都有num_frame * num_xyz个向量,下面是单个向量的结构:
// vertex
typedef struct
{
    unsigned char   v[3];                // 压缩的向量 坐标(x, y, z)
    unsigned char   lightnormalindex;    // 向量的索引
} vertex_t;
你可能注意到了 unsigned char   v[3]; 是的,这个向量的表示的是已经压缩过的数据(听起来很专业:)),在计算中我们会把这些数据解压,然后熏染,所以模型不会受到太大的影响。
Lightnormalindex是一个向量的索引。
对于一个模体的向量,还会有一个纹理坐标:
// 纹理坐标
typedef struct
{
    short    s;
    short    t;
 
} texCoord_t;
和向量的表示一样,纹理的数据也进行了压缩,在图形渲染时,我们会解压这些数据。
Like for vertices, data is compresed. Here we use short (2 bytes) instead of float (4 bytes) for storing texture coordinates. But to use them, we must convert them to float because texture coordinates range from 0.0 to 1.0, and if we kept short values, we could have only 0 or 1 and any intermediate value! So how to uncompress them? It's quite simple. Divide the short value by the texture size :
公式如下:
 
RealST[i].s = (float)texCoord[i].s / header.skinwidth;
RealST[i].t = (float)texCoord[i].t / header.skinheight;
 
 
关键帧的结构如下:
// frame
typedef struct
{
    float       scale[3];       // 压缩,解压码
    float       translate[3];   //平移向量
    char        name[16];       // 帧名称
    vertex_t    verts[1];       // 帧向量开始的地方
 
} frame_t;
scale[3] 就是我们在上面说的解压的关键角色,这个数组中储存了缩放的倍数,translate中主要用来储存缩放后的平移量。
那么关键帧的一个点实际值的表示方法如下:
vertex.x = (frame.verts[i].v[0] * frame.scale[0]) + frame.translate[0]
vertex.y = (frame.verts[i].v[1] * frame.scale[1]) + frame.translate[1]
vertex.z = (frame.verts[i].v[2] * frame.scale[2]) + frame.translate[2]
其中vertex就是模体中实际向量值。值得书名的是:frame.verts数组的索引范围是:
0-     (num_xyz – 1)
下面我们列出他们之间的关系图:
 
 
在真正的渲染过程中,我们需要把三角型的顶点向量和纹理坐标联系起来,那么我们会建一个结构体,考虑到MD2的文件结构,结构体如下:
// triangle
typedef struct
{
    short   index_xyz[3];    // 向量索引
    short   index_st[3];     // 纹理索引
 
} triangle_t;
 
你可能已经注意到,我们现在已经把每一个三角型的定点已经和三角型的纹理对应上了,渲染代码如下:
glBegin( GL_TRIANGLES );
 
// draw each triangle
for( int i = 0; i < header.num_tris; i++ )
{
    // draw triangle #i
    for( int j = 0; j < 3; j++ )
    {
        // k is the frame to draw
        // i is the current triangle of the frame
        // j is the current vertex of the triangle
 
        glTexCoord2f( (float)TexCoord[ Meshes[i].index_st[j] ].s / header.skinwidth,
                           (float)TexCoord[ Meshes[i].index_st[j] ].t / header.skinheight );
 
        glNormal3fv( anorms[ Vertices[ Meshes[i].index_xyz[j] ].lightnormalindex ] );
        
        glVertex3f( (Vertices[ Meshes[i].index_xyz[j] ].v[0] * frame[k].scale[0]) + frame[k].translate[0],
                        (Vertices[ Meshes[i].index_xyz[j] ].v[1] * frame[k].scale[1]) + frame[k].translate[1],
                        (Vertices[ Meshes[i].index_xyz[j] ].v[2] * frame[k].scale[2]) + frame[k].translate[2] );
    }
}
 
glEnd();
 
 
opengl命令应用,我不在翻译,参照原版。
 
下面是整个文件的结构图:
 
       http://tfc.duke.free.fr/old/models/md2.htm
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值