骨骼动画(Skeletal Animation)(三)
下面我们就来看看如何重载ParseObject方法来获得我们感兴趣的数据,不要担心,绝对简单。仔细看代码,你会发现只需要做一件事情,判断当前数据段的类型(通过GUID),分配对应的结构对象,然后从数据段拷贝数据(所有SDK自定义模板的GUID都在头文件rmxfguid.h中定义, 你需要把它加入你的工程中。所有预定义模板在这里可以找到)。先来看看如何获取当前数据段的GUID,
GUID objGUID; pDataObj->GetType( &objGUID );
简单吧,下面开始我们的分析之旅。x动画文件中骨骼是用Frame模板定义的,
template Frame { < 3D82AB46-62DA-11cf-AB39-0020AF71E433 > FrameTransformMatrix frameTransformMatrix; // 骨骼相对于父节点的坐标变换矩阵 Mesh mesh; // 骨骼的Mesh }
只有两个字段。FrameTransformMatrix就是一个matrix。Mesh稍微复杂,详细格式大家自己参考MSDN,我们也会有专门的代码来加载Mesh,现在关注Frame。为了加载Frame,我们要在程序中定义一个和Frame模板对应的数据结构,SDK中经默认提供了一个,那就是D3DXFRAME,
typedef struct _D3DXFRAME { LPSTR Name; // 骨骼名称 D3DXMATRIX TransformationMatrix; // 相对与父节点的坐标变换矩阵
LPD3DXMESHCONTAINER pMeshContainer; // LPD3DXMESHCONTAINER对象,用来 // 加载MESH,还有一些附加属性,见SDK
struct _D3DXFRAME *pFrameSibling; // 兄弟节点指针,和下面的子节点指针 // 一块作用构成骨骼的层次结构。 struct _D3DXFRAME *pFrameFirstChild; // 子节点指针. } D3DXFRAME, *LPD3DXFRAME;
这样一个结构已经足够容纳Frame模板中的数据并形成一个层次结构,不过为了我们程序的需要,我们还需要其他字段,为此我们通常会扩展D3DXFRAME,
typedef struct _D3DXFRAME_EX : public D3DXFRAME { D3DXMATRIX matCombined; // 存储当前节点相对于根节点的位置偏移矩阵,沿着到 // 到根骨骼的路径把所有的坐标变换矩阵相乘得到。
D3DXMATRIX matOriginal; // 在播放动画的时候有可能会改变原来结构中的 // TransformationMatrix,因此我们声名一个新的字段 // 将原来的坐标变换矩阵保存起来以便在需要的时候恢 // 复回去。
... // 忽略一些方法定义 }
我知道有些人已经按捺不住了,那么动手吧,
// 判断当前分析的是不是Frame节点 if( objGUID == TID_D3DRMFrame ) { // 引用对象直接返回,不需要做分析。一个数据段实际定义一次后可以 // 被其他模板引用,例如后面的Animation动画模板就会引用这里的Frame // 节点,标识动画关联的骨骼。 if( pDataObj->IsReference() ) return true;
// 创建D3DXFRAME_EX结构,准备拷贝数据 D3DXFRAME_EX *pFrame = new D3DXFRAME_EX();
// 拷贝名称 pFrame->Name = GetObjectName( pDataObj );
// 注意观察文件就可以发现一个Frame要么是根Frame,父节点不存在, // 要么作为某个Frame的下级Frame而存在。 if( NULL == pData ) { // 作为根节点的兄弟节点加入链表。 pFrame->pFrameSibling = m_pRootFrame; m_pRootFrame = pFrame; pFrame = NULL;
// 将自定义数据指针指向自己,供子 // 节点引用。 pData = ( void** )&m_pRootFrame; } else { // 作为传入节点的子节点 D3DXFRAME_EX *pDataFrame = ( D3DXFRAME_EX* )( *pData ); pFrame->pFrameSibling = pDataFrame->pFrameFirstChild; pDataFrame->pFrameFirstChild = pFrame; pFrame = NULL;
pData = ( void** )&pDataFrame->pFrameFirstChild; } }
结束了!是不是很简单,呵呵,记住我们只需要做一件事情,判断类型,分配匹配的对象然后拷贝数据,下面来分析Frame中的matrix,
// frame的坐标变换矩阵, 因为matrix必然属于某个Frame所以pData必须有效 else if( objGUID == TID_D3DRMFrameTransformMatrix && pData ) { // 我们可以肯定pData指向某个Frame D3DXFRAME_EX *pDataFrame = ( D3DXFRAME_EX* )( *pData );
// 先取得缓冲区大小,应该是个标准的4x4矩阵 DWORD size = 0; LPCVOID buffer = NULL;
hr = pDataObj->Lock( &size, &buffer ); if( FAILED( hr ) ) return false;
// 拷贝数据 if( size == sizeof( D3DXMATRIX ) ) { memcpy( &pDataFrame->TransformationMatrix, buffer, size ); pDataObj->Unlock();
pDataFrame->matOriginal = pDataFrame->TransformationMatrix; } }
这样大家应该对其他类型的模板数据分析代码都应该大致猜的出来了。具体的代码我就不在这里提供,只是简单的介绍一下它们的作用和关系,大家可以参考最后附上的工程。
Frame --
骨骼。正如大家已经看到的那样,我们可以用pFrameSibling和pFrameFirstChild两个字段来构成骨骼的层次结构。骨骼模板包含了当前骨骼相对父骨骼的坐标变换矩阵和骨骼对应的模型
Mesh --
模型。角色的顶点数据,包含vertex buffer, index buffer等。我们可以直接用普通的ID3DXMesh来加载其中的数据。除此之外,Mesh中还包含了SkinWeight模板。
SkinWeight --
骨骼关联的顶点已经该骨骼的坐标变换对该顶点的权重。实际中我们并不需要特殊处理这类模板数据,ID3DXMesh已经包含了对应的代码。
AnimationSet --
动画集合。例如角色的各种动作“Kill”,“Jump”等等,包含多个Animation。
Animation --
动画。由对应骨骼的名称和一组AnimationKey组成。
AnimationKey --
动画键。包含一组时间戳以及在对应时间戳应用到骨骼上的平移、缩放、旋转向量或者复合的坐标变换矩阵。
以上就是我们需要了解的全部了。至此,所有原料都已经准备齐全,各位大厨们下一步要做的就是骨骼动画这道小菜啦!
|