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

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

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

特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
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.