网格模型动画一般有两种:一种是渐变动画;另一种是骨骼动画,这种动画包含在网格模型之中,通过网格模型不同部分之间的相对运动来实现动画。
骨骼动画基本原理
骨骼动画是目前最流行也最复杂的角色动画,它包含以下几个重要元素:骨骼、动画。骨骼动画思想的起源很简单,自然界中的大多数动物都拥有一套骨骼,身体的皮毛血肉都依附于骨骼,当骨骼开始运动的时候,依附于对应骨骼的皮毛血肉都随骨骼一起运动。在三维图形编程领域,角色的躯体是由网格模型来表示的,网格模型通常由大量三角形图元组成,而三角形又是由顶点组成的。为了模仿现实世界中角色自身的动作,就需要为角色网格模型添加一套骨骼,同时需要确定哪些顶点依附于哪块骨骼,这样当骨骼运动时就能牵引依附骨骼的顶点一起运动,这就是骨骼动画的基本原理。
骨骼动画模型的骨骼是以树状层次结构组织起来的,整个骨骼结构中有一块根骨骼,其他的骨骼都直接或间接连接到根骨骼上,形成角色模型的整个骨骼框架。
一般每块骨骼都带有两个矩阵,一个是初始变换矩阵(LocalTransformMatrix),表示骨骼的初始位置;另一个是组合变换矩阵(CombinedTransformMaitrx),用于对骨骼进行各种变换,从而实现角色动画。在每次渲染角色模型前,需要更新整个骨骼层次结构,组合每个连续的变换,将上层骨骼的运动传递到下层骨骼,这个原理可表示为:
(子骨骼的)CombinedTransformMaitrx =
(子骨骼的)LocalTransformMaitrx x (父骨骼的)CombinedTransformMaitrx
骨骼的组合变换矩阵是随动画的播放不断变化的,而它的初始变换矩阵一般是不改变的,正是所有骨骼的这些矩阵相互作用才牵引着顶点的变化,从而实现了骨骼动画。因为一次变换只能将骨骼变换到一个特定位置,要形成连续的动画就需要一帧一帧地连续改变骨骼的位置,每次改变骨骼的位置都需要一个骨骼变换矩阵,在网格模型中不可能保存任意时刻骨骼的变换矩阵,通常是保存关键时间点骨骼的变换矩阵(即关键桢),然后在播放角色动画时,根据播放时间进行插值得到任意时刻骨骼的变换矩阵,从而形成连续的角色动画。
骨骼动画是通过骨骼变换矩阵实现的,在网格模型中保存的也是关键时间点骨骼的变换矩阵,因此插值就是针对这些关键时间点上的骨骼变换矩阵进行的。假设在s1时刻骨骼变换矩阵是mat1,在s2时刻骨骼变换矩阵是mat2,在s1和s2之间的任意时刻s,其骨骼变换矩阵mat为:
mat = (1-w) * mat1 + w * mat2
其中w是权值,通过这个权值来调节在s时刻骨骼变换矩阵中mat1和mat2所占的比重,对骨骼变换矩阵进行插值最简单的方法是线性插值,这时w
= (s-s1) / (s2-s1)
骨骼动画类的设计与实现
我们需要将骨骼动画网格模型的相关操作封装到一组类和结构中,这一组类和结构可以看成一套完整的骨骼动画网格模型接口,它们之间的关系如下图所示:
其中,D3DXFRAME和D3DXMESHCONTAINER是Direct3D提供的两个结构,D3DXMESHCONTAINER结构用于保存模型的网格数据,D3DXFRAME用于保存模型的骨骼框架。结构D3DXMESHCONTAINER_DERIVED继承自Direct3D提供的结构D3DXMESHCONTAINER,结构D3DXFRAME_DERIVED继承自Direct3D提供的结构D3DXFRAME,分别进行了相应的扩充,使其能够保存所需要的其他数据。
cAllocateHierarchy类负责从动画网格模型文件加载各种数据,该类继承自Direct3D中的ID3DXAllocateHierarchy接口。
cAnimMesh类是唯一对外开放的类,它通过cAllocateHierarchy类的对象从模型文件中加载所需的数据,并负责处理骨骼动画信息以及网格模型的渲染。
继承并扩展结构体D3DXFRAME
为了在渲染网格模型的同时播放包含在网格模型的动画,需要处理两个单独的实体:骨骼结构(即框架结构)和网格模型。框架结构和网格模型的相关数据分别使用D3DXFRAME_DERIVED和D3DXMESHCONTAINER_DERIVED结构保存。需要指出的是.x文件中的一个网格模型可以由多个框架和多个网格组成,但具体到某一个框架时,它一般只有一个网格,当然它也可以有多个网格。
为了方便加载骨骼动画网格模型,Direct3D提供了两个重要的结构体:D3DXFRAME和D3DXMESHCONTAINER,其中D3DXFRAME用来加载框架,其定义如下:
Encapsulates a transform frame in a transformation
frame hierarchy.
typedef struct D3DXFRAME {
LPSTR Name;
D3DXMATRIX TransformationMatrix;
LPD3DXMESHCONTAINER pMeshContainer;
D3DXFRAME * pFrameSibling;
D3DXFRAME * pFrameFirstChild;
} D3DXFRAME, *LPD3DXFRAME;
Members
- Name
- Name of the frame.
- TransformationMatrix
- Transformation matrix.
- pMeshContainer
- Pointer to the mesh container.
- pFrameSibling
- Pointer to a sibling frame.
- pFrameFirstChild
- Pointer to a child frame.
Remarks
An application can derive from this structure to add
other data.
显然在实现动画网格模型的绘制前,不仅要得到每个框架的初始变换矩阵,同时还要得到从该框架的所有父节点到本级框架的组合变换矩阵,这是因为任何一个父框架的位置改变都会影响该框架自身位置的变化,所以在此将结构D3DXFRAME扩展为D3DXFRAME_DERIVED,在D3DXFRAME_DERIVED中添加一个成员变量CombinedTransformMaitrx,用TransformationMatrix记录在任何动画数据未加载前框架的初始变换矩阵,也就是该框架的初始位置,用CombinedTransformMaitrx来记录从所有的父框架到该框架自身所积累起来的组合变换矩阵,这样就将整个网格模型很方便地组织起来了。
结构D3DXFRAME_DERIVED的定义如下:
struct D3DXFRAME_DERIVED : public D3DXFRAME
{
D3DXMATRIX CombinedTransformMatrix;
};
继承并扩展结构体D3DXMESHCONTAINER
结构体D3DXMESHCONTAINER用来加载每个具体网格模型的数据,其定义如下:
Encapsulates a mesh object in a transformation frame
hierarchy.
typedef struct D3DXMESHCONTAINER {
LPSTR Name;
D3DXMESHDATA MeshData;
LPD3DXMATERIAL pMaterials;
LPD3DXEFFECTINSTANCE pEffects;
DWORD NumMaterials;
DWORD * pAdjacency;
LPD3DXSKININFO pSkinInfo;
D3DXMESHCONTAINER * pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;
Members
- Name
- Mesh name.
- MeshData
- Type of data in the mesh.
- pMaterials
- Array of mesh materials.
- pEffects
- Pointer to a set of default effect parameters.
- NumMaterials
- Number of materials in the mesh.
- pAdjacency
- Pointer to an array of three DWORDs per triangle
of the mesh that contains adjacency information.
- pSkinInfo
- Pointer to the skin information interface.
- pNextMeshContainer
- Pointer to the next mesh container.
结构体D3DXMESHCONTAINER中没有记录网格模型的纹理信息,所以将该结构体扩展为D3DXMESHCONTAINER_DERIVED,定义如下:
struct D3DXMESHCONTAINER_DERIVED : public D3DXMESHCONTAINER
{
IDirect3DTexture9** ppTextures;
};
其中ppTextures用来存储网格模型的纹理对象。