骨骼动画及微软示例:SkinnedMesh的解析骨骼动画是D3D的一个重要应用。尽管微软DXSDK提供了示例SkinnedMesh,但由于涉及众多概念和技术细节,示例相对于初学者非常复杂,难以看懂。在此,提供一些重要问题评论,以使初学者走出迷局,顺利上手。文中所述都是参照各种资料加上自己的理解,也有可能出些偏差,有则回贴拍砖,无则权当一笑。V1M6J#X$P8_[0Tb一骨骼动画原理原理方面在网上资料比较多,大家都基本明白。在此说一下重点:总体上,绝大部分动画实现原理一致,就是“提供一种机制,描述各顶点位置随时间的变化”。有三种方法:!|'s9_9L;M!x_1.1关节动画:由于大部分运动,都是皮肤随骨骼在动,皮肤相对于它的骨骼本身并没有发生运动,所以只要描述清楚骨骼的运动就行了。用矩阵描述各个骨骼的相对于父骨骼运动。(大多运动都是旋转型)易知,从子骨骼用矩阵乘法累积到最顶层根骨骼,就可以得到每个子骨骼相对于世界坐标系的转换矩阵。这种动画,只须用普通Mesh保存最初始的各顶点坐标,以及一系列后续时刻所对应的各骨骼的运动矩阵。不用保存每时刻的顶点数据,节省了大量存储空间。而且比较灵活,可以利用关键帧插值运算,便于通过运算调节动作。缺点是在两段骨骼交接处,容易产生裂缝,影响效果。1.2渐变动画:通过保存一系列时刻的顶点坐标来完成动画。虽然比较逼真,但占用大量空间,灵活性也不高。1.3骨骼蒙皮动画(skinnedMesh)$T3P#J,y4F9Q&rC-R6U相当于上面两方法的折中。现在比较流行。/A'R4r5L;R4C在关节动画的基础上,利用顶点混合(VertexBlend)技术,对于关节附近的顶点,由影响这些顶点的两段(或多段)骨骼运动,分别赋以权值,共同决定顶点位置。相当于在骨骼关节上动态蒙皮,有效解决了裂缝问题。+r2E/c+O+R:o这里,引入一个D3D技术概念:“VertexBlending”---顶点混合技术。比如说,你肯定用过SetTransform(D3DTS_WORLD,....),但SetTransform(D3DTS_WORLDMATRIX(i),....)是不是很奇怪?这个问题后文会讲到。你也可以在微软的DXSDK的帮助文件中搜索“GeometryBlending”主题,有裂缝及其解决办法图示。Z.u{1~:rA8u二X文件如何保存骨骼动画理解X文件格式,对用好相关的DX函数是非常重要的。不含动画的普通X文件,有一个Mesh单元,保存了各顶点信息、各三角面的索引信息、材质种类及定义等。)f&N5t\,O~;G7F:Z0o.c动画X文件,则在这个单元中增加了“各骨骼蒙皮信息”、“骨骼层次及结构信息”、“各时刻骨骼矩阵信息”等。(?m%A5[1w(q:\2.1网格蒙皮信息:首先,在Mesh{}单元中,在原有的普通网格顶点数据基础上,新增了XSkinMeshHeader{}结构,以及多个SkinWeights{}结构。用以描述各个骨骼的蒙皮信息。0}S2I;C;b2b.t&Z7R其中,XSkinMeshHeader是总括,举一实例,如下:K-t.??9J3r9G0b2G8D.|XSkinMeshHeader{&a2`8J-o(l-p%mR$p8R2,//一个顶点可以受到骨骼影响的最大骨骼数,可用于计算共同作用时减少遍历次数x7`&l!A0d)b#O9{5@7G4,//一个三角面可以受到骨骼影响的最大骨骼数。这个数字对硬件顶点混合计算提出了基本要求。35//当前Mesh的骨骼总数。}由于每个骨骼的蒙皮信息都需要用SkinWeights结构去描述,所以有多少块骨骼,在Mesh中就有多少个SkinWeights对象。1l%q9V4j+}/?3J#_+s注意,一般把SkinWeights视作Mesh的一部分。这种Mesh又称SkinnedMesh(蒙皮网格)SkinWeights结构如下:+t4\+`0B/q&`3Y4G;K{STRINGtransformNodeName;//骨骼名8p-J7G/e-y+B8[DWORDnWeights;//权重数组的元素个数,即该骨骼相关的顶点个数arrayDWORDvertexIndices[nWeights];//受该骨骼控制的顶点索引,实际上定义了该骨骼的蒙皮2}-Q!S1M%l)Qarrayfloatweights[nWeights];//蒙皮各顶点的受本骨骼影响的权值$LG(`*C$pMatrix4x4matrixOffset;//骨骼偏移矩阵,用来从初始Mesh坐标,反向计算顶点在子骨骼坐标系中的初始坐标。0d,c#S,cC)R}0K2Q,Y)@/Y;q0d#R在有的书中,把上面的matrixOffset叫骨骼权重矩阵,是不恰当的。应该称为骨骼偏移矩阵比较合适。*d*J2\:S-{*L)W4N[问题]在整个动画过程中,子骨骼运动矩阵的数值是不断变化的。上面的骨骼偏移矩阵变化吗?有没有必要重新计算?它在什么时候使用?1|1u4i%q1M,L$X&}P*j答:各骨骼的偏移矩阵matrixOffset专门用来从原始Mesh数据计算出各顶点相对于骨骼坐标系的原始坐标。在绘制前,把它与当前变换矩阵相乘,就可以得到该骨骼的当前的最终变换矩阵。总之,骨骼偏移矩阵是与原始Mesh顶点数值相关联的,在整个动画过程中是不变的,也不应该变。在动画过程中变化是当前骨骼变换矩阵,可由.X中的AnimatonKey中的各时刻矩阵得到。这个矩阵乘法在示例中的对应代码如下:D3DXMatrixMultiply(&matTemp,&pMeshContainer-pBoneOffsetMatrices[iMatrixIndex],pMeshContainer-ppBoneMatrixPtrs[iMatrixIndex]);:^9~+t8a#U;v'i即,D3DXMatrixMultiply(输出最终世界矩阵,该骨骼的偏移矩阵,该骨骼的变换矩阵)'F8z;~#v.WS2.2骨骼层次信息4M2H6]&^3?-^2E在X文件中,Frame是基本的组成单元。又称框架Frame。一个.x可以有多个Frame。(注意此处的Frame不是帧,与帧没什么关系)框架Frame允许嵌套,这样就存在父子框架了。而并列的框架,称为兄弟框架。这两种关系组合在一起,即可以纵深,又可以并列,形成一种层次结构。这种结构,可用二叉树描述。#{)K5{9f2@0~L每个框架结构的最前面,有一个FrameTransformMatrix矩阵数据,描述了该框架相对于父框架的变换矩阵。也就是说,该框架中的坐标,与该矩阵相乘,可转换为父框架坐标系的坐标。8D;j/H4Z%c!\:W这种层次结构,使得X文件能描述许多复杂的物体。如地形场景。在骨骼动画文件中,框架结构可直接拿来描述人物骨骼的层次结构。框架的名字通常为对应的骨骼名。如“左上臂-左前臂-手掌-手指”就形成一个父子骨骼链。而左上臂与右上臂是并行关系。-n2D5V-]&G数据示例:D:\D9XSDK\Samples\Media\tiny.x,P7P5~+r2T1eFrame...{,F;J:uy&m5c.....,I!|:y:l)?!b+_%\,]FrameBip01_R_Calf{//子骨骼FrameTransformMatrix{(K&f&y%q'Mf1.000000,-0.000691,-0.000000,0.000000,0.000691,1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,119.231522,0.000021,-0.000011,1.000000;;#V\7T(`!o;w5@}FrameBip01_R_Foot{//--孙子骨骼5gZ.c7i,o+N!l,J:m;A3U(^-m6Z!c*o7\FrameTransformMatrix{:X4q8l2n:v/Q'}#M2\0.988831,0.124156,0.082452,0.000000,-0.122246,0.992109,-0.027835,0.000000,-0.085257,0.017445,0.996206,0.000000,119.231476,-0.000039,0.000023,1.000000;;/K3s2@']0h!{/HC}....缩进}}&\3K/[+S7i+l4Z.|[问题]查看示例tiny.x文件,发现只有根框架下有一个Mesh,包含了所有顶点信息。其它各个Frame都没有Mesh数据。怎么理解?答:一般来说,每个动画文件只有一个Mesh网格,包含物体所有顶点信息。3L;_3f(c*M0?1R其它Frame,只是借用来描述各骨骼的层次信息,没必要再定义骨骼网格。每块骨骼对应的蒙皮顶点信息,由根Mesh中的相应骨骼的SkinWeights中蒙皮顶点索引描述的。在动画过程中,各个顶点的新坐标,要借助SkinWeights中的顶点索引来进行重新计算。:f7|*J#I6]$x/Z3p2.3动画信息:&O&H*M6q4F由一系列AnimatonKey组成,数据示例如下:3R6I-v7Q8G0H4aAnimationKey{3]&_+z)|1S;[0b4;--动画类型4表示矩阵62;--动画帧数,即下面矩阵个数3Y%m1k/j/g.|2t&W0;16;1.000000,-0.000691,-0.000000,0.000000,0.000691,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,119.231514,-0.000005,0.000001,1.000000;;,80;16;0.992696,-0.120646,-0.000000,0.000000,0.120646,0.992696,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,119.231514,0.000002,-0.000002,1.000000;;,..上面红数字表示时刻tick,兰数字表示数值的个数。...其它各时刻矩阵...,R:T#v&D'FIH'H#n{Bip01_R_Calf}--对应的骨骼对象引用3|;H7u0U6j)N,l6D&b}-p$h,h#i*GO{-t'L注意:)n*m!Y+j/W4q+e/Z.a(1)每块骨骼都有一个AnimationKey{}.9K;y8z/?9u5A2F(2)在上面数据结构中,主要保存了各典型时刻的该骨骼相对于父的变换矩阵.(3)在0时刻的矩阵,与该骨骼对应的前面的Frame所对应的矩阵是相同的。如FrameBip01_R_Calf{}中的变换矩阵,与Bip01_R_Calf所对应的AnimationKey的第0时刻矩阵是一样的。这说明,在以后动画运行时,DX会提供一种功能,用AnimatonKey中的对应数据刷新初始的变换矩阵(也可能启用关键帧插值算法)。这个功能对应于示例中的m_pAnimController-SetTime(...)语句。三怎样从X文件加载骨骼动画信息?3.1负责加载的函数:可能有多种加载方式,在此以SDK中的示例为准,叙述一种标准加载方式,需要用到DX函数D3DXLoadMeshHierarchyFromX(),函数字面意思是读取Mesh层次信息。1q$I5Z6g6n'w+g3GHRESULTWINAPI%F(B)^%b1tD3DXLoadMeshHierarchyFromX(LPCSTRFilename,//.x文件名&T:@!O8R.X0z1W8~#Q9NDWORDMeshOptions,//Mesh选项,一般选D3DXMESH_MANAGED0@&H6a!V(j7Z1i0G$@LPDIRECT3DDEVICE9pD3DDevice,//指向D3D设备Device4n*o0a/~*v9x(W5e4@&oLPD3DXAL