骨骼動畫驅動的一般方法,以及使用FBX SDK的一些實現
我終於要更新部落格了,進公司之後每天學的做的很多,但真正有時間去總結的時候很少。之後的話儘量每週都要做一個知識的總結和積累吧,一方面自己真的是做過就忘,另一方面也能夠將自己踩到的坑,遇到的問題與大家分享一下。
入職三個月吧第一個月最主要的事情就是在骨骼動畫的驅動,一開始是研究的Assimp這個庫,也實現了相應的功能,但後來發現坑實在是太多,對之後的擴充套件也不好,最後還是開始啃超級不友好的FBX SDK。也算是把整個過程理順了。Assimp的先留個坑,之後再總結。
萬萬沒想到是我還是做起了OpenGL,比之前OSG感覺還要難一些,不過shader好有趣啊,現在終於會寫shader,之前都覺得在看天書,Unity也在繼續做,目前主要就是在Unity上實現demo,然後移植到OpenGL中。現在的工作真的很讓人進步,也很開心~加油吧~
————————————————————————————————————————————————————
骨骼動畫驅動的一般方法
(一) 所需資料
1、 One or multiple Mesh:包含在世界座標下的(fbx中其實是相對於所輸mesh對應的node座標下的),in bind pose的頂點座標
2、 A hierarchy of bones:骨骼層次,有些地方也叫frames,參考座標系,實際上每個骨骼就是一個座標系。除了根骨骼以外,所有骨骼都有一個parent bone,none or multiple children bone。
3、 An array of matrices:每個骨骼都有一個matrix,通常叫nodeTransform。是父骨骼空間到該骨骼的local space的transform ,in bind pose。相對於父節點
4、 An array of matrices: 每個骨骼都有一個matrix,通常叫bind pose matrix,offset matrix etc. 每個matrix 是從world coordinate到該骨骼的local space的transform。
5、 A collection of animation data: 通常是KeyFrames。儲存的資料是一組對每個骨骼的關鍵幀資料,以及關鍵幀時間。這個transform通常是local的。
計算bind pose matrice的方法:
bone’s bind pose matrix = inverse(bone’slocal matrix(nodeTransform)* parent’s local matrix * parent’s parent’s localmatrix*….);
(二) Principle
1、 計算一個animationmatrix for each bone,是一個local transform from the parent’s bone space to the bone’s animatedorientation.
2、 combine matrix, 將每個骨骼的animation matrix與父骨骼的animation matrix相乘,級聯。遞迴。得到combine matrix,是從該骨骼的local space 到 world space的transform。
3、 final matrix = bind-pose-matrix * combine matrix (乘法順序由API決定,OpenGL是右乘)
4、 在vertexshader中,每個頂點都會乘上影響它位置的骨骼的weighted 的final matrix。
(三) For FBX SDK
對於FBX SDK來解析模型的mesh和頂點等還是很簡單的,比較容易困擾的就是骨骼動畫驅動中會用到的這些矩陣。所以也主要介紹一下這一部分,有時間的時候會將整個流程都記錄下來。我們的需求是將模型檔案解析出來,之後用OpenGL來渲染,在渲染過程中不想使用到Fbx SDK的方法等,所以都是把相關的資料儲存成自己的類。
1、nodeTransform:node->EvaluateLocalTransform();
返回bind pose下該節點的local transformation matrix. 該transform有考慮到pre/postrotation之類的變換
函式原型及需要注意的點:
FbxAMatrix&EvaluateLocalTransform(FbxTimepTime=FBXSDK_TIME_INFINITE, FbxNode::EPivotSetpPivotSet=FbxNode::eSourcePivot, boolpApplyTarget=false, boolpForceEval=false);
remarks The local transform matrix iscalculated in this way: ParentGlobal.Inverse * Global, all transforms such as pre/post rotation are takeninto consideration.
This will return a different valuethan LclTranslation, LclRotation and LclScaling at the specified time. Toevaluate these properties separately
* without taking pre/post rotation,pivots and offsets into consideration, please use GetNodeLocalTranslation(),GetNodeLocalRotation() and GetNodeLocalScaling().
2、bindPoseTransform/offsetMatrix
FBXSDK中每個mesh通常包含一個deformer(也有可能多個,比如有skin deformer,也有BlendShape deformer。目前只針對skin deformer做處理。
每個deformer下有多個cluster,看起來好像是一個骨骼,但真正的骨骼是cluster->GetLink().是這個奇怪的link。
bindPoseTransform的作用實際上是將mesh上的頂點由mesh空間,變換到骨骼的localspace。
step1:啊,對了,vertex在FBX裡面叫control points,這個control points 是儲存在mesh’s object space,首先把頂點從mesh的座標,先變換到世界座標下
FbxAMatrix lReferenceGlobalInitPosition;
FbxAMatrix lClusterGlobalInitPosition;
FbxAMatrix lReferenceGeometry;
FbxAMatrix lClusterRelativeInitPosition;
cluster->GetTransformMatrix(lReferenceGlobalInitPosition);
lReferenceGeometry = GetGeometry(pMesh->GetNode());//這個geometry transform是針對在3ds Max中對pivot進行修改後會影響的值,並且該值不影響子節點。
lReferenceGlobalInitPosition *=lReferenceGeometry;
實際上,上面得出的lReferenceGlobalInitPosition對於同一個mesh下的骨骼來說是一樣的,因為他們其實是在一個節點下的。所以可以考慮只計算一次。(當然目前是每個節點都計算了)。
step2:把所有的vertex從世界座標變換到bonespace at binding moment
currCluster->GetTransformLinkMatrix(lClusterGlobalInitPosition);
lClusterRelativeInitPosition
= lClusterGlobalInitPosition.Inverse() * lReferenceGlobalInitPosition;
3、從動畫構造KeyFrame
fbx sdk裡面只提供了按照時間去獲得變換矩陣的方法,所以這裡就提前將這些每個關鍵幀時間儲存下來,用fbx sdk 的方法pEvaluator->GetNodeLocalTransform(pNode, fbxtime);計算出來,然後整理成KeyFrame。