目前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/morph)基本原理。
大體上講就是在拓撲結構相同的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()的代碼便可以直接想象出來了:
1,對於只有一個變形目標的通道(非漸近變形),只要根據通道變形進度weight在變形物體和變形目標間插值。
2,對於含有多個變形目標的通道(漸近變形),看變形進度weight落在哪兩個變形目標的fullWeight之間,然后計算weight將此區間分成的比例得到此區間上的變形進度weightOfSpan,再根據此區間變形進度在上述兩個變形目標間插值。
下面是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拓撲結構相同的,只要頂點能一一對應,頂點位置插值就可以進行,所以如果像ViewScene示例那樣只處理頂點變形而不考慮法線變形的話,不必要求模型在3dmax中就建成三角網。(不需要考慮法線變形的情況確實是存在的,例如的像《地鐵跑酷》那種無光照的Q版3d游戲,根本不需要法線,因此做變形時也不用考慮法線變形)。
法線插值是向量間插值,與頂點位置插值(點之間插值)是不同的。點之間的插值直接線性插值即可,而向量間插值嚴格來講應該使用球面插值(slerp)。不過經過試驗,發現在此處兩種方法的視覺差異並不很大,且線性插值速度要快得多,所以最后我仍然選擇了線性插值。
六,ViewScene示例中變形動畫代碼有錯誤。
ViewScene示例中播放變形動畫的代碼是有錯誤的,將動畫由3dmax中導出為fbx再用viewScene示例播放,會發現viewScene示例播放結果與3dmax中不一樣,而且動畫會出現跳變等明顯錯誤。
我現在已經記不清具體是什么原因造成的了,但我在自己的播放程序中解決了這些問題。
我在c3dEngine2(https://github.com/wantnon2/c3dEngine2)中添加了一個app_fbxLoader_shapeDeformTest demo,效果視頻: