Unreal Engine 4 動畫系統介紹
Unreal Open Day 2017 活動上 Epic Games 資深開發者支持工程師王禰先生為到場的開發者介紹了在 Unreal Engine 4 中動畫系統,以下是演講實錄。
大家好!鑒于引擎移動端功能以及 UI 優化都有同事做了介紹,今天我選擇講的主題是關于動畫。動畫是一個非常復雜的系統,我會主要介紹一些基本的概念,大家在了解了基本概念后,就可以在上面做出擴展。我并不會教大家怎么使用動畫工具,關于一些動畫節點的使用,我們的在線文檔上都有比較詳細的說明,也有比較多的資源。今天不會講到的內容包括 Morph target,IK,Retargeting,Rootmotion,Additive,Skeletal Control 這些。
首先,我們先來看看引擎中的動畫系統是如何工作的。為什么我要先講解這樣一個問題,因為國內有很多用戶在使用動畫系統的時候,有很多疑問。這些疑問并不是因為他們沒有查閱文檔,而是因為沒有理解系統的工作方式。本質上,動畫系統工作原理是非常簡單的,我這里還是重新介紹一下。
我們先來看看在引擎中動畫相關的資源主要分為哪幾類。
第一大類是最基本的數據資源。其中主要來自于外部 DCC 工具制作并導入的原始資源,我們稱之為 Anim sequence。
然后,有些資源可能為了制作和導入的方便是分散開來的,但是有些情況下會組合到一起使用。所以引擎中有一種資源叫 Anim Composite。他是使用多個 Anim sequence 或是自身(Anim Composite)所組合成的資源。在使用時,依然被看作是普通的 Anim Sequence。
第三種數據資源類型叫 Blendspace。他可以是一維的也可以是二維的。二維的情況下,在兩個軸上,通過變量控制對任意在二維平面上指定的動畫序列(Anim sequence)作混合。對于任意的二維輸入,總能找到這個輸入值在二維圖像附近最接近的四個動畫序列按照權重來混合。嚴格來說,Blendspace 并不是單純的基礎數據,他也受其它輸入參數的影響來混合 Pose 。但是,由于在動畫混合藍圖中是作為 Pose 的輸入結點,我們這里依然把它作為數據類資源。
第四種數據資源叫 Montage。這一類資源一般是直接受邏輯控制的組合資源。
在數據資源的基礎上,我們還可以綁定一些額外的數據。
第一類常用的數據類型叫 Notify。引擎包括一些內建的 Notify類型。譬如,在走路的時候希望腳步踩到地面的那一刻,觸發踩地面的事件,用來向地面投射貼花,用于產生腳印,以及播放腳步音效或揚起塵土的特效之類。這里的 Notify 你還可以擴展成你自定義的事件類型,可以在藍圖以及代碼中去處理事件對應的邏輯。舉個例子:如果做一個動作或格斗類游戲,在出招的時候,判定并不是從這個動畫開始播放的時刻就已經有了的,可能是從出招動畫到某一時刻開始,才有打擊判定。那么我們就可以通過 Notify 來用事件通知游戲邏輯在特定的時候去打開和關閉判定。
第二類叫 Curve。Curve 就是伴隨動畫序列的時間軸所綁定的曲線數據,后面會有一些舉例。再然后你也可以綁定一些你自定義的數據類型。
?
? 講完剛剛這些數據類型,接下來就是最重要的處理動畫混合邏輯的資源,叫 Anim instance。Anim sequence 的設計是基于對于 3A 級游戲中復雜的動畫需求所產生。這里有一個假設,那就是動畫狀態在復雜的情況下一定是需要對骨骼結構有認知的。所以引擎中的 Anim instance 和骨骼是強耦合的關系。譬如你需要知道腰部的骨骼位置來區別開上半身和下半身的動畫,這樣的設計可以完成相當復雜的動畫混合,但是卻也帶來了一些限制。如果我的整個動畫狀態只需要簡單的一個狀態機在不同的狀態中,譬如閑置、追逐、攻擊、受擊、死亡,在每中狀態中,并不作復雜的混合,而只是播放一個簡單的 Anim instance。在整個邏輯中完全不需要用到骨骼信息。那么照理來說,即使擁有不同骨骼結構的對象,如果只需要這個簡單邏輯的話都可以共享這套邏輯。然而由于我剛剛所說的 Anim instance 和骨骼的強耦合設計導致在現在的引擎框架下,這樣的功能暫時無法完成。我們在內部也在作一些討論,以后可能會有支持純邏輯的 Anim instance 功能,而目前來看,如果大家有這樣的需求,我建議在可能的情況下把這些對象的骨骼層次結構盡可能保持一致,這并不是說多個對象的骷髏要完全一致,而只是骨骼樹的層次結構一致就可以了。譬如你的基礎骨骼是個人形,有些怪物會多出尾巴或翅膀,這些多出的骨骼并不破壞原先的樹狀結構,而只是多出來的分支。所以還是可以利用 Retargeting 來共享 Anim instance 的邏輯。
? Anim instance 中,最明顯的兩塊分別是 EventGraph 和 AnimGraph。其中 EventGraph 就類似于普通的藍圖,用來在 tick 的時候處理一些邏輯狀態的更新以及播放 Montage。當然這些邏輯也可以在 C++里面做。AnimGraph 是用來混合和輸出 Pose 的地方。說到混合,我們可以把每一幀中整個混合的過程看成是一棵樹,從葉子結點輸出的 Pose 經過枝干結點的混合計算輸出到根結點的最終 Pose。我們剛剛說到的數據類的資源,就是這里所謂的葉子結點。這些結點本身不需要其它的 Pose 作為輸入,而直接提供了 Pose 的輸出。而枝干結點則是進行混合的結點,當然真的說混合也不是很準確,有些枝干結點只需要輸入一個 Pose,在自己的結點邏輯中,對這個 Pose 作一些修正,并不進行混合。我們把這些枝干結點計算調整和混合 Pose 的行為稱作評估(evaluate)。舉個最簡單的枝干結點的例子,那就是多結點混合。譬如,輸入的有兩個 Pose ,一個權重是 0.8,另一個是 0.2,相當于是把第一個 Pose 的 BoneMap 的 transform 乘以 0.8,第二個乘以 0.2,再相加輸出。這里我列了一個樹狀圖,來表示動畫混合的過程。但是因為這是個非常簡化了的例子,所以其中不包括直接對骨骼進行控制或者直接 Override 一個 Fullbody slot 來強制更新整個 BoneMap 之類的行為。并且一般來說,一個正常的 anim graph 的一幀的混合也不會像這張簡化圖這樣是棵紅黑樹。首先,就像我剛剛說的,你并不能保證他是二叉的,譬如剛剛說的多混合結點完全可以由三個或以上結點來混合,以及我剛剛說的有些枝干結點,只有一個輸入。再者,大部分情況下他也不會是平衡的。在混合狀態復雜的情況下,我們一般會分層次來混合,這就導致了這棵混合樹會往一個分支方向衍生出去。
好了,那么剛剛看到的是單幀的 Pose 混合計算情況。當持續到多幀以后,情況又會稍微復雜一些。譬如說兩個 Pose 混合起來,他們的長度很有可能不一樣。舉例,我有一個走路的動畫,他可能長達 2 秒,同時我又有一個跑步的動畫,他長達 1 秒。如果我直接混合,就會出現很怪異的情況,譬如走路還在邁左腿的時候,跑步已經邁右腿了,混合起來的姿勢就會非常奇怪?;谶@種情況,我們引入了 Sync Groups 的概念,當我們設置這兩個動畫序列在同一個 Sync Groups 下進行混合時,引擎會把當前混合時權重較高的作為領導,把剩下的序列縮放到和領導序列一樣長的情況,再按比例去做混合。這樣就能解決動畫長度不一致的混合問題。
再來看多幀動畫狀態下,如果狀態復雜,動畫樹上的某些分支在不同的幀內是完全不同的狀態。為了簡化樹的邏輯,動畫混合系統中可以使用狀態機來隔離每一幀的狀態。我這里的圖例舉了一個比較簡單的 Locomotion 的狀態機。
關于動畫混合的這棵樹,在復雜的情況下,我們還會把他做分層。也就是把一棵混合完的樹的根結點緩存下來,作為另一棵樹的葉子結點。當然你也可以把整個復雜的樹連到一起,分層只是為了便于維護和調整。這個圖片是我們的 MOBA 游戲《虛幻爭霸》中一個角色分層混合的模版示例。
?講完了動畫的基礎概念后,我們來看一些例子加深理解。
子樹類用例。在引擎中有一類功能叫 Sub anim instance。這就類似于剛剛說到分層里面的一棵子樹,這個子樹可以擁有一個輸入結點,并且輸出一個 Pose 。典型的應用方式,是把在同一個邏輯下有多種可替換的子邏輯分離開,做到不同的 Sub anim instance 中。這樣可以把剩余的邏輯用來共享。通過替換不同的 Sub anim instance 來組合出最終不同的效果。
接下來講一些葉子類的用例。通常的葉子類結點就是我們剛剛說的數據類結點,我這里舉兩個比較特殊的例子。在 4.17 版本中,我們會加入一個叫 live link 的結點。它通過引擎的消息總線從外部實時讀入數據輸出 Pose 。這里的輸入源可以是各種 DCC 工具,也可以是動作捕捉或手勢識別類設備。在我們放出的第一個版本中,會帶有一個 maya 的實現,通過 maya 的插件把在 maya 中當前動畫的 BoneMap 數據通過 live link 消息總線和引擎進行通信。引擎把接收到的數據轉換成引擎內的數據輸出當前的 Pose 。這樣就可以做到在 maya 中一邊播動畫一邊在引擎中看到效果了。
下一個葉子類結點的舉例,叫 Pose Snapshot。Pose Snapshot 就是把任意指定幀的 BoneMap 記錄下來,在接下來的任意時刻,用來作為數據源輸入和其它 Pose 做混合。譬如在 Robo Recall 中,你打倒了機器人,機器人會進入物理狀態而倒地。你可以把這個狀態存下來,在之后再和站起來的動畫作混合。
剛剛舉了兩個葉子類結點的例子,我們再來看看動畫混合中最大的一類——枝干類結點的例子。大部分情況都是多個 Pose 按權重進行混合,當然也可以是按照 bool、int、enum 值進行混合。我這里依然舉一些特殊的例子。
? 第一個例子是 RigidBody 結點。在講這個結點前,我要先介紹一個伴隨而來的概念,叫 immediate mode physics。引擎中以前的 Physics 是所有的 RigidBody 都加到同一個 PhysX scene,這種情況下如果每個角色身上都有多個需要計算物理的 RigidBody,場景中又有大量的這樣的角色,計算量就相當的大。但是大部分時候角色互相之間的物理碰撞細節大家并不關心,所以這樣的效率比較低。
? 因此我們和 Nvidia 進行了合作,他們對 PhysX 的 Api 進行了調整。在新版本中放出了更底層的 Api 可以讓我們在引擎中做更細致的控制。大家可以看到這個新的 immediate physics,一個角色身上所有的 RigidBody 都只注冊在當前這個 skeletal mesh component 下,多個 SMC(skeletal mesh component 縮寫)之間并不會有交互,這樣很大程度上提高了運行的效率。
大家可以看到,這里的視頻同屏有幾百個小兵站在地上做閑置的動畫,在受到物理沖擊后轉入到物理狀態。這么大量的物理對象在我的筆記本上依然能穩定在 60 幀,而右邊的圖也顯示了單個較為復雜的角色在模擬物理時候的開銷,只使用了 0.24ms。大家可能覺得這是一個純粹物理的功能,為什么我放到動畫的枝干結點的例子里來講呢? 因為事實上你可以在動畫中把動畫計算完的 Pose 輸入進去,在這個結點中根據當前動畫的 Pose 和前一幀計算完的結果計算出骨骼結點的變化,從而模擬出物理受力的變化,并根據輸入的權重混合回你的 Pose 。有了這樣的功能,做我之前說的 Robo Recall 中很自然的擊倒機器人或者拳擊類的游戲、以及用槍射擊怪物時怪物比較自然的受擊都變得相當簡單。
好了,下面我們再來看另一個枝干結點的例子。我們稱之為 Speed Warping。傳統的游戲中如果你調整了移動速度,那么為了不產生滑步你也需要調整跑步的動畫播放的速率。譬如你的速度翻了一倍,那么很多時候你就需要把動畫也加快一倍播放,大家可以看到在這里的視頻右邊加快播放后的動畫其實是很別扭的。真實情況下我們提高速度除了邁出的腳步速度會有一些變快以外,更多的情況下,其實是調快了步幅。同樣的減慢速度也是這樣。所以 Speed Warping 就是做了這么一個效果。那我們是怎么計算的呢??
? 簡單來講,原始的動畫雙腳的位置是這里的紅球。我們計算他跟腰部垂線的水平距離并根據加減速的倍率橫向擴展。譬如當是 2 倍的時候,調整到綠球的位置。但這個時候兩只腳的距離被拉的太長了,因此我們適當的往下調整了屁股的位置,并且將兩只腳以剛才綠球所在位置到屁股的連線上挪動一段距離使得腳步的長度保持不變,所以最終計算出來的就是藍球的位置。
我再舉一些其它的例子。比如引擎中當你對 AnimBP 進行繼承的時候,所創建出來的內容叫 Child AnimBP ——它所做的事情是讓你重載所有的葉子類結點。舉個實用的例子:譬如我有一種敵人,他永遠是從初始的出現狀態到發現玩家到向玩家攻擊這樣轉化,而這樣的怪物在地圖上不同的場景下有不同的出現動畫,有可能是從地上爬出來的,有可能是從墻上跳出來的。對于這個怪物來說,他的動畫切換狀態都是一樣的,所不同的只是初始狀態所需要使用的資源,所以只需要替換初始的動畫(某個葉子結點)就可以了。
再舉一個例子,有不少人問過,在《虛幻爭霸》中,是怎么做到讓角色不滑步的。傳統的主機游戲中,為了讓腳不滑步很多時候我們都是使用 root motion 來做移動的動畫。但是因為《虛幻爭霸》是個 MOBA 游戲,策劃會希望能夠用數據來驅動移動的速度。譬如在有不同的 buff 或者裝備的情況下,角色的速度也會發生變化,這用 root motion 就很不好處理。所以我們做了一個叫 Distance Curve 的功能,這也是我剛剛說到的 Curve 數據的一種運用方式。我們可以把 Distance Curve 的方式看成是反向的 root motion。它通過給所有的啟動、旋轉、站定動畫都加入曲線數據,曲線上的數值表示當前這幀動畫到達站定點的位置的距離,其中站定點(Marker)是很容易預測的。?
-
分享到:
全部評論:0條