1. 程式人生 > >骨骼動畫的原理及在Unity中的使用

骨骼動畫的原理及在Unity中的使用

製作骨骼動畫

我們看看這幾步操作後,我們得到了那些資料: 1.每個面板頂點的初始世界座標。 2.每個骨骼關節頂點的初始世界座標。 3.每個頂點被骨骼頂點的影響資訊。 4.骨骼如何移動。

骨骼動畫原理

核心: 通過骨骼帶動面板運動,也就是通過骨骼的移動動態計算mesh上的點的位置

過程:

1.將mesh上的點轉換為骨骼空間上的點。 骨骼空間就是以關節為原點確定的空間,並不是一個實體。 2.通過縮放、旋轉、平移將骨骼移動到新的位置。 3.根據骨骼的新位置計算mesh頂點新世界座標(骨骼移動,但mesh頂點與骨骼的相對位置不變,所以產生了頂點隨骨骼移動的感覺),若一個頂點被多個骨骼影響,則要進行頂點混合計算新世界座標。

舉例

初始位置(繫結姿勢): 骨骼移動後的位置: 1.計算小臂上一點S在小臂空間中的位置。 這個就要根據初始的骨骼位置和mesh上頂點的位置來計算了,也就是常說的繫結姿勢狀態。 先說一下該例中每個座標的意義: (x1,y1,z1):左肩關節SL的世界座標。 (x2,y2,z2):左肘關節在以左肩關節為原點的座標系本地座標。 (x3,y3,z3):附著於左小臂上的面板上的一點S的世界座標。 這裡為了簡單,假設所有的關節都沒有經過旋轉和縮放。 先將S轉換到通過SL確定的空間中,也就是大臂UAL空間,直接減去SL的座標即可(x3-x1,y3-y1,z3-z1)。 再將S點在大臂UAL空間中的座標轉換到小臂LA空間中,直接減去EL的座標即可(x3-x1-x2,y3-y1-y2,z3-z1-z2)。

以上只是一個簡單的說明,而實際的使用中,初始的骨骼位置可能是通過縮放、旋轉、平移後得到的,一般會通過矩陣的方式來表示這一系列變換。 關於OpenGL中的座標轉換可以參考這個連結的說明LearnOpenGL CN。 看完這個我們就應該知道如何將一個子座標空間的點轉化為世界座標了。下面再使用該例子進行舉例。 先計算模型矩陣再求模型矩陣的逆矩陣: 或者直接將求模型矩陣的運算反過來也可以: 得到World→EL矩陣後就可應直接通過矩陣運算直接將世界座標上的一點轉化為EL空間上的一點了。 注意: 1.注意矩陣的運算順序,因為矩陣運算是不滿足交換率的,如果順序錯了,結果很可能也就錯了。 2.矩陣EL→World一般叫做EL空間的模型(model)矩陣,矩陣World→EL一般叫做EL空間的繫結姿勢矩陣(bindpose) 3.有時儲存的Mesh頂點資訊不是直接的世界座標,而是一個有層次結構的mesh,但這並不影響流程,只要在運算時增加一步將這些頂點轉化為世界座標的操作即可。 2.計算EL順時針旋轉90°後S點的位置。

直接通過左肩SL,左肘關節EL的縮放、旋轉、平移資訊計算小臂LA空間的模型矩陣,使用上一步算出的小臂LA空間座標乘以該模型矩陣即算出了該點收到骨骼移動的影響後的位置。 3.頂點混合 有一些頂點不一定只受一個骨骼的影響,可能受多個骨骼的影響,此時就要通過頂點混合計算該點的新座標。 現在假設點S同時受SL,EL的影響且影響權重分別為0.4,0.6。 ①分別計算點S在SL、EL空間中的本地座標。 ②分別計算點S在SL、EL移動後的世界座標。 ③根據SL、EL對點S的影響權重混合座標,獲得新的世界座標。 這裡舉例的是受兩個骨骼影響的情況,受3個、4個時原理也是相同的,只不過運算量會更大一些。

小結

1.空間的平移、旋轉、縮放都可以用矩陣來表示,而且這些矩陣也可以結合在一起成為一個矩陣,多層空間結構的變換也一樣可以組合為一個矩陣。 2.現在看來骨骼動畫的核心其實就是幾個矩陣乘法的問題,大概就是這樣: 其中model矩陣隨著動畫的播放不停的變化,也就實現了骨骼帶動面板的功能。 假設一個頂點受多個骨骼影響,那麼就再根據權重混合一下。 3.mesh的初始位置、bindpose、影響因素都是通過製作該骨骼動畫模型時確定的,可以參考第一小節制作骨骼動畫的過程。

Unity中的骨骼動畫

我們這裡以一個Mixamo上的免費資源Samba Dancing為例。

資源下載

資源匯入

直接將資源拖入Unity中即可,可以看到在Unity中生成了一個資料夾和一個預製件。

加入動畫

1.把模型prefab拖入場景中。 2.然後將mixamo.com動畫拖到場景中的Samba Dancing中,Unity會自動生成對應的Animator Controller。

執行場景,檢視動畫效果

直接點選執行即可。

資料說明

動畫資料說明:

左邊是每一幀變化的骨骼,右邊是每個骨骼關鍵幀的平移,旋轉,縮放資訊。

模型資訊說明

Skinned Mesh Renderer屬性詳解

Cast Shadows:是否投射陰影。 Receive Shadows:是否接收陰影。 Materials:材質。 Use Light Probes:是否使用光探針。 Reflection Probes:反射探針設定。 Anchor Override:網格錨點。 Lightmap Parameters:光照烘培引數。 Quality:每個頂點最多收到的骨骼影響數量。 Update When Offscreen:當mesh在螢幕外時是否更新,依據RootBone和Bounds判斷。 Mesh:Mesh資訊。 mesh資訊包含了每個頂點的位置資訊,受骨骼影響的權重資訊、切線、法線、UV對映資訊。 RootBone:根骨骼,有兩個作用。 1.作為mesh在螢幕外時是否更新的依據。 2.進行座標計算時的Root空間。 在Unity中計算mesh上一點位置的流程大概是這樣的: $$V_{RootBone_{local}} = M_{World→RootBone}⋅M'{Bone→World}⋅M{Bindpose}⋅V_{world}$$ 先通過上述一系列計算得到點在RootBone空間中的位置,上述過程對開發者時不可見的。然後將接下來的步驟交給Material中的Shader解決。檢視Shader檔案可以看到,在頂點著色其中第一步會給輸入的點乘以一個MVP矩陣獲取該點在螢幕上的位置,其中的M就是RootBone的模型矩陣。 以Unity 5.37的Standard Shader為例,擷取其使用的頂點著色器的一部分

VertexOutputForwardBase vertForwardBase (VertexInput v)
{
    UNITY_SETUP_INSTANCE_ID(v);
    VertexOutputForwardBase o;
    UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
    UNITY_TRANSFER_INSTANCE_ID(v, o);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
    #if UNITY_REQUIRE_FRAG_WORLDPOS
        #if UNITY_PACK_WORLDPOS_WITH_TANGENT
            o.tangentToWorldAndPackedData[0].w = posWorld.x;
            o.tangentToWorldAndPackedData[1].w = posWorld.y;
            o.tangentToWorldAndPackedData[2].w = posWorld.z;
        #else
            o.posWorld = posWorld.xyz;
        #endif
    #endif

可以看到,通過posWorld = mul(unity_ObjectToWorld, v.vertex);對頂點以RootBone空間為基礎做了轉換。

Bounds:根骨骼的邊界。

如果有什麼錯誤,希望各位在部落格下留言指正,我會盡快改正。