《疊紙游戲》的資深物理算法工程師趙英杰先生在本屆 Unite 大會上給大家詳細介紹了游戲中高性能物理框架的實踐,其中包括布料、碰撞檢測等核心功能,以及如何將這些技術在玩法中優(yōu)化應用。
![]()
趙英杰:大家好,接下來由我給大家?guī)怼稇倥c深空》物理效果開發(fā)的相關內(nèi)容分享。我叫趙英杰,來自疊紙游戲,曾參與過《閃耀暖暖》《戀與深空》等項目,目前在《戀與深空》制作組擔任引擎程序,主要負責物理和動畫相關內(nèi)容的開發(fā)。本次分享主要分為四個部分:布料模擬實現(xiàn)、實時表演控制、基于 Unity DOTS 的開發(fā)和碰撞檢測模塊。
布料模擬實現(xiàn)
布料模擬實現(xiàn)在整個《戀與深空》的表現(xiàn)當中占了相當大的一塊部分。比如說我們的劇情表演、戰(zhàn)斗一些活動以及玩法都大量使用了布料的。我們使用的布料系統(tǒng)是自己開發(fā)的一套基于骨骼的布料模擬系統(tǒng),內(nèi)部名稱的叫做 StrayCloth。采用的模擬方法是 XPBD 結合 SubStep 的方式。相比 PBD,XPBD 的優(yōu)點是擺脫了迭代次數(shù)和時間步長的依賴,結合 SubStep 可以顯著提升解算的收斂效果。比較特殊的地方在于,我們使用骨骼作為模擬粒子,也就說每個粒子除了位置以外還帶旋轉信息。在具體的 SubStep 實現(xiàn)中,我們針對不同性能壓力場景采用動態(tài)的子步幅時間,在 1/200 -1/300 之間。并且對場景中的運動對象進行運動插值,這樣碰撞的效果會更加穩(wěn)定。事實上運動插值雖然性能開銷不是很高,但是由于類型眾多,比如有靜態(tài)粒子,碰撞體,風場等,實踐起來還是非常麻煩的。
![]()
這里其實有一個疑問,我們?yōu)槭裁词褂霉趋蓝皇褂么砭W(wǎng)絡,而是使用頂點的方式去模擬布料。《戀與深空》項目中對于布料表現(xiàn)的模擬需求其實是比較復雜的,很多時候需要動畫和解算的共同介入。骨骼方案可以在這兩者之間做一個很好的過渡和平衡。然后受限于移動端的性能,骨骼方案結合我們 cts 的一些配置,可以留給美術很大的自由調(diào)節(jié)空間。在骨骼的基礎上,我們實際上構建了類似頂點模擬的約束方式,可以看下圖右側的圖,是一個物理資產(chǎn)的 debug 圖,可以達到和 Mesh 模擬相對近似的效果。
![]()
在已有的骨骼布料方案里,骨骼約束實現(xiàn)常采用基于 Local 和 Global 形狀約束的實現(xiàn)方式,這種方式的優(yōu)點就是簡單快速。但是也有明顯的缺點,在用來做布料模擬時,效果偏向卡通風格,這不符合《戀與深空》追求的3D寫實風格,而且它的參數(shù)調(diào)整非常不直觀,因為它有 gloabl 和 local 兩個彎曲參數(shù),不利于美術調(diào)整以及在不同場景下的效果匹配。
![]()
我們在骨骼約束方案上,選擇了基于 Cosserat Rod 的骨骼約束。它有幾個優(yōu)點,第一是效果上更加自然貼近《戀與深空》整體的3D寫實美術表現(xiàn)風格。第二是彎曲參數(shù)上只有一個參數(shù),更加直觀,并且三個軸向強度分離,在模擬一些特殊場合,比如帶有裙撐的裙子的時候,可以通過各向異性的彎曲強度來模擬出近似裙撐的效果。第三,這個方法在正常情況下其實更多地使用于頭發(fā)和繩索的模擬,所以我們頭發(fā)和衣服一樣也使用同一套約束。這樣工程量就會簡化不少。下方視頻是《戀與深空》最新日卡的一個表現(xiàn)視頻,總的來說基于 Cosserat Rod 的骨骼約束是可以滿足項目的一些表現(xiàn)需求的。
布料和角色的連接通過兩種方式連接。第一種是靜態(tài)骨骼直接受角色的骨骼動畫影響,根據(jù)層級關系進行移動。這種方式比較簡單,在一些偏向于剛性的連接部位時表現(xiàn)良好。但是對于一些骨骼交界有多個骨骼影響或者存在一定幅度拉伸和收縮的較為復雜的位置,例如手肘、肩部、腰部,表現(xiàn)上容易出現(xiàn)布料和角色分離。這時候我們提供第二種吸附的方式,將靜態(tài)粒子吸附到角色模型的某個三角形上,通過并行的 bake mesh 獲取每幀頂點的更新位置,使用離線計算的重心坐標來更新粒子的 transform。對于三角形存在的退化的特殊情況,我們使用三角形頂點的蒙皮骨骼的變換,進行加權平權來更新靜態(tài)粒子的 transform。
![]()
碰撞方案上,我們使用一個 dynamic Bvh 來作為場景碰撞的 broad phase 管理,每個角色作為 sub tree,其內(nèi)部的碰撞體就作為 sub tree node。通過角色 ID、分享可見性還有部件類型,這個三個規(guī)則來實現(xiàn)不同角色、不同部件的碰撞規(guī)則的共享規(guī)則管理。在 narrow phase 當中,我們不直接生成 contact,而是緩存碰撞體對,在 substep 中再具體的解決它們的 overlap。因為我們采用的 sub step 的優(yōu)點,大多數(shù)情況下直接使用 DCD 就可以避免一些快速運動下造成的穿透問題,不需要引入 ccd 或者可預測碰撞(predictive contact)等一些操作。
![]()
對于參數(shù)化的幾何碰撞體,例如平面(plane)、膠囊體(capsule)、box,可以比較簡單地解決它們和粒子以及 edge 的碰撞。在肩部、胸部、背部等比較復雜區(qū)域,參數(shù)化的幾何體難以準確表達角色模型形態(tài),表現(xiàn)上容易發(fā)生穿透,所以在這些部位我們大量的使用 Mesh collider,但是 mesh collider 作為不規(guī)則的凹體,甚至有些時候來說它都不是封閉的,只是一個面。想達到精準的碰撞效果相對參數(shù)化幾何體就比較困難,特別是在移動設備下。我們采用散列哈希來作為三角形的粗略查找方式,結合緩存的鄰近三角形結果,在迭代開始前生成一次粒子-三角形碰撞對,后續(xù)的迭代中判讀粒子是否在三角形的范圍,如果超出三角形的范圍,通過鄰接關系進行限制步幅的三角形查找來獲取最近的三角形,并且緩存結果作為下一次的使用。下方的視頻是目前我們項目中的一些具體表現(xiàn)示例,可以看到表現(xiàn)上是比較穩(wěn)定的。
面部碰撞體可以看作是特殊的 Mesh collider,相對于基本的 mesh collider,它形態(tài)較為固定,也較為平滑,從模型中心出發(fā)基本上沒有三角形重疊,所以我們使用 16x16 的 cubemap 來預計算各個方向上的三角形,這樣碰撞計算時可以快速查找到鄰近的三角形。
![]()
游戲當中布料模擬的自碰撞是最難處理的部分,出于性能上的考慮,我們給出的方案是由美術預先對布料進行分層,只考慮這些層之間的碰撞。使用散列哈希作為查找的加速結構,并且為了避免層之間卡住的情況,我們只考慮單法線方向的碰撞,如果已經(jīng)穿透了則略過,交給后面的步驟來修復。實際實踐中,我們使用上一次 substep 的粒子位置來和當前的粒子位置進行碰撞,這樣可以很簡單的就解耦數(shù)據(jù)避免依賴。下方視頻是項目中的一些層間碰撞的例子。
對于層碰撞已經(jīng)穿透的部分,我們參考了 untanging cloth 的方式,使用了一個輕量的解決辦法,通過布料分層,從布料的固定點出發(fā),計算不同層級的邊和三角形的交點,因為我們的資產(chǎn)結構必定為一個 uniform 的網(wǎng)格,因此可以通過網(wǎng)格交點比較簡單的推測出其它粒子的推出三角形,最后對穿透的粒子-三角形對施加彈簧約束來解決穿透。在實踐中由于 substep 的關系,穿透的概率相對不大,因此我們采用分幀分塊執(zhí)行來減輕性能壓力。下方視頻當中是一個三層的穿透分離測試,可以看到各層布料可以正確的從已穿透的狀態(tài)中恢復出來。

