作者:i_dovelemon
来源:CSDN
日期:2014 / 9 / 17
主题:3D Format, Milk 3D Shape, Chunk Based System, Skeleton Animation
(文中以红色中字标示的文字,是整个文章的注意项,请读者留心)
引言
在3D游戏领域,艺术家们通过3D建模软件建立自己的艺术品模型和绚丽的3D场景。当我们想要在自己的游戏中使用这些模型的时候,我们就需要将这种模型变成能够被我们的引擎所识别的文件格式。每一个建模软件都有自己的文件格式,不同的游戏也会建立自己的3D模型格式。如此多的格式,给我们带来了很多麻烦。所以在本节,我们先设计一个自己引擎的3D格式,然后在编写一个格式转换的工具,将别的3D格式转换成我们想要的模型的格式。
Chunk Based System File 3D格式
Chunk Based System是指一种将真实数据通过首尾的两个Mark来进行标示的数据结构体。比如,下图这样的结构:
我们真实的需要保存的数据是struct,但是在struct的上面和下面分别有两个标示符,begin(名字只做示例说明,在代码中并不一定就是begin)标记中会存放一个标示符,告知我们下面的struct是什么样的结构,并且一共有多大。在结构保存完毕之后,我们在struct后面加上一个end标记,表示对这块的Chunk已经读取完毕。同样的事情持续的发生在下面的空间中,直到我们将所有要保存的结构都保存完毕为止。同时,我们还需要明确一点,在每一个CHUNK中,我们可以继续嵌套一个CHUNK(这就是Chunk Based System的强大之处)。
使用这样的结构简单,容易进行读写操作,并且是2进制的文件,更加节省空间。
.CBF 3D格式
在ZFXEngine中,我们使用.CBF文件格式。这个格式是我们自己定义的文件格式类型。我们希望这个格式能够支持如下的几个功能:
- 支持顶点(Vertex)数据
- 支持面(Face)数据
- 支持Mesh数据
- 支持材质
- 支持骨骼动画(Joint)
为此,我们分别定义了如下的几个结构体:
<span style="font-family:Microsoft YaHei;">/**
* Define the Chunk Header
*/
typedef struct _CHUNKHEAD
{
UCHAR ucIdentifier[4]; // identifier
UCHAR ucName[32]; // name
UCHAR ucAuthor[32]; // author
UCHAR ucEmail[32]; // E-mail
UCHAR ucType; // type
UCHAR ucVersion; // version
ULONG ulNumVertices; // number vertices
ULONG ulNumIndices; // number indices
ULONG ulNumFaces; // number faces
ULONG ulNumMeshs; // number meshes
UINT uiNumMaterial; // number material
UINT uiNumJoints; // number joints
float fAnimationFPS; // FPS
float fCurrentTime; // current time
UINT uiNumFrames; // number frames
UINT uiNumAnimations; // number animations
}CHUNKHEAD_S;
typedef CHUNKHEAD_S* LPCHUNKHEAD ; // Chunk-Header
/**
* Define the VERTEX_3F_S
*/
typedef struct _VERTEX
{
float fXYZ[3]; // coordinates
float fUV0[2]; // texture coordinates 1
float fUV1[2]; // texture coordinates 2
ZFXVector fNormal; // normal vector
USHORT usReferences; // references
UINT uiBoneID_A; // bone-ID 1
float fWeight_A; // weight 1
UINT uiBoneID_B; // bone-ID 2
float fWeight_B; // weight 2
BYTE byFlags; // flags
}VERTEX_3F_S;
typedef VERTEX_3F_S* LPVERTEX_3F;
/**
* Define the FACE
*/
typedef struct _FACE
{
ULONG ulIndices[3]; // indices
ZFXVector fNormal; // normal vector
ULONG ulMeshID; // mesh-ID
UINT uiMaterialID; // material-ID
BYTE byFlags; // flags
}FACE_S;
typedef FACE_S* LPFACE;
/**
* Define the Mesh
*/
typedef struct _MESH
{
char cName[32]; // name
WORD wNumFaces; // number of faces
PWORD pIndices; // face index
UINT uiMaterialID; // material ID
BYTE byFlags; // flags
}MESH_S;
typedef MESH_S* LPMESH;
/**
* Define the Material
*/
typedef struct _MATERIAL
{
char cName[32]; // name
float fAmbient[4]; // ambient color
float fDiffuse[4]; // diffuse color
float fSpecular[4]; // specular color
float fEmissive[4]; // emissive color
float fSpecularPower; // specular power
float fTransparency; // transparency
char cTexture_1[128];// texture name
char cTexture_2[128];// texture name
BYTE byFlags; // flags
}MATERIAL_S;
typedef MATERIAL_S* LPMATERIAL ;
/**
* Define the Keyframe Rotation
*/
typedef struct _KF_ROT
{
float fTime; // time
ZFXVector vRotation; // rotation
}KF_ROT_S;
typedef KF_ROT_S* LPKF_ROT;
/**
* Define the Keyframe Position
*/
typedef struct _KF_POS
{
float fTime; // time
ZFXVector vPosition; // position
}KF_POS_S;
typedef KF_POS_S* LPKF_POS;
/**
* Define the JOINT
*/
typedef struct _JOING
{
char cName[32]; // descriptor
char cParentName[32]; // parent descriptor
WORD wParentID; // parent - ID
ZFXVector vRotation; // rotation
ZFXVector vPosition; // position
WORD wNumKF_Rotation; // number of rotations
WORD wNumKF_Position; // number of positions
LPKF_ROT pKF_Rotation; // key frame rotations
LPKF_POS pKF_Position; // key frame position
bool bAnimated; // animated or not
BYTE byFlags; // flags
ZFXMatrix sMatrix; // matrix
ZFXMatrix sMatrix_absolute; // matrix absolute
ZFXMatrix sMatrix_relative; // matrix relative
}JOINT_S;
typedef JOINT_S* LPJOINT;
/**
* Define the Animation
*/
typedef struct _ANIMATION
{
char cName[64]; // name string
float fStartFrame;// start frame
float fEndFrame; // end frame
bool bActive; // active or not
}ANIMATION_S;
typedef ANIMATION_S* LPANIMATION;</span>
这些结构体能够完整的描述一个3D模型,并且支持骨骼动画,完全满足我们的要求。
上面所有的格式表示的就是Chunk Based System里面的struct,是需要实际输出到文件中的数据。
除了上面的struct之外,我们还需要进行标记,下面是标记的结构定义:
<span style="font-family:Microsoft YaHei;">/**
* Define the CHUNK in the CBF file
*/
typedef struct _CHUNK
{
WORD wIdentifier;
ULONG ulSize ;
}CHUNK_S;
typedef CHUNK_S* LPCHUNK;
/**
* Define the CHUNK Identifier
*/
#define V1_BEGIN 0x0010 // File Begin
#define V1_HEADER 0x0100 // Header
#define V1_VERTEX 0x0200 // Vertices
#define V1_FACE 0x0300 // Faces
#define V1_MESH 0x0400 // Meshes
#define V1_MATERIAL 0x0500 // Material
#define V1_JOINT 0x0600 // Joints
#define V1_JOINT_MAIN 0x0610 // Joints Main
#define V1_JOINT_KEYFRAME_ROT 0x0620 // Keyf. - Rotation
#define V1_JOINT_KEYFRAME_POS 0x0630 // Keyf. - Position
#define V1_ANIMATION 0x0700 // Animation
#define V1_END 0x9999 // End-Chunk</span>
Begin标记和End标记的结构体通用一个结构CHUNK_S,在这个结构体中有wIdentifier标示,用于存放在下面定义的标示符,从而来确定是否为end标记或者begin标记。读者请注意,begin标记并不只是V1_BEGIN这个,除了V1_END是作为end标记外,其他的所有标示符都可以用来作为begin标记,仔细理解我这里说的话。
比如,我们可以这样来构建一个Chunk:
<span style="font-family:Microsoft YaHei;">CHUNK_S sChunk;
sChunk.wIdentifier = V1_HEADER ;
sChunk.ulSize = sizeof(CHUNKHEAD_S) ;
writetofile(sChunk);
writetofile(header);
CHUNK_S eChunk;
eChunk.wIdentifer = V1_END ;
eChunk.ulSize = 0 ;
writetofile(eChunk);</span>
我们并没有强制规定CHUNK的顺序。但是请注意,CHUNKHEAD_S必须作为第一个Chunk保存在.CBF文件中。这是因为当我们读取.CBF文件的时候,我们需要根据CHUNKHEAD_S里面的成员数据来控制后面的读取流程。
上面大部分的结构都是单一的Chunk,在将数据保存到.CBF中的时候,只要简单的将结构体输出即可。但是对于那些结构中含有指针,指针又指向另外的内存的这样的结构,我们就需要使用嵌套的Chunk来保存该结构。
比如,上面结构体中的JOINT_S中,含有对KF_POS_S和KF_ROT_S的指针。我们在对JOINT_S进行序列化操作的时候,如果单单将JOINT_S的结构体输出,那么这两个指针所指向的数据并不会自动的进行序列化操作。所以,我们需要再另外的单独将这两个指针中的内容作为单独的Chunk来保存。同时为了维持,这两个单独的chunk和JOINT_S所形成的CHUNK之间的包含关系,我们就将这两个单独的Chunk保存在JOINT_S所形成的Chunk中,从而利用Chunk Based Ssytem的可嵌套特性来保存它。
通过上面的描述,希望大家对我们的.CBF文件格式的数据组织方式有一个宏观的认识,具体的细节等我们详细讲解源代码的时候,再来向大家讲述。
Milk3D Shape软件及 Milk3D Shape ASCII文件格式解析
Milk3D Shape是一款免费的3D建模软件。大家可以到Milk3D Shape来进行下载。我第一个支持这种软件导出的格式,是因为它的格式支持骨骼动画,并且相对于3D Max和Maya来说,这款软件的操作更加的简单,快捷。所以,对于像我这样的非3D美工人员来说,非常适合使用。关于更多Milk3D的介绍,请读者自己到官网去了解。
在官网上,我们能够找到很多的插件或者开源代码。并且它也提供了一个C++版本的读取它的.ms3d格式的源码。我下载使用了下,