目前fbx 2015.1中支持三种变形器:skinDeformer,blendShapeDeformer,vertexCacheDeformer。定义在fbxdeformer.h中:
enum EDeformerType {
eUnknown, //!< Unknown deformer type
eSkin, //!< Type FbxSkin
eBlendShape, //!< Type FbxBlendShape
eVertexCache //!< Type FbxVertexCacheDeformer
};
前两种变形器分别对应:骨骼蒙皮动画 和 变形动画,(第三种还没研究)。
相应的播放代码都可以在ViewScene示例中找到。
其中 骨骼蒙皮动画 游戏里用得最多,所以研究得较早。这几天又看了一下 变形动画(BlendShape/Morph animation),散乱记录一下若干注意事项。
一,blendShape基本原理。
大体上讲就是在拓扑结构相同的mesh之间插值。细节在下文中会提到。
典型应用是做表情动画。
二,在3dmax中制作带有 变形动画 的模型。
我用的是3dmax 2012(中文版)。
1,先建一个模型X,然后复制出一份X1。
2,对X1在保持拓扑结构不变的条件下进行变形。
3,为X添加“变形器”修改器,将X1添加到“变形器”修改器的一个通道中,则X1成为X的一个变形目标。
4,调整通道值,便可看到X受X1影响发生变形。
5,打开自动关键点,记录变形动画。
可以重复上面方法为X添加多个变形目标。
“变形器”修改器的一个通道可以添加多个变形目标,通道名称将显示为此通道中所添加的第一个变形目标的名称,此通道所添加的所有变形目标可以在”渐进变形“栏的”目标列表“中看到。
当一个通道中添加多个变形目标时,称为progressive morph,X将按目标列表中目标的顺序依次变形。
另外注意,并非对X1的任何保持拓扑的修改都能对X产生影响,只有局部空间的修改(例如使用局部空间修改器进行的修改、直接拖动顶点位置进行的修改等)才起作用,而对X1整体进行的修改(例如对X1整体进行旋转、平移、缩放等)则不会起作用。
三,fbx变形动画原理。
fbx sdk的ViewScene示例中的ComputeShapeDeformation()函数实现变形动画的数据提取和播放,通过阅读这部分代码,可以了解fbx中变形动画数据的逻辑结构:
一个mesh包含多个blendShape,
一个blendShape包含多个blendShapeChannel,
一个blendShapeChannel包含多个targetShape和一个weight动画曲线weightCurve。
targetShape中包含controlPoints和一个fullWeight值。
这些结构与3dmax中的对应关系如下:
mesh对应的就是3dmax中的模型,
blendShape对应“变形器”修改器,一个模型可以添加多个“变形器”修改器,
blendShapeChannel对应“变形器”修改器的通道,blendShapeChannel->GetName()得到通道名称.
targetShape对应变形目标,一个通道可以添加多个变形目标。targetShape->GetName()得到变形目标名称。
通道的weightCurve给出各时间点此通道变形进度weight。
targetShape的fullWeight值表示的是本通道变形进度weight值达到多少时恰好完全变形为本targetShape。对于只有一个targetShape的通道,targetShape的fullWeight必定是100;而对于含有多个targetShape的通道,各targetShape的fullWeight值按顺序依次增大,并且最后一个targetShape的fullWeight必定是100。
可见,非常符合直观,至此变形插值算法几乎不用再看ComputeShapeDeformation()的代码便可以直接想象出来了。
下面是ComputeShapeDeformation()中的注释,用具体例子说明在progressive morph和progressive morph情况下的插值算法,与我们的直观想象完全一致:
If there is only one targetShape on this channel, the influence is easy to calculate:
influence = (targetShape - baseGeometry) * weight * 0.01
dstGeometry = baseGeometry + influence
But if there are more than one targetShapes on this channel, this is an in-between
blendshape, also called progressive morph. The calculation of influence is different.
For example, given two in-between targets, the full weight percentage of first target
is 50, and the full weight percentage of the second target is 100.
When the weight percentage reach 50, the base geometry is already be fully morphed
to the first target shape. When the weight go over 50, it begin to morph from the
first target shape to the second target shape.
To calculate influence when the weight percentage is 25:
1. 25 falls in the scope of 0 and 50, the morphing is from base geometry to the first target.
2. And since 25 is already half way between 0 and 50, so the real weight percentage change to
the first target is 50.
influence = (firstTargetShape - baseGeometry) * (25-0)/(50-0) * 100
dstGeometry = baseGeometry + influence
To calculate influence when the weight percentage is 75:
1. 75 falls in the scope of 50 and 100, the morphing is from the first target to the second.
2. And since 75 is already half way between 50 and 100, so the real weight percentage change
to the second target is 50.
influence = (secondTargetShape - firstTargetShape) * (75-50)/(100-50) * 100
dstGeometry = firstTargetShape + influence
四,多通道同时影响时的叠加方式。
假设变形物体X受channel1和channel2两个通道影响,channel1中只有一个变形目标shape1,通道变形进度为w1;channel2中只有一个变形目标shape2,通道变形进度为w2。设v是X上一点,原始坐标为p;v1是shape1上等位点,坐标为p1;v2是shape2上等位点,坐标为p2。
则v在两个通道影响下变形后的坐标p_deformed应计算如下:
p_deformedByChannel1=p+(p1-p)*w1
p_deformedByChannel1andChannel2=p_deformedByChannel1+(p2-p_deformedByChannel1)*w2
p_deformed=p_deformedByChannel1andChannel2
五,法线问题。
对变形物体进行变形,如果只是顶点位置发生变化,而法线不变的话,那么在有光照的情况下显示效果是不对的,所以法线也需要在变形物体和变形目标之间进行插值。
前面分析fbx变形动画数据逻辑结构时提到“targetShape中包含controlPoints和一个fullWeight值”,是因为在viewScene示例中,只使用targetShape的controlPoints对变形物体的顶点位置进行了变形,而根本没有处理法线的变形。我没有找到由targetShape获得有效normals的方法。而且当我把动画导出为ascii的fbx,查看targetShape的normals数据时发现全是0。
我目前的解决办法是通过targetShape获得targetShapeName,然后再按名称在场景中搜索到与targetShape相对应的模型(mesh),然后从mesh中取得法线数据。这样就要求变形目标模型不能删,且要随变形物体一同导出到fbx文件中。另外一个需要注意的问题是,由于我的引擎中只支持三角网,于是要求或者在3dmax建模时物体就建成三角网,或者在导出为fbx文件时勾选“三角化”选项,或者在引擎载入fbx文件之后调用fbx sdk提供的api转换为三角mesh。但是后两者都不能保证变形物体mesh和变形目标mesh在三角化后仍具有完全相同的拓扑结构。而如果变形物体mesh和变形目标mesh的拓扑结构不同,法线插值就无法进行。
注意:顶点位置插值是不要求变形物体mesh和变形目标mesh拓扑结构相同的,只要顶点能一一对应,顶点位置插值就可以进行,所以如果像ViewScene示例那样只处理顶点变形而不考虑法线变形的话,不必要求模型在3dmax中就建成三角网。(不需要考虑法线变形的情况确实是存在的,例如的像《地铁跑酷》那种无光照的Q版3d游戏,根本不需要法线,因此做变形时也不用考虑法线变形)。
法线插值是向量间插值,与顶点位置插值(点之间插值)是不同的。点之间的插值直接线性插值即可,而向量间插值严格来讲应该使用球面插值(slerp)。不过经过试验,发现在此处两种方法的视觉差异并不很大,且线性插值速度要快得多,所以最后我仍然选择了线性插值。
六,ViewScene示例中变形动画代码有错误。
ViewScene示例中播放变形动画的代码是有错误的,将动画由3dmax中导出为fbx再用viewScene示例播放,会发现viewScene示例播放结果与3dmax中不一样,而且动画会出现跳变等明显错误。
我现在已经记不清具体是什么原因造成的了,但我在自己的播放程序中解决了这些问题。
效果视频: