《英雄聯盟》角色動畫為何如此精美?看懂的最少有碩士程度吧…

 

不論是哪個平台的遊戲,開發商最為頭疼的問題之一就是封包大小,因為它不僅影響玩家硬體的存儲空間,還直接決定玩家從發現到體驗遊戲的時間差。而遊戲動畫則是佔用資源最大的部分,所以如何壓縮動畫是所有開發者都需要面對的問題。

 

 

最近,《英雄聯盟》開發商Riot Games發佈了相關的技術貼,設計師Jaewon Jung討論了如何在不降低動畫品質的情況下進行動畫壓縮的話題,並且在文章中講述了Riot公司所使用的一些技巧,希望給遇到這類問題的開發者提供一些幫助。

 

 

●何解《英雄聯盟》如此精美容量又細?

 

《英雄聯盟》的英雄數量已經超過了125個(目前正式推出126個),每個都有一套獨特的動畫設定,要問我最喜歡的是哪個?毫無疑問是亡靈勇士-塞恩的跳舞動畫(下圖),而這只是他38個動畫中的一個。這些動作的加入才讓英雄們變得栩栩如生,從角色的移動到強大技能的釋放以及悲慘的死亡動畫,都可以讓英雄變得更有個性。隨著我們不斷的增加和重做英雄,動畫資料的總量已經形成了很大的資源負擔,比如運行記憶體、補丁大小以及存儲空間等等。

 

 

Sion Dance

 

 

除了動畫資料之外,最近發佈的《召喚師峽谷》視覺更新則增加了記憶體需求,這次的更新使用了Unique-texel的做法,比此前的Tiling方式更能帶來優秀的視覺效果,然而它也不可避免的增加了地圖對於記憶體的佔用。

 

 

我們認為支援多種不同配置的硬體是非常重要的,這樣所有人才能同時享受到遊戲的樂趣。隨著新的動畫和地圖更新的記憶體需求不斷增加,我們開始尋找降低記憶體使用的方法。我們發現的其中一個方法就是壓縮遊戲內骨骼動畫資料來減少記憶體佔用,同時維持最低的品質損失,還要保證不對性能產生任何影響。

 

 

我們可能用了很多方式進行動畫資料壓縮,但在這次討論中,我會介紹我們主要使用的2個:量化(Quantization)和曲線擬合(Curve Fitting)。壓縮的做法總會帶來品質降低和釋放記憶體空間兩者的矛盾,所以,我會講述我們發現的可以讓人接受的方案,還會解釋我們是如何管理資料並且做到最大化性能的。我會使用一些像四元數(quaternions)以及樣條曲線(Spline Curves)這樣的概念,所以,如果你們對這些不熟悉的話,可以參考結尾中非常有用的參考資料。

 

 

我需要說明的是,這裡提到的所有東西都不是什麼新技術,而是從事遊戲開發的夥伴們分享的一些非常實用的知識,我還想補充的是,遊戲引擎開發商BitSquid(已被Autodesk收購)是非常有幫助而且值得一看的。

 

 

●量化(Quantization

 

量化指的是把一系列連續的可能性限制在相對小而分離的設定的處理過程。骨骼動畫(Skeletal animations)有位置、旋轉和量化資料,我們很容易量化3D向量(用於表示位置和量),只要通過獲得他們的最大/最小值範圍並且在這個範圍內統一分割即可。但骨骼動畫資料的複雜性通常來自於旋轉。

 

 

我們這裡使用四元法指代3D空間裡的旋轉,我們量化旋轉資料的方式使用了四元數的特殊數學性質,我們使用了單位四元數(unit quanternions),所有元件的範圍都是[-1,1],並且找出最大絕對值的元素定義x,y,z,或者w。然後放棄(絕對值最大的)並保留其餘三個,因為我們可以很容易計算出被省略的元件,只要這個單位四元數滿足x² + y² + z² + w² = 1方程式即可。通過省略最大的元件,我們可以把其餘三個元件的範圍限制在[-1/sqrt(2), 1/sqrt(2)]之間,要知道,在單位四元數當中,這個範圍之外的一個元件必須具備最大的絕對值,所以這也是我們將會忽略掉的元件。我們通過量化到比[-1,1]更小的範圍來最大化精確度,如果不排除最大元件的話,我們原本可能會(誤)用這個範圍。這樣的做法還讓我們避免了對一個較小價值的元件進行重做,從而避免了更多的錯誤。

 

 

7

 

 

通過這種方式,我們為每三個保留下來的元件都分配15 bits,被省略的元件分配2 bits,因此每個四元數都總共佔據48 bits(其中1 bit是不使用的),細節如上圖。作為對比,未經處理的四元數需要為每個元件使用32 bits的浮點數(floating-point number),所以最終使用128 bits,通過我們的處理,原本的128 bits降低到了48 bits,也就是說,我們的壓縮率達到了0.375。

 

 

這種48-bit的四元數量化可以保證數值精度(numerical precision)達到0.000043。所以你可以想像,這個精度幾乎可以適用於所有的案例。實際上,當我們把這種量化方式應用到所有動畫的時候,沒有任何一個動畫出現品質下降。另外,我們可以把這些轉化應用到載入時間而不是持續的大批量轉化過程,所以也不需要以後再為此打補丁,所以這種量化方式是非常簡單可行的。

 

 

樣條曲線適配(Curve Fitting

 

為了進一步壓縮,我們使用了樣條曲線適配的方式來改變四元數的值,這是一個創造曲線的過程,或者說是數學功能,能夠最佳適應一系列的資料點。我們特別使用了Catmull-Rom樣條曲線,可以用一個三階多項式(3rd-order polynomial)表示。你需要四個控制點來確定Catmull-Rom樣條曲線,下面借用維基百科提供的資料圖可以更好的說明:

 

 

http://upload.wikimedia.org/wikipedia/commons/4/42/Catmull-Rom_Spline.png

 

 

為了做到準確的適配,我們使用了反覆運算(iterative)方式來減少失誤,這個過程一開始只有2個關鍵幀(keyframes),並且包含了動畫的開始和結束。我們通過反覆運算的方式增加更多的關鍵幀來減少曲線的整體失誤,把它降低到一個可以接受的水準。在每一次的反覆運算中,我們都找出關鍵幀之間的最大錯誤,並且插入一個中間點關鍵幀作為替代,這個找錯並且替代關鍵幀的過程是不斷重複的,直到每一個部分的錯誤都降低到可以接受的程度。

 

 

curve fitting

 

 

你可以看上圖的紅色適配曲線和綠色初始曲線在反覆運算過程中的對比。黃點代表每次反覆運算過程中增加的(新的)關鍵幀。通過這種做法,我們經過88次的反覆運算之後,把最初的661幀降低到了90幀。

 

 

在做曲線插值(curve interpolation)之前,千萬不要忘記調整四個四元數控制點。一個四元數Q和它的相反數-Q代表的是同樣的旋轉,但如果不調整的話,最終旋轉可能無法實現最短途徑的插值。比如,一艘向北行駛的船準備轉向東方,如果沒有合適的四元數調整,那麼它可能直接逆轉270度才能做到,而不是順時針轉90度。

 

 

曲線適配可以對量化結果進行進一步的壓縮,而且壓縮率在25%-75%之間。我們發現為定位、旋轉和量化資料設置合適的誤差值對於不損失視覺體驗情況下獲得最大化壓縮率是至關重要的。

 

 

velkoz joke

 

 

為了更好的壓縮,我們還考慮了樣條曲線節點參數,比如在動畫資料的案例中,基於關鍵幀時序(keyframe timings)的參數是最自然的。不過,你仍然可以看一下的資料(也是來自維基百科),四個同樣控制點的曲線形狀取決於使用哪種節點參數:比如uniform、chordal或者我們使用的centripetal。

 

 

http://engineering.riotgames.com/sites/all/themes/riot_games_engineering/images/catmull_rom_parameterized_time.png

 

 

https://upload.wikimedia.org/wikipedia/commons/thumb/2/2f/Catmull-Rom_examples_with_parameters..png/220px-Catmull-Rom_examples_with_parameters..png

 

 

這些技術會對於某些動畫造成明顯的品質損失,但通過使用嚴密的誤差值,我們可以把損失降低到最小,但這樣做的壓縮率也會降低。因此,我們的動畫師對每個案例都進行審查,以求做到品質和壓縮率方面的平衡。而且,和量化過程不同的是,由於曲線適配過程需要大量計算,所以不可能轉換到載入時間上,因此我們必須對所有現存的動畫資料進行預處理。

 

 

●降低損失

 

壓縮過程導致最明顯的現象就是foot sliding,這會導致動畫中出現角色的腳或者任何末端執行器(end-effector)一直不動。

 

 

http://engineering.riotgames.com/sites/default/files/yasuo-jiggly-foot.gif

 

 

你可以從上面圖中很容易的看到本來應該擺動腳卻一直不動。這是因為skeletal rigging中的骨骼是分等級的,錯誤的累積會造成很大的影響。我們解決這種問題的方法是使用了一種我們稱之為‘可適應錯誤率(adaptive error margins)的技術,它意味著如果一個節點有較長的派生數,你需要把誤差值降到最低,而不是為所有的節點使用同樣的誤差值。比如,一個末端執行器使用特定的比例,但其母單位則使用半數,那麼更上一級的則使用三分之一數,諸如此類。這種自上而下的誤差率降低可以最大化限制派生值發生錯誤的概率。

 

 

Game Programming Gems 7》一書介紹了另一種被稱為‘在骨骼動畫中減少累積誤差’的方法,我們內部把這種方法叫做“連接銷(joint pinning)”。對一個連接銷(比如足部),我們不使用來源資料流(source data stream),而是計算新的本地轉換資料,這樣可以抵消早代資料壓縮中所產生的誤差,這本書中還有更多在這個話題是非常不錯的材料,值得同行們一讀。

 

 

允許緩存的資料結構(Cache-friendly Data Organization

 

最後,我們來討論有效實現了以上概念的方法,在研發這些技術的同時,我們同時非常清醒的知道玩家們的硬體存在很大差異,而且對於降低性能方面的做法十分謹慎,我的團隊專注的其中一件事就是實現允許緩存的資料結構。

 

 

我們採取的非常關鍵的一步就是把所有的關鍵幀(每一個連接銷的位置、旋轉和量化幀)放到一個相連的存儲塊裡。通常見到的做法都是為每個連接銷創造不同的存儲塊,但這樣看似自然的結構在特定時間段評估一個完整骨骼姿勢的時候會導致嚴重的緩存丟失。我們把資料放到一個存儲塊是因為所有管道類型的有效負荷都是48 bit,如我們此前所見,我們把四元數量化到了48 bits,還把3D向量的每一個x,y以及z元件都分配同樣的16 bits,你可以從下面的壓縮幀數代碼看到實際的代碼連接銷結構:

 

 

5

 

 

這裡,我們還把key time量化到了16 bits,連接索引(jointIndex)基於各自幀數資料而不同。V箭頭包含了量化的有效負荷,確定有效負荷是屬於旋轉、位置還是量化是非常重要的,我們使用這兩種最重要的連接索引來完成。這種方法可以把連接索引控制到14 bits,我們一共有16384個連接銷,這對於《英雄聯盟》的英雄來說是足夠用的了,因為通常一個英雄只使用不到100個。

 

 

所以對著這些連接銷做恰當的關鍵幀順序是非常重要的,不管是那種連接銷或者類型都很重要,我們本可以用key time進行瑣碎的排序,但問題很快就出現了。你可以想像一下動畫運行時會發生什麼,從下面的圖片就可以看出問題:

 

 

10

 

 

你可以看到被key time分開的四個關鍵幀以及一個標明了目前重放時間的計時針,你需要Tn、Tn+1、Tn+2以及Tn+3的資訊,因為評估一個樣條曲線需要四個控制點。如果計時針的目前位置是已經過了Tn和Tn+1,那麼它應該是已經熟悉了的,可Tn+2和Tn+3怎麼辦?你可能會覺得自己可以快速的進行線性掃描,因為這兩個幀是可以快速找到的。

 

 

然而這種方法並不是最優的,假如說這些T是幀數位置,如果動畫包含很多旋轉變化的話,那麼很多的旋轉幀可能會存在於兩個臨近位置的幀數之間(如下圖)。這樣的話,所有的幀都放在了一起,通過線性掃描的方式尋找Tn+2和Tn+3就是非常低效率的。

 

 

11

 

 

要讓每一次線性掃描都實現重播的訣竅在於,你要按照時間需要的順序組織幀數,而不是根據key time。一旦計時針通過了Tn的key time之後我們就需要Tn+2,因此我們應該把Tn+2根據Tn的關鍵資料進行安排。這樣任何時候都可以獲得需要的資訊,所以緩存丟失就可以被最小化。下面的圖表可以展示這個工作原理:

 

 

12

 

 

希望我們使用的壓縮方法能夠幫助到所有遇到類似問題的開發者們。

 

 

●結論

 

平均來說,在這篇文章中我所討論的量化技術基本上讓《英雄聯盟》的英雄記憶體需求減半,我們還在努力把曲線適配技術做到更好,因為它需要預處理所有的資料,但從我們初期的結果來看,很可能實現另外50%的壓縮率,也就是說,我們很肯能做到把最初的記憶體需求降低到25%,我對此感到興奮,因為這樣就有機會提高各種玩家設備的遊戲體驗。

 

 

我們未來還可能探索更多的方向,比如32-bit四元數量化、對不同的曲線適配做不同的節點參數、用最小二乘法適配替換反覆運算的做法、對增加新的key進行更多優化等等。動畫壓縮是一個廣泛而且非常深度的話題,我們本文討論的還只是冰山一角。不過,我仍舊希望這些是對你們做動畫壓縮有幫助的,下面是一些參考文獻的連結,祝你們好運。

 

 

上一篇duke-nukem-forever
騎呢FPS《毀滅公爵》官司結束 下隻新game幾時黎?
下一篇maxresdefault
MTGamer 請睇戲!《破風》換票証送比你!
評論

評論

LEAVE A REPLY