實時表演控制
實時表演在《戀與深空》中了占了相當大的一塊部分。《戀與深空》劇情表現(xiàn)中大部分的物理表現(xiàn),都是依托于 cutscene 來實現(xiàn)的各種物理效果的控制和調(diào)節(jié)。感謝我們的工具同學開發(fā)和維護了一套非常強大的 cutscene 工具,在他們的基礎上我們開發(fā)了多種的功能軌道來具體調(diào)控物理效果。物理相關的功能軌道的種類非常多,下面是一些較為常用的功能軌道,后面我會細致的介紹一些它們的具體功能。
![]()
下方的動圖是我們一個動卡的 Cutscene Physcs Track 的例子,因為我們美術同學對于畫面表現(xiàn)扣得非常細,所以看上去很普通的一個鏡頭可以看到整個物理軌道的配置還是非常復雜的。從這個圖中可以看出來,美術配置了大量的軌道來保證畫面能夠達到他們預期的效果。

SmoothBlendPose Track:在表現(xiàn)當中,一個非常常見的問題就是動作瞬間切換帶來的物理抖動。這個無論是在劇情表演中還是換裝中,都經(jīng)常出現(xiàn)。我們開發(fā)了一個較為通用的辦法,通過記錄初始物理姿態(tài),在切換的時候在初始姿態(tài)和當前姿態(tài)中進行姿態(tài)插值計算,這樣就可以大幅度的緩解抖動,當然這個會帶來一些時間開銷,一般會在幾毫秒到幾十毫秒左右,在大多數(shù)情況下都可以接受的。我們也會額外提供一些參數(shù),例如插值次數(shù)、插值的步幅大小,來讓美術可以根據(jù)實際需要來去調(diào)整。下方是換裝中的視頻,在切動作的時候會有大幅度切換,可以看到動作表現(xiàn)還是相對來說比較穩(wěn)定的。
當然,SmoothBlendPose 也存在局限性,是通過 pose 間插值得到結果,不能保證的完全順暢,特別是在一些劇情表演的復雜鏡頭切鏡下。在這種情況下,我們還提供了一個比較直接的方案,離線直接保存某個時間幀的物理狀態(tài),在播放時,將保存的物理狀態(tài)直接應用到布料上,這樣就可以完美避免切鏡帶來的一些布料抖動的問題。下方視頻中是快速的切鏡,動作切換的鏡頭,可以看到使用 Pose Track 就可以讓布料和頭發(fā)的表現(xiàn)相對來說非常穩(wěn)定。
參數(shù)編輯軌道:單一的物理參數(shù)是很難滿足劇情當中的各種不同場景下的表現(xiàn)的,比如有的時候希望布料軟一些硬一些,阻尼大一些小一些。我們提供編輯參數(shù)的軌道,通過這個軌道來實時修改參數(shù),絕大部分的參數(shù)都可以覆蓋大,可以非常方便美術針對一小段時間幀進行參數(shù)修改。這個參數(shù)修改還可以用來做一些特殊的效果,比如下方視頻當中,美術就會利用參數(shù)軌道對約束參數(shù)進行編輯來實現(xiàn)實時的類似于布料斷開的效果。
動畫軌道:完全的物理效果實際上不足以支持起整個畫面方方面面的表現(xiàn)的,很多時候需要動畫和物理的結合來做一些互動。我們通過動畫軌道來實現(xiàn)動畫和物理的銜接與融合,精細的控制不同時間幀范圍下的表現(xiàn)。在實際制作流程當中,動作在 DCC 里和最終進引擎的表現(xiàn)差異是比較大的,包括一些引擎的實時 rig 系統(tǒng)修改后,動畫可能和其它地方有穿透,所以我們會在動畫融合的基礎上,疊加上物理的碰撞效果,來避免一些穿插。視頻當中展示是項鏈在物理和動畫之間的交互效果,包括從物理到動畫的狀態(tài)切換以及在不同動畫之前的切換。
碰撞體軌道(Collider Track)與風場軌道(Wind Track)都可以在 cutscene 中動態(tài)地創(chuàng)建、銷毀。可以根據(jù)不同畫面需求,靈活改變碰撞體和風場的狀態(tài)。通過角色、部件類型、還有布料的層分組來細節(jié)控制所要影響的對象范圍。并且,碰撞體和風場軌道的絕大部分參數(shù)可以添加動畫幀控制,包括碰撞體的形態(tài)大小、風場的方向、范圍、強度、湍流等,方便美術細節(jié)地把控物理效果,精準控制變化。視頻當中是一些軌道膠囊體和風場的表現(xiàn)例子。
基于 Unity DOTS 的開發(fā)
我們整套物理系統(tǒng)都是構建在 DOTS上,DOTS 這套工具非常強大。在 C# 層就可以實現(xiàn)高性能的多線程開發(fā)功能,迭代和 Debug 都非常便利。目前來說最高可以支持 2000+ 骨骼粒子的模擬。在針對性的項目使用當中我們也做了一些優(yōu)化來進一步的提升性能。
Cache Job
模擬中的 job 數(shù)量和依賴關系確定,job data 并不頻繁變化,幀內(nèi)一般為相同數(shù)量和依賴關系的 job 組多次循環(huán)執(zhí)行,Unity Jobs 在發(fā)起任務時每次都需要重新創(chuàng)建 job,雖然可以提前發(fā)起任務緩解,在子線程上執(zhí)行,但是依然會卡主線程。基于以上的觀察,我們開發(fā)了 Cache Job 的方案,預先創(chuàng)建好 job data,然后每次執(zhí)行時復用,避免每次重新創(chuàng)建 job 帶來的性能開銷。
在實現(xiàn)上的話相對來說比較簡單。因為它是一個專用的結構,只考慮一些固定的使用場景。我們額外添加了一個 Atomic Queue 用來存 cache job,使用 fetch and add array 來存具體的所需的 job data。下圖右側就是 worker 執(zhí)行 cache job 的流程示意圖。
![]()
Neon Intrinsics
Burst 會針對不同的平臺生成高性能的 simd code。在 Burst Inspector 中可以非常方便地查看。經(jīng)過檢查 Burst Inspector 和實機測試,在某些場合下也可以通過手寫 Arm Neon Intrinsics 來進一步提升性能。
![]()
這里給出兩個比較常使用的例子:一是,F(xiàn)loat4 的點乘。對于點乘,我這里列出了 3 種方式,使用 neon intrinsics 相比于 mathematics 在測試用例中可以獲得相當大的性能提升。如果目標機型支持 armv8.2 的話,可以使用新增的規(guī)約加法指令,來進一步地提升性能。一般來說,市場上大部分的流行機型,如果是重度游戲,都會支持 ArmV8.2 的。但是在我實際的測試當中,最新的 Burst 實際上可以直接生成 ArmV9.0 的指令。但是在當前,你想要使用一定要去手寫它。不過它帶來的性能提升,其實還是比較明顯的。特別是你去做一些展開,利用指令級可以得到更高的性能提升。
二是 float4×4 的轉置。對于轉置計算,可以看到 mathematics 生成的 assembly code 看起來性能是非常低的,通過手寫 neon intrinsics 就可以得到一個巨大的性能提升。如果只是純粹的需要轉置,可以直接使用交錯讀,這樣可以得到更快的性能。這里這樣實現(xiàn)因為在一般的實際使用中,是通過對 4 個 float4 轉置來將點乘變成矢量乘。但是在實際的使用當中,因為 mathematics 的代碼一般被內(nèi)聯(lián)到具體的上下文當中,最終生成的 assembly code 相對于你自己的單元測試差別會非常大。所以在具體優(yōu)化時還需要根據(jù)代碼的上下文進行具體的優(yōu)化,可以結合 burst inspector、真機測試和 Arm 的優(yōu)化手冊,來具體針對你的項目做出一些性能之間的比較和修改。
碰撞檢測模塊
首先要回答一個問題,就是 Unity 已經(jīng)有基于 physx 的一套非常成熟的物理模塊了,我們?yōu)槭裁匆撾x Unity 成熟的物理模塊重新開發(fā)呢?
因為《戀與深空》有相當多不同種類的玩法,玩法間的 layer 設置相對獨立,有些模塊比如戰(zhàn)斗,希望需要有特殊的 Trigger 觸發(fā)和退出機制,并且希望在底層就能夠支持,對于執(zhí)行流程也希望有更靈活的控制。我們在性能探索上,也有一些自己的想法。就是說在僅需要碰撞測試的情況下,我們利用 DOTS 能否提升它的性能?
在《戀與深空》的實現(xiàn)當中,我們的碰撞檢測模塊基本實現(xiàn)了 Unity 原生的所有碰撞查詢功能,針對戰(zhàn)斗和其他模塊我們定制了 Update 和 Trigger 相關邏輯。并且對于查詢接口,在底層就保證了線程安全,讓上層可以無負擔調(diào)用。結合 DOTS 進行輕量化的實現(xiàn),以及結合一些實際需求的優(yōu)化,在性能測試當中我們最高獲得了 15% 的性能提升。下方視頻是戰(zhàn)斗當中的一些錄屏,整個角色的移動、技能命中,都是用我們自己的碰撞檢測模塊來實現(xiàn)的。
查詢流程示例。下圖是一個簡單的示例。由于真機上我們實際的線程數(shù)量是固定的為 4,所以對于 memory allocator 可以預先按照線程數(shù)量分配好,在分配時可以直接根據(jù)當前線程索引來獲取所需的內(nèi)存。我們使用基于 SAH 的 dynamic bvh 作為 broadphase 加速結構,在插入、刪除以及超出范圍的移動時,對當前操作節(jié)點的鄰近的幾個層級節(jié)點進行旋轉平衡。因為碰撞檢測的功能目標相對概括,對于精度要求沒有那么高,所以我們也適當?shù)臓奚恍┚群喕艘恍┡鲎矙z測算法來提升性能。
![]()
為了滿足戰(zhàn)斗模塊的需要,我們還設計了特殊的 trigger 觸發(fā)邏輯,觸發(fā) Trigger的進入和退出必須要成對出現(xiàn),可以看到下方的流程示意圖,在 A 觸發(fā) B 的函數(shù)中移除 B 后,會觸發(fā)所有和 B 存在 overlap 的 collider。這里是和 Unity 原生的不一樣的,原生的 Unity 中在 trigger 邏輯中刪除掉 B 是不會觸發(fā)其它碰撞體的 trigger的。最后,我們通過 History 計數(shù)來標記 collider 的版本,解決戰(zhàn)斗中角色或碰撞體等道具的復用可能會導致的一些潛在問題。
![]()
我的分享結束了。謝謝大家!
Unity 官方微信
第一時間了解Unity引擎動向,學習進階開發(fā)技能
每一個“點贊”、“在看”,都是我們前進的動力

特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺“網(wǎng)易號”用戶上傳并發(fā)布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.