盛崇山
http://antsam.blogone.net
AntsamCGD@hotmail.com
Ogre 数据文件分析主要分析 Ogre 中用于存放外部文件相关的类,例如用于存放 Mesh
相关的类或者用于存放纹理的相关的类。这些类组成我们的道具^_^。我们将对一下几部分
进行分析。
纹理
CS 骨骼动画分析
模型(mesh)
一、纹理
纹理创建可以来之很多类型的外部文件,例如:bmp、jpg、png 等,而 DirectX(以
BMP 或 PNG 作为标准)和 OpenGL 的纹理正常情况是未经压缩的文件格式,所以
DirectX 和 OpenGL 中创建纹理所需要的数据也是没有压缩过的,所以对于 JPG、TGA、
PNG 需要进行解压,我们可以利用第三方开发的库用来解压来获取数据。下面的 UML
图就是关于图像压缩相关的类:
图一:文件解压相关类
从图中我们可以看出纹理创建的过程大概如下流程:外部文件通过各类 Decode,
把数据输出到 Data Chunk 中,然后 Image 通过 Load 函数把 Data Chunk 作为参数创建
Image 类,DirectX 和 OpenGL 再通过 Image 创建纹理。
二、模型
模型文件中比较重要是人物 Mesh,人物动画一般是通过关键帧或者骨骼动画实现
的,现在的游戏大都采用骨骼动画。下面部分我们主要来分析骨骼动画。在分析 Ogre
中模型相关类前,我们先分析 Counter Strike 中的模型文件 MDL,MDL 是真正的骨骼
动画。
在 MDL 文件中,定点数据只有一帧,作为初始位置,其它的帧通过骨骼动画来确
定,其初始位置如下图所示:
图 2:模型初始位置点
初始位置是有了,那么,通过骨骼动画就可以动了,那么骨骼是怎么定义的呢?骨骼
(Skeleton)当然是由很多的骨头(bone)组成的,我们看一下 Bone 的定义:
struct sBone
{
char name[32];
struct sBone *parent;
float matrix[3][4];
};
三个变量用来说明骨头的名称(大腿骨等^_^)、关节点、transform 矩阵;关节点把
各个骨头连接在一起组成 skeleton(其实可以看成是一棵倒立的树型结构),还有一
点必须说明的是:这种倒立的树型结构中父节点的动作会影响到子节点的,例如:大腿
的运动会影响小腿的运动,也就是说 bone 中的 matrix 记录的是其相对于父节点的
transform,所以为了计算与 bone 相对应的定点的最后的位置,必须把这些定点对应的
bone 同其父节点、祖父节点等矩阵进行连接(contract)。
那么这些动作是怎么保存的呢?MDL 中动作称为 sequence,而每个动作(sequence)
中又有许多关键帧(frame)组成,在骨骼动画中,关键帧是通过 skeleton 来表示,而
在文件中则是记录 skeleton 中的每块 bone 的 transform,它们的定义如下:
struct sTransform
{
float pos[3];
float rot[3];
};
struct sSequence
{
char name[32];
float fps;
int numframes;
sTransform *frames;
};
sSequence中的transform的数目通过下面计算:
transform number = number of frame in sequence× number of bone in skeleton
而transform保存的则是bone相对与其父节点平移和旋转的信息。所以模型数据可以用
下面的数据结构来表示:
struct sModel
{
// 当前动作
int currentseq;
// 当前所在的关键帧
float currentframe;
// 在真个骨架中骨头的数目(^_^)
int numbones;
// 指向bone的数据
sBone *bones;
// 在模型中动作的个数
int numseqs;
// 指向动作数据
sSequence *seqs;
// 定点的个数
int numverts;
// 指向定点数据
sVertex *verts;
// 三角形个数
int numtriangles;
sTriangle *triangles;
int numtexture;
sTexture *textures[MAX_TEXTURES];
};
前面分析了关键帧是怎么保存的了,所以我们要渲染某个关键帧的话,我们必须设置好
bone中的matrix,下面是在MDL loader中的代码:
void CMdlLoader::SetupBone()
{
sBone *pbone;
float q1[4],q2[4];
float m[3][4];
sSequence *pseq;
sTransform *ptrans;
int curframe;
float fraction;
// 获取但前动作的首地址
// get current frame and sequence;
pseq = m_Model.seqs + m_Model.currentseq;
// 计算关键帧之间的插值比例
curframe = (int)m_Model.currentframe;
fraction = m_Model.currentframe - curframe;
// 获取骨骼的首地址
pbone = m_Model.bones;
for(int i = 0; i < m_Model.numbones; i++, pbone++)
{
// 获取但前frame,但前bone对应的transform的地址
ptrans = pseq->frames + (pseq->numframes * i) + curframe;
// 如果不是是最后一帧,着需要对两个关键帧之间进行插值
if(pseq->numframes > (curframe + 1))
{
// 计算旋转、平移对应的矩阵
AngleQuaternion(ptrans[0].rot, q1);
AngleQuaternion(ptrans[1].rot, q2);
QuaternionSlerp(q1,q2,fraction,q1);
QuaternionMatrix(q1, m);
m[0][3] = ptrans[0].pos[0] * (1.0 - fraction) + ptrans[1].pos[0] * fraction;
m[1][3] = ptrans[0].pos[1] * (1.0 - fraction) + ptrans[1].pos[1] * fraction;
m[2][3] = ptrans[0].pos[2] * (1.0 - fraction) + ptrans[1].pos[2] * fraction;