DX 骨骼动画

简介:

第一,了解骨骼结构(Skeletal Structures)和骨层级(Bone Hierarchies):

骨骼结构就是连续很多的骨头(Bone)相结合,形成的骨层级。第一个骨头叫做根骨(root bone),是形成骨骼结构的关键点。其它所有的骨骼作为孩子骨(child bone)或者兄弟骨(sibling bone)附加在根骨之上。所谓的“骨”用一个帧(frame)对象表示。在Directx中,用一个D3DXFRAME结构或者X文件中的Frame template来表示帧对象。下面看一下Frame template和D3DXFRAME结构的定义:

template Frame
{
        < 3D82AB46-62DA-11cf-AB39-0020AF71E433 >
        FrameTransformMatrix frameTransformMatrix;      // 骨骼相对于父节点的坐标变换矩阵,就是一个matrix
        Mesh mesh;                                      // 骨骼的Mesh
}

typedef struct _D3DXFRAME
{
LPSTR                   Name;                   // 骨骼名称 
D3DXMATRIX         TransformationMatrix;        // 相对与父节点的坐标变换矩阵
LPD3DXMESHCONTAINER     pMeshContainer; // LPD3DXMESHCONTAINER对象,
//用来加载MESH,还有一些附加属性,见SDK
struct _D3DXFRAME       *pFrameSibling;         // 兄弟节点指针,和下面的子节点指针
// 一块作用构成骨骼的层次结构。    
    struct _D3DXFRAME       *pFrameFirstChild;   // 子节点指针
} D3DXFRAME, *LPD3DXFRAME;


注意D3DXFRAME * pFrameSibling和D3DXFRAME * pFrameFirstChild,主要是利用这两个指针形成骨层级。pFrameSibling把一个骨头连接到兄弟层级,相对的,pFrameFirstChild把一个骨头连接到子层级。通常,你需要用建模软件为你的程序创建那些骨骼结构,输出骨层级到X文件以便使用。Microsoft有3D Studio Max和Maya的输出插件(exporter),可以输出骨骼和动画数据到X文件。很多建模程序也都有这样的功能。


利用D3DXFRAME pointers指针形成了一个兄弟帧和孩子帧的链表。

在前面template Frame中已经提及过每个Frame数据对象中存放着一个变换矩阵,这个矩阵描述了该骨骼相对于父骨骼的位置。另外在根Frame数据对象中内嵌了一个标准的Mesh数据对象。Frame定义了骨骼的层级,而Mesh中的SkinWeights数据对象定义了Frame代表的骨头。我们用D3DXFRAME结构容纳从X文件加载进来的Frame数据对象。为了更好的容纳Frame数据对象,我们需要扩展下D3DXFRAME结构:

struct D3DXFRAME_EX : D3DXFRAME 
{
D3DXMATRIX matCombined;   // 组合变换矩阵,用于储存变换的骨骼矩阵
D3DXMATRIX matOriginal;   // 从X文件加载的原始变换矩阵 
D3DXFRAME_EX()
{
    Name = NULL;
    pMeshContainer = NULL;
    pFrameSibling = pFrameFirstChild = NULL;
    D3DXMatrixIdentity(&matCombined);
    D3DXMatrixIdentity(&matOriginal);
    D3DXMatrixIdentity(&TransformationMatrix);
}

~D3DXFRAME_EX()

    delete [] Name;          Name = NULL;
    delete pFrameSibling;    pFrameSibling = NULL;
    delete pFrameFirstChild; pFrameFirstChild = NULL;
}
}

利用我们以前介绍的cXParse类可以遍历X文件的数据对象,从而加载出Frame数据对象。下面的代码都是写在方法ParseObject中,如下:

