先說明,這篇文章由我翻譯自Evan Pipho的<<Focus On 3D Models>>一書的內關於骨骼動畫第五章的內容,去掉了前面的說明和最後的Demo說明,包含了所有的理論內容,轉載請註明出處,謝謝!
理解骨骼動畫骨骼動畫是使用”骨頭”來運動一個模型而不是通過手動編輯和移動每個頂點或面而實現的動畫.每個頂點被附著到一根骨頭(或有時是多根骨頭).一根骨頭或一個關節只是一組頂點的一個控制點.這些概念類似於我們身體裡的關節,例如我們的膝關節或腕關節等.當骨頭運動時,每個附著在它上的頂點也跟著運動,5.2顯示的那樣.甚至骨頭自身的運動也會導致其它骨頭的運動.這使得模型運動起來比較適當合理,因為在真實的生活中,身體的一部分運動會影響到身體的其它部分.所以,程式員需要能利用骨頭來計算單個頂點的變換.雖然這會帶來較多的工作量,可是結果證明這是值得的.
<!--[if !vml]--><!--[endif]-->
圖5.2 頂點動畫需要你移動每個頂點,而骨骼動畫使你只需移動模型中的一根骨頭,頂點將能隨之而變.
骨骼動畫的優點骨格動畫比傳統的頂點變換型動畫有很多優點,正如你在前面章節裡看到的那樣.
首先,也是對於玩家最直觀的一點便是要增加真實感.基於骨骼運動的角色更趨於真實性,並常出現在比傳統模型要求和周圍事物有更強互動性的地方.要使模型更趨於運動真實性,則對基於骨骼的模型來說確是件簡單的事.在傳統的主要畫面格動畫中,遊戲會在兩個位置中間進行線性插值.然而,在這種情況下,關節實際上並沒有旋轉,這對於生命體以旋轉方式運動會有問題.
這對使用者而言,並不那麼明顯,但對程式員來說,那樣的動畫是否佔用了較少的儲存空間是非常重要的.用於代替為每幀動畫都儲存一組新的頂點,所有我們需要儲存的只是骨頭的旋轉和平移資訊.這可以節省巨大的儲存空間,你僅僅只要加入骨頭和頂點—骨頭附著資訊.
這因骨骼動畫需要而多加的一點儲存空間可以用於儲存更細緻的模型,加入額外的支運動幀,或甚至可經適當改進後,用於遊戲的其它地方.你能夠添加更多細節至遊戲的世界中,改進A.I.來提供一個更刺激的遊戲,或者加入一些很酷的東東或複活節彩蛋,而這些都是原來你很想加入卻因涉及空間問題而不能加入的.
而另一個優點表現在那些建立遊戲3D內容的美工上.一個好的骨骼動畫系統將縮減美工做模型動畫的時間.幾乎每一個好的動畫程式都已使用骨骼動畫來確保模型運動的過程從美工到程式員,再到遊戲,最後到玩家的平滑過渡.這加速了遊戲內容的建立過程,也確保了當模型被匯出成遊戲所使用的格式時沒有動畫或特徵的失真.
一個更長遠的優點是對程式員的另一個好處(這越來越好了,不是嗎?).因為骨骼的自由性,這使得你可以隨心所欲地即時定位它們,也可以實現在運行時建立動畫.就相當於提供了一個更多樣化的動畫庫.你甚至能夠讓遊戲控制當身體碰到一個物體時的動作,或從一個斜坡上滑下來.這種技術是在玩的時候即時產生的,Unreal Tournament 2003’s Physics系統(http://www.epicgames.com)裡就有這樣一個值得注意的例子.角色和模型在環境裡面真實互動,包括從斜坡上滑下和靠在上面徐徐移動.
骨骼動畫有一個缺點就是相對於傳統的主要畫面格動畫,它不容易被理解和實現.本章將協助你緩解任何你對骨骼動畫的擔心.
骨骼動畫的原理首先來看看你的手臂.將你的手臂在你胸前展開並觀察它.你的手臂有許多骨頭,兩根主要的大骨頭(two main ones),還有手掌(譯註:這裡指腕關節到指尖的所有部分,下面說的手掌也一樣)和手指上的一些骨頭.
移動你的手指,是不是它們移動?只移動你的手指,而你手臂上的其它部分並不會因此而移動.現在彎曲你的肘關節,不僅你的手臂移動了,而且你的手指和手掌也同樣跟著移動了.如果不是這樣的,那你的手臂和你的手掌及手指就變得斷開了,且每部分都孤立得懸掛在空中,這可不太好.
如何將這個手臂和骨骼動畫關聯在一起運動呢?你的手臂代表了3D模型的一部分,你的手指,手掌,以及手臂的上下各部分都是這個模型的一部分.不同的關節和骨頭貫穿了你的手臂,如肩關節,肘關節,腕關節,還有指關節.
這就是說,當你移動手臂”上遊”的一根骨頭時,任何在這根骨頭的下遊部分也都會跟著移動.這就是骨骼動畫最基本的概念.
這樣就有一個美妙之處是:它可以允許你移動模型上的任何一根骨頭,並滲透到下面的移動,應用到以這個移動為原點的下面的任何事物. 例如,這允許你只需移動角色的肩膀,不需擔心肘和手的移動位置,因為它們會自動移到正確位置.你也能夠通過複位以確保它們會被自動更新.圖5.3顯示了一個關節和附著在這些關節上的頂點的例子.
<!--[if !vml]--><!--[endif]-->
圖5.3 當執行骨骼動畫時,你不必擔心關節,或骨頭之間的結合點.每個頂點實際上是附著(關聯)到關節點的,而不是骨頭.
根關節根關節是一個模型中的終端關節.任何其它關節都以自己的路徑最終關聯到根關節.任何對根關節的操作,如不論是平移或是旋轉,都會影響到模型中的每個頂點.你可以認為根關節是控制所有其它關節的關節.簡單修改根關節,你能使角色直立行走,或旋轉他以讓他倒過來在天花板上行走—所做的只是一個簡單的調用.
在每個模型裡只有一個根關節,它沒有父關節.根關節通常是許多骨頭串連的地方,而不是需要一個小動畫的地方.例如包括中部和下後部,但沒有明確要求根關節一定要在模型中的某個準確位置.只要你願意,每個模型都可以不同.圖5.4顯示了當根節點的位置和朝向被修改後會發生的變化.
<!--[if !vml]--><!--[endif]-->
圖5.4 旋轉或平移根關節將影響模型中其他所有的關節和項點.
由於希賽部落格日誌容量限制,只好拆成兩篇發表先說明,這篇文章由我翻譯自Evan Pipho的<<Focus On 3D Models>>一書的內關於骨骼動畫第五章的內容,去掉了前面的說明和最後的Demo說明,包含了所有的理論內容,轉載請註明出處,謝謝!
父關節和子關節一個關節可以有父關節和子關節.其父關節會影響到它做任何事情.父關節的旋轉和平移會影響計算當前關節的新位置.再拿手臂的例子來說,肘關節是手掌的父關節.移動肘則影響手掌.在簡單的骨骼動畫裡,每個關節只有一個父關節,如果有的話.
但是,一個關節可以有許多子關節.子關節是相對於父關節來說的.任何你對父關節做的事都會滲透到子關節.換個角度來說就是,當前關節是所有在它下面的關節的父關節.
<!--[if !vml]--><!--[endif]-->
圖5.5 父-子關節的關係
骨骼動畫的主要畫面格常規主要畫面格動畫儲存了多份頂點拷貝,骨骼動畫系統也有主要畫面格.主要畫面格的再現是一個模型位置的瞬時狀態.
然而,不同於每個主要畫面格都包含其自身所有頂點的拷貝的方式,骨骼動畫的主要畫面格或叫骨骼幀(boneframe)包含了旋轉和平移的變換資訊,一般平移是一個X,Y,Z值的形式,和三個分別包含了按X,Y,Z軸旋轉的值.正如常規頂點主要畫面格一樣,這些骨骼幀必須被插值來提供平滑的動畫效果.
位置或平移的值可以在兩個之間進行線性插值,就像你在傳統主要畫面格動畫裡對頂點的操作一樣.但旋轉則有問題,簡單的在兩個值之間進行平移那樣的插值會產生奇怪的效果.旋轉將不是平滑的,它可能會加速和減速,這依賴於其自身的位置.如果兩個旋轉資訊之間的差異比較大,模型可能像膠在一起的方塊那樣出現”滲出(ooze)”.這是因為使用線性插值,所有的東西都是沿著直線插值的,如果這用這種方式執行旋轉就會產生奇怪的效果,因為旋轉是沿著弧插值而不是直線.對弧形路徑按兩端點的直線方式而不是沿弧的路徑方式就會產行”滲出”效果.
解決這個問題的最好方法就是採用四元數.這你在第二章就學會過了.”介紹四元數”,四元數最大的優點之一是他們可以容易地進行插值.不僅容易插值,而且能很容易地進行球麵線性插值.
球麵線性插值在球面上的兩點之前進行插值.然而,相對於在兩點之間以直線方式逼近,球麵線性插值是沿著球表面的曲面的.你可以設想撿起一隻球,比如一個籃球,並在上面標上兩點,然後,用你的手指找出這兩點間最短的路徑.因為你的手指不能進進球的內部,於是這條在兩點間的路徑將是一條弧.這就是SLERP的處理方式.使用SLERP函數,旋轉就可以沿著弧進行插值,建立出一個比較平滑的,順眼的效果.
計算位置利用你在上面己經閱讀到的資訊,你可能想嘗試實現骨骼動畫了.然而,你還沒有學習關於父關節是如何影響子關節的具體內容.簡單地使用主要畫面格將使每個關節獨立地移動,很可能產生一個奇怪的,扭曲的網格.
這部分內容將告訴你關於如何更改它以使關節之間能正常運動.首先你要做的就是建立一個變換矩陣,該矩陣用於每一個使用了不同旋轉和平移主要畫面格資料的(主要畫面格)點.這個變換矩陣可以通過先產生三個旋轉矩陣(譯註:x,y,z三個方向的旋轉矩陣)和平移矩陣,這在第一章已經學過了, 再將這三個矩陣相乘就會產生了最終的變換矩陣.你也可以使用matrix類的SetRotation和SetTranslation函數來避免你自己做矩陣乘法.這個產生的矩陣叫做相對矩陣(relative matrix).
下一步你需要計算出一個絕對矩陣(absolute matrix).絕對矩陣是關節的相對矩陣乘上它的父關節的絕對矩陣得到的.絕對矩陣告訴你關節的絕對變換(譯註:就是將關節的本地座標變換為全局座標).這包括了自身的相對變換,除此之外,階層中所有在它之前的關節的變換都已計算完成.這允許其他關節的移動作為移動關節鏈上遊關節的結果.細想一下,例如,當你移動肩膀時肘又是怎麼移動的,這引出了一個問題:你如何計算最初的絕對矩陣?記住,根關節是沒有父關節的,因此,它的絕對矩陣就是它的相對矩陣.
假如你以正確的順序遍曆了各關節,同時按此順序計算出各個絕對矩陣,每個關節(的絕對矩陣)將會包含它父關節的變換,以及它父關節的父關節的變換等等.圖5.6顯示了當你變換一個關節前顧及所有先前(關節)的變換時發生的情況.
<!--[if !vml]--><!--[endif]-->
圖5.6 遍曆各關節,並顧及其所有前面的變換.注意哪怕只有一個關節要移動,在它下面的關節也得跟著移動,這很像移動你的臀部,則你的膝和踝也得跟著移動一樣.
你是否只儲存了子關節的索引,而沒有儲存關節的父關節索引?你立即要訪問的索引集依賴於模型的格式.一些格式像MS3D同時給你每個關節的父關節索引,其它格式則可能給你子關節索引.使用子關節索引需要一個明顯不同於使用父索引的方法,但一點都不會比後者難.你再次從根關節開始,在計算了根關節的變換矩陣之後,通過一個類似於glPushMatrix的命令將一個新的矩陣壓入棧內.就建立了一個新的世界變換矩陣(world matrix)的拷貝,這個世界變換矩陣是在被顯示前執行了所有的幾何變換的矩陣.現在用你的新矩陣乘以根關節的本地矩陣.結果就是世界變換矩陣,它在下一個骨頭被畫出之前將所有事物放置到正確位置,父關節的變換也被考慮在內了.舉個例子,角色的臀部的旋轉可能是某些動作的總合.因為膝關節和踝關節是臀關節的子關節,它們也要被旋轉.
繪製函數像這樣遞迴,它對自己的子關節繼續調用繪製函數,每個子關節又對自己的子關節調用渲染函數,以此類推直到到達終端關節(沒有子關節的關節),複位棧用一個glPopMatrix這樣的命令.例如,當要繪製一個角色的一條腿時,新的矩陣被壓入棧內用於計算膝關節,踝關節和腳關節.但是,如果要開始計算手臂時,你應該出格到處理腿之前的棧狀態,否則,每當你移動腿時,手臂也將跟著移動了.
圖5.7顯示了一個遞迴渲染函數.
<!--[if !vml]--><!--[endif]-->
圖5.7 渲染用子索引代替父索引儲存的關節
將網格附著到骨骼當你的關節己能平滑運動的時候,也是該將網格附著到骨骼的時候.網格(mesh)組成了模型的形狀,它是由一組使模型具有立體感和細節的頂點和三角形組成,沒有網格,基於骨骼運動的模型只是一個簡單的骨架.每個網格的頂點儲存了一個指向關節數組的索引,用於指示它被附著到某根骨頭.現在,儲存關節的方式決定了這些頂點的變換和渲染的方法.
如果每個關節儲存時帶有其父關節的索引,同時你已經遍曆和計算了最終的絕對矩陣,那麼附著網格是很簡單的.確保變換後的頂點儲存到一個專門的地方,不要覆蓋原始的頂點.這是因為在絕大多數格式裡,骨骼幀不會被累積,每幀儲存了從起點起的特定關節的旋轉和平移的資訊.如果你不回退到原始頂點,那麼當每次計算新頂點位置時,模型將會飄乎不定得運動.圖5.8中可以看出每個頂點單獨附著到一個關節是什麼意思.
<!--[if !vml]--><!--[endif]-->
圖5.8頂點附著到關節
現在你很可能在對自己說,”好的,我能運動一個模型的頂點,那三角形,法線,以及紋理座標怎麼處理呢”? 這也正是骨骼模型真正開始閃光的地方.每個模型包含了一個紋理座標的集合和一個三角形資訊的集合.僅因為頂點的位置變更並不意味著三角形頂點索引和紋理座標也會變化.這意思是說,當你第一次設定好它們之後就根本不需要再擔心它們.
法線則是另一回事了,因為多邊形的朝向和頂點的位置更改了,因此法線也相應更改了.如果你正在使用面法線,那麼你在每一幀將他們傳給渲染器之前都需要重新計算.然而,如果你一開始就計算了頂點法線,那麼你很幸運,頂點法線不必在變換後都完全重新計算,它們能利用和頂點相同的變換矩陣來進行變換.唯一不同的是,你不需要考慮平移.使用matrix類的Transform3()函數將旋轉你的頂點法向量,同時仍能保留其原始大小.
如果關節儲存了子關節的索引,並且你使用glPushMatrix將當前的變換矩陣壓入棧內,那麼渲染模型就會變得十分容易.對這種情況,沒有必要在顯示之前變換每個頂點.不管渲染任何東西都沒有更改的必要.任何你要渲染的現在將被適當變換,甚至面法線.另一個需要考慮的問題是,頂點如何附著到多於一根的骨頭.此時,每根骨頭將被賦予一個權重,這個權重決定它影響關節點的比重.最終的變換是所有這些被附著的骨頭的變換的加權平均.