重要struct详解
SkinWeights
{
STRING transformNodeName; // 骨骼名
DWORD nWeights; // 该骨骼相关的顶点个数
array DWORD vertexIndices; // 受骨骼控制的顶点索引
array float weights; // 蒙皮各个顶点受本骨骼影响的权值
Matrix4*4 matrixOffset; // 骨骼偏移矩阵
}
这里的骨骼偏移矩阵 matrixOffset 是从原始Mesh数据计算出各个顶点相对于骨骼坐标系的原始坐标,仅与原始Mesh顶点数据相关联,在整个动画过程中是不变的。
而整个动画过程中变化的仅是 AnimationKey 中各个时刻的骨骼变换矩阵。
在绘制前求得 该骨骼当前的最终变换矩阵 = 骨骼变换矩阵 * 骨骼偏移矩阵。
每个骨骼的蒙皮信息用SkinWeights结构去描述,有多少块骨骼,就有多少个SkinWeights对象。
Skinned Mesh:将SkinWeights视作mesh的一部分。
d3dx9anim.h
//----------------------------------------------------------------------------
// D3DXFRAME:
// ----------
// This struct is the encapsulates a transform frame in a transformation frame
// hierarchy. The app can derive from this structure to add other app specific
// data to this
//----------------------------------------------------------------------------
typedef struct _D3DXFRAME //一个Frame可视为一个Bone
{
LPSTR Name;
D3DXMATRIX TransformationMatrix; //本骨骼的转换矩阵
LPD3DXMESHCONTAINER pMeshContainer; //本骨骼对应的存放Mesh数据信息的容器
struct _D3DXFRAME *pFrameSibling; //兄弟容器
struct _D3DXFRAME *pFrameFirstChild; //孩子容器
} D3DXFRAME, *LPD3DXFRAME;
//----------------------------------------------------------------------------
// D3DXMESHCONTAINER:
// ------------------
// This struct encapsulates a mesh object in a transformation frame
// hierarchy. The app can derive from this structure to add other app specific
// data to this.
//----------------------------------------------------------------------------
typedef struct _D3DXMESHCONTAINER
{
LPSTR Name; //容器名
D3DXMESHDATA MeshData; //Mesh数据, 可创建SkinMesh数据覆盖其
LPD3DXMATERIAL pMaterials; //材质数组
LPD3DXEFFECTINSTANCE pEffects; //对应的效果
DWORD NumMaterials; //材质数
DWORD *pAdjacency; //邻接三角形数组
LPD3DXSKININFO pSkinInfo; //蒙皮信息,包含各个蒙皮顶点及各个骨骼偏移矩阵等
struct _D3DXMESHCONTAINER *pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;
动画文件:
Frame是基本的组成单元,其允许嵌套,形成父子框架。
Frame 视作一个骨骼 Bone。(但可能Frame对象的个数多于骨骼数目,用作其他用途)。
在内存中,frame布局与x文件中一一对应。
每个Frame有一个 TransformationMatrix ,描述了该Frame相对于其父Frame的变换矩阵。则此Frame内的坐标与该矩阵相乘,可得到此坐标在父Frame坐标系中的坐标。
当mesh加载到内存后,只有一个无名框架 Root Frame 保存原始 Mesh 网格及所有的顶点信息;其他 子Frame 只是借用来描述各个骨骼的层次结构,每块骨骼对应的蒙皮顶点信息,由 root mesh 中的相应骨骼的 SkinWeights 中的蒙皮顶点索引描述,在动画过程中,各个顶点的新坐标借助SkinWeights中的顶点索引来进行重新计算。
因此,读取x文件之后,会产生多个D3DXFRAME对象,但只有一个D3DXMESHCONTAINER对象。
建立一个自定义数据容器类:
ID3DXAllocateHierarchy
//--------------------------------------------------------------------------------------
// Name: class CAllocateHierarchy
// Desc: Custom version of ID3DXAllocateHierarchy with custom methods to create
// frames and meshcontainers.
//--------------------------------------------------------------------------------------
class CAllocateHierarchy : public ID3DXAllocateHierarchy
{
public:
STDMETHOD( CreateFrame )( THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame );
STDMETHOD( CreateMeshContainer )( THIS_
LPCSTR Name,
CONST D3DXMESHDATA *pMeshData,
CONST D3DXMATERIAL *pMaterials,
CONST D3DXEFFECTINSTANCE *pEffectInstances,
DWORD NumMaterials,
CONST DWORD *pAdjacency,
LPD3DXSKININFO pSkinInfo,
LPD3DXMESHCONTAINER *ppNewMeshContainer );
STDMETHOD( DestroyFrame )( THIS_ LPD3DXFRAME pFrameToFree );
STDMETHOD( DestroyMeshContainer )( THIS_ LPD3DXMESHCONTAINER pMeshContainerBase );
CAllocateHierarchy()
{
}
};
STDMETHOD
#define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method
#define STDMETHODCALLTYPE __stdcall
这样展开后:
STDMETHOD 相当于 virtual HRESULT __stdcall
四个重载函数:
CreateFrame():
CreateMeshContainer(): 将传入的参数数据保存到自己的 d3dxmeshcontainer 对象中,其中所有数组所需的空间都要在全局堆中 new出来,且注意在 DstroyMeshContainer 中 Delete 掉。最重要的是蒙皮信息处理。当以上工作完成之后,调用GenerateSkinnedMesh()创建SkinMesh。
DestroyFrame()
DestroyMeshContainer()
绘制显示动画:
1、首先开启顶点混合,应用相关的Vertex Blending技术。比如:
V( pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ) );
2、OnFrameMove函数:每帧渲染前的更新控制
最最关键的动画更新函数即为:
g_pAnimController->AdvanceTime( fElapsedTime, NULL );
AdvanceTime就是关键所在,它控制模型的动画。
Microsoft.DirectX.Direct3D.AnimationRootFrame AnimationController.AdvanceTime(double)是用来滚动模型时间轴的方法,它的参数代表滚动幅度。D3D的时间轴是3DMAX的十分之一,以0~10为一个完整周期。
3、OnFrameRender函数:每帧的渲染操作
注:
OnFrameMove和OnFrameRender——前者负责在渲染过程开始前更新场景,后者负责渲染场景。我们自己在构建框架的时候也建议采用这种更新和渲染分开进行的机制,简单说就是在帧刷新前发出更新场景事件,随后再发出渲染场景事件。
4、