// 判断当前分析的是不是Frame节点
if( objGUID == TID_D3DRMFrame )
{
// 引用对象直接返回,不需要做分析。一个数据段实际定义一次后可以被其他模板引用,例
//如后面的Animation动画模板就会引用这里的Frame
// 节点,标识动画关联的骨骼。
if( pDataObj->IsReference() )
return true;
        // D3DXFRAME_EX为D3DXFRAME的扩展结构,增加些数据成员
        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数据对象。这个函数如下:

D3DXFRAME_EX *FindFrame(D3DXFRAME_EX *Frame, char *Name)
{
if(Frame && Frame->Name && Name) {
// 如果名字找到,返回一个Frame指针
if(!strcmp(Frame->Name, Name)) // strcmp函数比较两个字符串,如果两个字符串相等,返回0
return Frame;
}
// 在sibling frames找匹配的名字
if(Frame && Frame->pFrameSibling) {
D3DXFRAME_EX *FramePtr = FindFrame((D3DXFRAME_EX*)Frame->pFrameSibling, Name);
if(FramePtr)
return FramePtr;

}
// 在child frames找匹配的名字
if(Frame && Frame->pFrameFirstChild) {
D3DXFRAME_EX *FramePtr = FindFrame((D3DXFRAME_EX*)Frame->pFrameFirstChild,Name);
if(FramePtr)
return FramePtr;
}
// 如果没有找到,返回 NULL
return NULL;
}

如果你想找到一个叫“Leg”的Frame,可以把“Leg”传入FindFrame函数,并且提供指向RootFrame的指针:
// pRootframe 为D3DXFRAME_EX root frame 指针
D3DXFRAME_EX *Frame = FindFrame(pRootFrame, "Leg");
if(Frame) {
// 可以在这里做一些处理,比如旋转操作
// 你在这里可以稍微的旋转这个骨头
D3DXMatrixRotationY(&Frame->TransformationMatrix, 1.57f);
}

一旦你修改变换骨头,你需要更新整个骨骼层级,也就是把变换的组合矩阵存入D3DXFRAME_EX结构的matCombined成员中,用于后面的渲染。下面的函数应该增加到D3DXFRAME_EX结构中,如下:

void UpdateHierarchy(D3DXMATRIX *matTransformation = NULL)
{
    D3DXFRAME_EX *pFramePtr;
    D3DXMATRIX matIdentity;
    // 如果为空,用一个全同矩阵
    if(!matTransformation) {
      D3DXMatrixIdentity(&matIdentity);
      matTransformation = &matIdentity;
    }
    // 把变换矩阵组合到matCombined中
    matCombined = TransformationMatrix * (*matTransformation);
    // 更新兄弟层级
    if((pFramePtr = (D3DXFRAME_EX*)pFrameSibling))
      pFramePtr->UpdateHierarchy(matTransformation);
    // 更新孩子层级
    if((pFramePtr = (D3DXFRAME_EX*)pFrameFirstChild))
      pFramePtr->UpdateHierarchy(&matCombined);
}
现在matCombined储存着每个骨骼相对于原点的变换矩阵,然后只要把各个顶点附在相应的骨骼上,就能渲染了。

第三,使用蒙皮网格:

蒙皮网格和普通网格的唯一不同点就是看XskinMeshHeader和SkinWeights模版是否存在。如果把这两个模版从任何一个蒙皮网格里面移走的话,就可以得到一个普通网格。在X文件中,我们将会发现一个GUID为TID_D3DRMMesh的模版,这表示模版里面存有一个网格。利用D3D的帮助函数D3DXLoadSkinMeshFromXof将会加载蒙皮网格和其它补充性数据。只需要向它传递一个IDirectXFileData指针,然后它将为你做剩下的事情。现在介绍下D3DXLoadSkinMeshFromXof函数:

HRESULT D3DXLoadSkinMeshFromXof(
LPD3DXFILEDATA pxofMesh,        //X文件数据接口
DWORD Options,                   //加载参数
LPDIRECT3DDEVICE9 pD3DDevice, //使用的三维设备
LPD3DXBUFFER * ppAdjacency,      //邻接信息缓冲接口
LPD3DXBUFFER * ppMaterials,       //材质缓冲接口
LPD3DXBUFFER * ppEffectInstances, //效果实例接口
DWORD * pMatOut,                 //材质数
LPD3DXSKININFO * ppSkinInfo,      //蒙皮信息接口
LPD3DXMESH * ppMesh             //加载的网格模型接口
);

作者:码瘾少年·麒麟子 
出处:http://www.cnblogs.com/geniusalex/ 
蛮牛专栏:麒麟子 
简介:09年入行,喜欢游戏和编程,对3D游戏和引擎尤其感兴趣。 
版权声明:本文版权归作者和博客园共有,欢迎转载。转载必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载:http://www.cnblogs.com/geniusalex/archive/2009/03/23/1940528.html

目录
相关文章
|
8月前
|
图形学
浅谈Unity之贝塞尔曲线
贝塞尔曲线的创建
|
17天前
|
前端开发 JavaScript 搜索推荐
webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)
webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)
19 1
webgl canvas系列——animation中基本旋转、平移、缩放(模拟冒泡排序过程)
|
26天前
【着色器实现Flicker“DJ”闪烁效果_Shader效果第十五篇】
【着色器实现Flicker“DJ”闪烁效果_Shader效果第十五篇】
|
5月前
|
算法框架/工具 图形学
Unity 四元数
Unity 四元数
|
10月前
|
算法
DX 纹理像素格式转换算法 R10G10B10A2 转 R8G8B8A8
DX 纹理像素格式转换算法 R10G10B10A2 转 R8G8B8A8
92 0
|
Serverless 图形学
Unity基础——三角函数与坐标系
Unity基础——三角函数与坐标系
290 0
|
图形学
【Unity3D Shader】学习笔记-UV画图
前言 本篇介绍在片段着色器中进行画图操作,主要使用极坐标的方式。具体极坐标方程就需要花时间去学习了,可以通过其中一两个公式进行详细的研究,这样大部分的曲线图就可以绘制出来了。 一、心形线的方程:r = 1 - asinθ
273 0
【Unity3D Shader】学习笔记-UV画图
|
机器学习/深度学习
【OpenGL】十六、OpenGL 绘制四边形 ( 绘制 GL_QUADS 四边形 )
【OpenGL】十六、OpenGL 绘制四边形 ( 绘制 GL_QUADS 四边形 )
414 0
【OpenGL】十六、OpenGL 绘制四边形 ( 绘制 GL_QUADS 四边形 )
|
数据可视化 图形学
Unity 之 贝塞尔曲线介绍和实际使用
Unity 中对贝塞尔曲线的实战应用,制作可视化操作曲线工具,文末附工具源码链接~
799 0
Unity 之 贝塞尔曲线介绍和实际使用