1.引言
最近在Unity3D中實現一個基於自定義Mesh網格的骨骼動畫。存儲關鍵幀信息,然后通過插值形成中間動畫。網格GameObject之間存在父子關系。插值動畫對模型骨骼的Position、Sclae、Rotation三個部分分別混合插值。
並且注意,一般選取的是子骨骼相對父骨骼的Transform信息,即上述關鍵幀信息具體應該為Transform.localPosition、localScale、localRotation。
並且這個系統的關鍵幀是定義在三維空間中的,用戶在一系列關鍵幀點之間拖動預設點,系統自動混合生成預設點位置處的模型動作信息。
演示視頻地址:http://v.qq.com/page/c/f/j/c0149v62gfj.html
視頻中紅色的為預設點,綠色的為關鍵幀點,關鍵幀點把該關鍵幀定義在了一個空間位置中,預設點則計算自身到各關鍵幀點的距離,生成N個權重信息,進而混合成模型動畫。
2.距離權重
預設點和N個空間關鍵幀的距離分別為d1,d2,d3...dn,我的權重函數:weight_i=1/(di^4), (靠近關鍵幀時收斂較快,並且整個過程較平滑),且sum_weight=weight_1 + weight_2 +weight_3 +...+weight_n
則預設對於N個空間點的權重分別是weight_i/sum_weight(i=1,2,3...N)。
Position和Scale只需要乘以權重求和即可,麻煩的是Rotation,3D空間的旋轉一般采用的都是四元數,四元數具有不產生“萬向節鎖(Gimbal Lock)”等優良特性。下面討論一下四元數的性質和對N個四元數的插值運算。
3.N個四元數的插值計算
四元數表示的是空間的一個軸角對((x,y,z),w),也表示成( (sin(theta/2)Nx,sin(theta/2)Ny,sin(theta/2)Nz) ,cos(theta/2) )
並且在Unity3D中,自帶的Quaternion類生成的四元數為單位四元數,即使上式x^2+y^2+z^2+w^2==1,並且Nx^2+Ny^2+Nz^2==1.
那么,問題來了,對於N個四元數,我們也求得了相應的N個權重值,現在要怎么求混合值呢?
先上代碼:
1 //計算四元數的冪 2 static public Quaternion quaternion_exp(Quaternion q,float exp) 3 { 4 //若w==1,則w==cos(theta/2),theta/2==360,則sin(theta/2)==0 5 //若w>1,輸入的四元數不是單位四元數 6 if(q.w>=1.0f) 7 { 8 return Quaternion.identity; 9 } 10 //計算theta/2 11 float theta_2=Mathf.Acos(q.w); 12 //四元數冪為exp,等於旋轉角乘以exp 13 float newTheta_2=theta_2*exp; 14 float newW=Mathf.Cos(newTheta_2); 15 //為了使得新構造的四元數也符合單位四元數定義 16 //即[w,x,y,z]=[cos(theta/2),sin(theta/2)*Nx,sin(theta/2)*Ny,sin(theta/2)*Nz] 17 float mult=Mathf.Sin(newTheta_2)/Mathf.Sin(theta_2); 18 float newX=q.x*mult; 19 float newY=q.y*mult; 20 float newZ=q.z*mult; 21 Quaternion result=new Quaternion(newX,newY,newZ,newW); 22 return result; 23 }
這段代碼是四元數的冪的求法,為什么要求冪呢?
因為我們要對四元數進行插值。所謂插值,必然是權重值乘以”特定幾何意義值“,然后得到合理的中間值的過程。
這里”特定幾何意義值“,正是旋轉的度數。
例如一個四元數q(sin60*Nx,sin60*Ny,sin60*Nz,cos60),表示繞(Nx,Ny,Nz)軸旋轉30度,此時q^k則表示繞(Nx,Ny,Nz)旋轉30*k度。
具體證明不贅述,推薦參考《3D數學基礎:圖形與游戲開發》153頁。
所以到這里,我們很清楚的知道,每個關鍵幀提供的Rotation,最后都應該以 Rotation^(weight_i/sum_weight)的形式提供給預設點。
然后呢?對應加法的運算,在四元數這邊如何體現呢?
答案是:四元數叉乘,四元數叉乘幾何意義即為連接兩個旋轉。
所以,預設點的旋轉值,混合計算的式子應該是這樣的:R1^(weight_1/sum_weight)*R2^(weight_2/sum_weight)*...*Rn^(weight_n/sum_weight)。
而四元數乘法,Unity3D中提供了Quaternion的*運算符重載,直接用就是了。