SkinnedMesh DX9

本想学习PRT技术,但是觉得这个技术太高级,而且就中国现在的技术来看,还未到这个程度。但是PRT技术一定要学,一定要掌握,才能不致于落后国际水平太多。这几天学习了一下骨骼动画。以前记得参加齐鲁软件大赛的时候,用Ogre很简单的就实现了骨骼动画。现在终于可以自己在DX基础上亲手完成这个3d游戏中最重要的技术。

 

骨骼动画中有许多我们需要注意的。简单屡一下骨骼动画中需要知道的基本知识点。

  1. 首先我们需要一个骨架。这些骨架中的骨骼有父子关系,有兄弟关系。父关节的运动是会带动子关节的。就像我们的上臂动会带动前臂一样。
  2. 骨架和人物网格建立绑定空间中。因为每个骨骼都可以绕着它们的关节旋转,要表达这些旋转我们必须在关节处建立一个骨骼空间,这样就能方便的表示骨骼在关节上的旋转。骨骼的这些旋转运动(或者直接说骨骼)是用矩阵表示。

  3. 在骨骼空间中,我们采用三个向量来表示骨骼运动(或者说骨骼)。RST(Rotation,Scaling,Translation)Values。Rotation代表骨骼在骨骼空间中的旋转变换,由四元数(Quaternion)表示,因为需要平滑动画,我们经常需要对两个动作中的旋转进行差值,所以必须用四元数。Scaling表示骨骼的缩放变换。Translation表示子骨骼相对于父骨骼的偏移。因为父骨骼有骨骼大小,而父骨骼自身无法表示。所以为了给父骨骼腾出骨骼空间,子骨骼必须要进行相当于父骨骼大小的偏移

  4. 每个骨骼在运动时会牵扯附着在其表面的皮肤,也就是相应部分的人物网格。为了让骨骼旋转能运用于这些网格,我们必须将网格变换到骨骼空间中去。这样每个骨骼中应该包含相应的网格从绑定空间到其骨骼空间的变换矩阵,称为平移矩阵(OffsetMatrix)。
  5. 每个骨骼的最终位置不光取决于其自身的旋转,还取决于其父骨骼的旋转。所以要想表示每个骨骼最终在角色空间(我认为一般情况下角色空间和绑定空间是一样的,只要骨架不进行偏移)。这样我们为了需要求出每个骨骼在角色空间中位置,我们需要将其父节点的变换应用与子节点的变换。而父节点也会被其父节点影响。这是一个递归过程,所以我们必须找到祖先。一般就是盆骨。从祖先往下,将其变换运用到其子节点,再由子节点运用到其子子节点。这样我们为每个骨骼求出了一个变换矩阵,这个矩阵我们称为组合矩阵(CombineMatrix),这个矩阵可以将骨骼变换从骨骼空间转换到角色空间。
  6. 人物网格最终在角色空间中的位置,由平移矩阵(OffsetMatrix)和组合矩阵(CombineMatrix)共同决定。因为网格应该先变换到骨骼空间,所以采用OffsetMatrix,然后再由骨骼空间变换到角色空间,所以采用CombineMatrix。
  7. 这样只要我们不断更新每个骨骼的旋转矩阵,然后重新建立CombineMatrix。我们再和OffsetMatrix进行组合,就能不断的得到新的骨骼姿态,就能让人物网格动起来。
  8. 为了达到关节处的平滑过度,也就是关节旋转不是完全僵硬的。我们采用一个顶点可以被多个骨骼影响的方法。这样在关节处的顶点,受关节处两端的骨骼影响,这样关节一端的骨骼运动不会完全带动这个顶点运动,而这个顶点还会受另一端骨骼的牵制。就想人皮一样,在关节处会被拉伸。所以称为Skinned Mesh(我称其为蒙皮网格)。而这种思想被称为顶点混合(Vertex Blending ),可以这么理解,将关节处顶点分别用两端骨骼的矩阵进行变换,得出两个新的顶点,然后将这两个顶点按照一定比率进行插值(混合)得到最终的顶点,所以称为顶点混合。而且在骨骼动画中经过长期研究发现,正常情况下一个顶点最多被四个骨骼影响。所以DX中顶点的数据中,最多有四个混合权重值。

接下来介绍SkinnedMesh案例中的重点和难点。

  1. DX中每个骨骼由D3DXFRAME表示。虽然名字都点不恰当,但是Frame确实很通用,因为骨骼运动不光可以用于动物,还可以用于其它任何网格。
  2. 由于D3DXFRAME中没有组合矩阵的信息,所以我们需要派生D3DXFRAME,添加上组合矩阵。
  3. DX中用D3DXMESHCONTAINER表示人物网格及其相应的材质等其他附属信息。需要特别注意的是D3DXFRAME有D3DMESHCONTAINER的指针。所以每个骨骼都有指向人物网格容器的指针。但是一般情况下(人物网格只有一个),只有一个D3DXFRAME的指针是有效的。
  4. DX中,为了让我们对骨架层次(Hierarchy)的控制更灵活,DX在创建骨架时让我们自己实现D3DXFRAME和D3DXMESHCONTAINER的创建和销毁接口。而我们需要做的就是继承ID3DXAllocateHierarchy,并改写其中有且仅有的四个虚函数。
  5. 在创建D3DXMESHCONTAINER和创建D3DXFRAME接口中一定要注意,传向该函数的所有指针对应的内存都属于DX,我们最好自己重新复制或者添加引用。因为我们DX创建的内存我们无法随心所欲的控制。所以我们需要对网格接口添加引用等操作,这样只有当我们也释放这个接口,这个接口对应的内存再会被销毁。
  6. 在创建Effect文件时要注意,我们必须要知道每个顶点至多可以被几个骨骼影响。因为这个数字会影响顶点数据中混合权重数组(Weight)的值。假如最多影响是2,那么4个混合权重值中前两个是有效的,后面的就无法保证了。所以我们只能用前两个混合权重值,另外两个不要碰。

我的经验教训:

  1. 为了这个错误,程序出现和难以理解的内存泄露,并耗费我大约1个多小时的时间。错误是这样的,在建立完骨架之后,我从D3DXFRAME中找到的D3DXMESHCONTAINER的指针。然后对其中的mesh添加的引用:pMeshContainer = pFrame->pMeshContainer; pMeshContainer->MeshData.pMesh->AddRef();然后在最后的Destroy中,使用这样的语句释放网格:SAFE_RELEASE( pMeshContainer->MeshData.pMesh );就是这样很正常的处理隐藏的重大问题。解决的方法是修改Destroy的销毁语句:pMeshContainer->MeshData.pMesh->Release(); 当我自己改到这里发现没内存泄露之后,楞了良久,才明白。pMeshContainer指向的是一块共有内存,如果我对这个共有内存进行修改,那么其他试图对这个共有内存进行的操作就会出错。因为SAFE_RELEASE中不光释放对象,还将对象置为空。所以其他的SAFE_RELEASE就行不通了。所以我们要对物体网格指针备份一个专门给自己用。原本看案例的代码发现其备份了一个,我还想又是一个多余操作,现在看来还是自己太大意啊。毕竟MS的总体水平排在那。

 

以上都是自己这个水平的理解。如有不对,敬请指出。共同进步,不胜感激。

最后附上很好的学习资料:d3dx_skinnedmesh.pdf,可以到我的资源处下载。

你可能感兴趣的:(游戏,hierarchy,translation)