骨骼蒙皮动画SkinnedMesh的原理解析二Word下载.docx
《骨骼蒙皮动画SkinnedMesh的原理解析二Word下载.docx》由会员分享,可在线阅读,更多相关《骨骼蒙皮动画SkinnedMesh的原理解析二Word下载.docx(7页珍藏版)》请在冰豆网上搜索。
BoneOffsetMatrix>
--->
Bonespace
---<
BoneCombinedTransformMatrix>
World
从这个过程中可看出,需要首先将模型顶点从模型空间变换到某块骨骼自身的骨骼空间,然后才能利用骨骼的世界变换计算顶点的世界坐标。
BoneOffsetMatrix的作用正是将模型从顶点空间变换到骨骼空间。
那么BoneOffsetMatrix如何得到呢?
下面具体分析:
Meshspace是建模时使用的空间,mesh中顶点的位置相对于这个空间的原点定义。
比如在3dmax中建模时(视xy平面为地面,+z朝上),可将模型两脚之间的中点作为Mesh空间的原点,并将其放置在世界原点,这样左脚上某一顶点坐标是(10,10,2),右脚上对称的一点坐标是(-10,10,2),头顶上某一顶点的坐标是(0,0,170)。
由于此时Mesh空间和世界空间重合,上述坐标既在Mesh空间也在世界空间,换句话说,此时实际是以世界空间作为Mesh空间了。
在骨骼动画中,在世界中放置的是骨骼而不是Mesh,所以这个区别并不重要。
在3dmax中添加骨骼的时候,也是将骨骼放入世界空间中,并调整骨骼的相对位置使得和mesh相吻合(即设置骨骼的TransformMatrix),得到骨架的初始姿势以及相应的TransformMatrix(按惯例模型做成两臂侧平举直立,骨骼也要适合这个姿态)。
由于骨骼的TransformMatrix(作用是将顶点从骨骼空间变换到上层空间)是基于其父骨骼空间的,只有根骨骼的Transform是基于世界空间的,所以要通过自下而上一层层Transform变换(如果使用行向量右乘矩阵,这个Transform的累积过程就是C=Mbone*Mfather*Mgrandpar*...*Mroot),得到该骨骼在世界空间上的变换矩阵-CombinedTransformMatrix,即通过这个矩阵可将顶点从骨骼空间变换到世界空间。
那么这个矩阵的逆矩阵就可以将世界空间中的顶点变换到某块骨骼的骨骼空间。
由于Mesh实际上就是定义在世界空间了,所以这个逆矩阵就是OffsetMatrix。
即OffsetMatrix就是骨骼在初始位置(没有经过任何动画改变)时将bone变换到世界空间的矩阵(CombinedTransformMatrix)的逆矩阵,有一些资料称之为InverseMatrix。
在几何流水线中,是通过变换矩阵将顶点变换到上层空间,最终得到世界坐标,逆矩阵则做相反的事,所以Inverse这种提法也符合惯例。
那么Offset这种提法从字面上怎么理解呢?
Offset即骨骼相对于世界原点的偏移,世界原点加上这个偏移就变成骨骼空间的原点,同样定义在世界空间中的点经过这个偏移矩阵的作用也被变换到骨骼空间了。
从另一角度理解,在动画中模型中顶点的位置是根据骨骼位置动态计算的,也就是说顶点跟着骨骼动,但首先必须确定顶点和骨骼之间的相对位置(即顶点在该骨骼坐标系中的位置),一个骨骼可能对应很多顶点,如果要保存这个相对位置每个顶点对于每块受控制的骨骼都要保存,这样就要保存太多的矩阵了。
。
所以只保存mesh空间到骨骼空间的变换(即OffsetMatrix),然后通过这个变换计算每个顶点在该骨骼空间中的坐标,所以OffsetMatrix也反应了mesh和每块骨骼的相对位置,只是这个位置是间接的通过和世界坐标空间的关系表达的,在初始位置将骨骼按照模型的形状摆好是关键之处。
以上的分析是通过将meshspace和worldspace重合得到OffsetMatrix的计算方法。
那么如果他们不重合呢?
那就要先计算顶点从meshspace变换到worldspace的变换矩阵,并乘上(还是右乘为例)CombinedMatrix的InverseMatrix从而得到OffsetMatrix。
但是这不是找麻烦吗?
因为Mesh的原点在哪儿并不重要,为啥不让他们重合呢?
还有一个问题是,既然OffsetMatrix可以计算出来,为啥还要在骨骼动画文件中同时提供TransformMatrix和OffsetMatrix呢?
实际上文件中确实可以不提供OffsetMatrix,而只在载入时计算。
但TransformMatrix不可缺少,动画关键帧数据一般只存储骨骼的旋转和根骨骼的位置,骨骼间的相对位置还是要靠TransformMatrix提供。
在微软的X文件结构中提供了OffsetMatrix,原因是什么呢?
我不知道。
我猜想一个可能的原因是为了兼容性和灵活性,比如mesh并没有定义在世界坐标系,而是作为一个object放置在3dmax中,在导出骨骼动画时不能简单的认为mesh的顶点坐标是相对于世界原点的,还要把这个object的位置考虑进去,于是导出插件要计算出OffsetMatrix并保存在x文件中以避免兼容性问题。
关于OffsetMatrix和TransformMatrix含有平移,旋转和缩放的讨论:
首先,OffsetMatrix取决于骨骼的初始位置(即TransformMatrix),由于骨骼动画中我们使用的是动画中的位置,初始位置是什么样并不重要,所以可以在初始位置中只包含平移,而旋转和缩放在动画中设置(一般也仅仅使用旋转,这也是为啥动画通常中可以用一个四元数表示骨骼的关键帧)。
在这种情况下,OffsetMatrix只包含平移即可。
因此一些引擎的Bone中不存放Transform矩阵,而只存放骨骼在父骨骼空间中的坐标,然后旋转只在动画帧中设置,最基本的骨骼动画即可实现。
但也可在Transform和OffsetMatrix中包括旋转和缩放,这样可以提高创建动画时的容错性。
在本文DEMO中,我们也没有使用矩阵保存BoneOffset,而只用了一个坐标保存偏移位置。
classBoneOffset
{
public:
floatm_offx,m_offy,m_offz;
};
在Boneclass中,有一个方法用来计算BoneOffset
classBone
BoneOffsetm_boneOffset;
//calledafterComputeWorldPos()whenboneloadedbutnotanimated
voidComputeBoneOffset()
m_boneOffset.m_offx=-m_wx;
m_boneOffset.m_offy=-m_wy;
m_boneOffset.m_offz=-m_wz;
if(m_pSibling!
=NULL)
m_pSibling->
ComputeBoneOffset();
if(m_pFirstChild!
m_pFirstChild->
}
在ComputeBoneOffset()中,使用计算好的骨骼的世界坐标来计算boneoffset,这儿的计算只是取一个负数,在实际引擎中,如果boneoffset是一个矩阵,这儿就应该是求逆矩阵,本文不做讨论了。
注意由于我们计算Boneoffset时是使用计算好的世界坐标,所以在这之前必须在初始位置时对根骨骼调用ComputeWorldPos()以计算出各个骨骼在初始位置时的世界坐标。
2-3)最终:
顶点混合(vertexblending)
现在我们有了Skininfo,有了Boneoffset,可谓万事具备,只欠东风了。
现在就可以做顶点混合了,这是骨骼动画的精髓所在,正是这个技术消除了关节处的裂缝。
顶点混合后得到了顶点新的世界坐标,对所有的顶点执行vertexblending后,从Mesh的角度看,Meshdeform(变形)了,变成动画需要的形状了。
首先,让我们看看使用单块骨骼对顶点进行作用的过程,以下是DEMO中的相关代码:
classVertex
voidComputeWorldPosByBone(Bone*pBone,float&
outX,float&
outY,float&
outZ)
//step1:
transformvertexfrommeshspacetobonespace
outX=m_x+pBone->
m_boneOffset.m_offx;
outY=m_y+pBone->
m_boneOffset.m_offy;
outZ=m_z+pBone->
m_boneOffset.m_offz;
//step2:
transformvertexfrombonespacetoworldsapce
outX+=pBone->
m_wx;
outY+=pBone->
m_wy;
outZ+=pBone->
m_wz;
这个函数使用一块骨骼对顶点进行变换,将顶点从Mesh坐标系变换到世界坐标系,这儿使用了骨骼的BoneOffsetMatrix和CombinedTransformMatrix(嗯,我知道这儿没用矩阵,但意思是一样的对吗)
对于多块骨骼,对每块骨骼执行这个过程并将结果根据权重混合(即vertexblending)就得到顶点最终的世界坐标。
进行vertexblending的代码如下:
voidBlendVertex()
{//dothevertexblending,getthevertex'
sposinworldspace
m_wX=0;
m_wY=0;
m_wZ=0;
for(inti=0;
i<
m_boneNum;
++i)
floattx,ty,tz;
ComputeWorldPosByBone(m_bones[i],tx,ty,tz);
tx*=m_boneWeights[i];
ty*=m_boneWeights[i];
tz*=m_boneWeights[i];
m_wX+=tx;
m_wY+=ty;
m_wZ+=tz;
这些函数我都放在Vertex类中了,因为只是一个简单DEMO所以没有特别考虑引擎结构问题,在BlendVertex()中,遍历影响该顶点的所有骨骼,用每块骨骼计算出顶点的世界坐标,然后使用SkinWeight对这些坐标进行加权平均。
tx,ty,tz是某块骨骼作用后顶点的世界坐标乘以权重后的值,这些值相加后就是最终的世界坐标了。
现在让我们用一个公式回顾一下Vertexblending的整个过程(使用矩阵变换)
Vworld=Vmesh*BoneOffsetMatrix1