《疊紙游戲》的資深物理算法工程師趙英杰先生在本屆 Unite 大會(huì)上給大家詳細(xì)介紹了游戲中高性能物理框架的實(shí)踐,其中包括布料、碰撞檢測(cè)等核心功能,以及如何將這些技術(shù)在玩法中優(yōu)化應(yīng)用。
![]()
趙英杰:大家好,接下來由我給大家?guī)怼稇倥c深空》物理效果開發(fā)的相關(guān)內(nèi)容分享。我叫趙英杰,來自疊紙游戲,曾參與過《閃耀暖暖》《戀與深空》等項(xiàng)目,目前在《戀與深空》制作組擔(dān)任引擎程序,主要負(fù)責(zé)物理和動(dòng)畫相關(guān)內(nèi)容的開發(fā)。本次分享主要分為四個(gè)部分:布料模擬實(shí)現(xiàn)、實(shí)時(shí)表演控制、基于 Unity DOTS 的開發(fā)和碰撞檢測(cè)模塊。
布料模擬實(shí)現(xiàn)
布料模擬實(shí)現(xiàn)在整個(gè)《戀與深空》的表現(xiàn)當(dāng)中占了相當(dāng)大的一塊部分。比如說我們的劇情表演、戰(zhàn)斗一些活動(dòng)以及玩法都大量使用了布料的。我們使用的布料系統(tǒng)是自己開發(fā)的一套基于骨骼的布料模擬系統(tǒng),內(nèi)部名稱的叫做 StrayCloth。采用的模擬方法是 XPBD 結(jié)合 SubStep 的方式。相比 PBD,XPBD 的優(yōu)點(diǎn)是擺脫了迭代次數(shù)和時(shí)間步長的依賴,結(jié)合 SubStep 可以顯著提升解算的收斂效果。比較特殊的地方在于,我們使用骨骼作為模擬粒子,也就說每個(gè)粒子除了位置以外還帶旋轉(zhuǎn)信息。在具體的 SubStep 實(shí)現(xiàn)中,我們針對(duì)不同性能壓力場景采用動(dòng)態(tài)的子步幅時(shí)間,在 1/200 -1/300 之間。并且對(duì)場景中的運(yùn)動(dòng)對(duì)象進(jìn)行運(yùn)動(dòng)插值,這樣碰撞的效果會(huì)更加穩(wěn)定。事實(shí)上運(yùn)動(dòng)插值雖然性能開銷不是很高,但是由于類型眾多,比如有靜態(tài)粒子,碰撞體,風(fēng)場等,實(shí)踐起來還是非常麻煩的。
![]()
這里其實(shí)有一個(gè)疑問,我們?yōu)槭裁词褂霉趋蓝皇褂么砭W(wǎng)絡(luò),而是使用頂點(diǎn)的方式去模擬布料。《戀與深空》項(xiàng)目中對(duì)于布料表現(xiàn)的模擬需求其實(shí)是比較復(fù)雜的,很多時(shí)候需要?jiǎng)赢嫼徒馑愕墓餐槿搿9趋婪桨缚梢栽谶@兩者之間做一個(gè)很好的過渡和平衡。然后受限于移動(dòng)端的性能,骨骼方案結(jié)合我們 cts 的一些配置,可以留給美術(shù)很大的自由調(diào)節(jié)空間。在骨骼的基礎(chǔ)上,我們實(shí)際上構(gòu)建了類似頂點(diǎn)模擬的約束方式,可以看下圖右側(cè)的圖,是一個(gè)物理資產(chǎn)的 debug 圖,可以達(dá)到和 Mesh 模擬相對(duì)近似的效果。
![]()
在已有的骨骼布料方案里,骨骼約束實(shí)現(xiàn)常采用基于 Local 和 Global 形狀約束的實(shí)現(xiàn)方式,這種方式的優(yōu)點(diǎn)就是簡單快速。但是也有明顯的缺點(diǎn),在用來做布料模擬時(shí),效果偏向卡通風(fēng)格,這不符合《戀與深空》追求的3D寫實(shí)風(fēng)格,而且它的參數(shù)調(diào)整非常不直觀,因?yàn)樗?gloabl 和 local 兩個(gè)彎曲參數(shù),不利于美術(shù)調(diào)整以及在不同場景下的效果匹配。
![]()
我們?cè)诠趋兰s束方案上,選擇了基于 Cosserat Rod 的骨骼約束。它有幾個(gè)優(yōu)點(diǎn),第一是效果上更加自然貼近《戀與深空》整體的3D寫實(shí)美術(shù)表現(xiàn)風(fēng)格。第二是彎曲參數(shù)上只有一個(gè)參數(shù),更加直觀,并且三個(gè)軸向強(qiáng)度分離,在模擬一些特殊場合,比如帶有裙撐的裙子的時(shí)候,可以通過各向異性的彎曲強(qiáng)度來模擬出近似裙撐的效果。第三,這個(gè)方法在正常情況下其實(shí)更多地使用于頭發(fā)和繩索的模擬,所以我們頭發(fā)和衣服一樣也使用同一套約束。這樣工程量就會(huì)簡化不少。下方視頻是《戀與深空》最新日卡的一個(gè)表現(xiàn)視頻,總的來說基于 Cosserat Rod 的骨骼約束是可以滿足項(xiàng)目的一些表現(xiàn)需求的。
布料和角色的連接通過兩種方式連接。第一種是靜態(tài)骨骼直接受角色的骨骼動(dòng)畫影響,根據(jù)層級(jí)關(guān)系進(jìn)行移動(dòng)。這種方式比較簡單,在一些偏向于剛性的連接部位時(shí)表現(xiàn)良好。但是對(duì)于一些骨骼交界有多個(gè)骨骼影響或者存在一定幅度拉伸和收縮的較為復(fù)雜的位置,例如手肘、肩部、腰部,表現(xiàn)上容易出現(xiàn)布料和角色分離。這時(shí)候我們提供第二種吸附的方式,將靜態(tài)粒子吸附到角色模型的某個(gè)三角形上,通過并行的 bake mesh 獲取每幀頂點(diǎn)的更新位置,使用離線計(jì)算的重心坐標(biāo)來更新粒子的 transform。對(duì)于三角形存在的退化的特殊情況,我們使用三角形頂點(diǎn)的蒙皮骨骼的變換,進(jìn)行加權(quán)平權(quán)來更新靜態(tài)粒子的 transform。
![]()
碰撞方案上,我們使用一個(gè) dynamic Bvh 來作為場景碰撞的 broad phase 管理,每個(gè)角色作為 sub tree,其內(nèi)部的碰撞體就作為 sub tree node。通過角色 ID、分享可見性還有部件類型,這個(gè)三個(gè)規(guī)則來實(shí)現(xiàn)不同角色、不同部件的碰撞規(guī)則的共享規(guī)則管理。在 narrow phase 當(dāng)中,我們不直接生成 contact,而是緩存碰撞體對(duì),在 substep 中再具體的解決它們的 overlap。因?yàn)槲覀儾捎玫?sub step 的優(yōu)點(diǎn),大多數(shù)情況下直接使用 DCD 就可以避免一些快速運(yùn)動(dòng)下造成的穿透問題,不需要引入 ccd 或者可預(yù)測(cè)碰撞(predictive contact)等一些操作。
![]()
對(duì)于參數(shù)化的幾何碰撞體,例如平面(plane)、膠囊體(capsule)、box,可以比較簡單地解決它們和粒子以及 edge 的碰撞。在肩部、胸部、背部等比較復(fù)雜區(qū)域,參數(shù)化的幾何體難以準(zhǔn)確表達(dá)角色模型形態(tài),表現(xiàn)上容易發(fā)生穿透,所以在這些部位我們大量的使用 Mesh collider,但是 mesh collider 作為不規(guī)則的凹體,甚至有些時(shí)候來說它都不是封閉的,只是一個(gè)面。想達(dá)到精準(zhǔn)的碰撞效果相對(duì)參數(shù)化幾何體就比較困難,特別是在移動(dòng)設(shè)備下。我們采用散列哈希來作為三角形的粗略查找方式,結(jié)合緩存的鄰近三角形結(jié)果,在迭代開始前生成一次粒子-三角形碰撞對(duì),后續(xù)的迭代中判讀粒子是否在三角形的范圍,如果超出三角形的范圍,通過鄰接關(guān)系進(jìn)行限制步幅的三角形查找來獲取最近的三角形,并且緩存結(jié)果作為下一次的使用。下方的視頻是目前我們項(xiàng)目中的一些具體表現(xiàn)示例,可以看到表現(xiàn)上是比較穩(wěn)定的。
面部碰撞體可以看作是特殊的 Mesh collider,相對(duì)于基本的 mesh collider,它形態(tài)較為固定,也較為平滑,從模型中心出發(fā)基本上沒有三角形重疊,所以我們使用 16x16 的 cubemap 來預(yù)計(jì)算各個(gè)方向上的三角形,這樣碰撞計(jì)算時(shí)可以快速查找到鄰近的三角形。
![]()
游戲當(dāng)中布料模擬的自碰撞是最難處理的部分,出于性能上的考慮,我們給出的方案是由美術(shù)預(yù)先對(duì)布料進(jìn)行分層,只考慮這些層之間的碰撞。使用散列哈希作為查找的加速結(jié)構(gòu),并且為了避免層之間卡住的情況,我們只考慮單法線方向的碰撞,如果已經(jīng)穿透了則略過,交給后面的步驟來修復(fù)。實(shí)際實(shí)踐中,我們使用上一次 substep 的粒子位置來和當(dāng)前的粒子位置進(jìn)行碰撞,這樣可以很簡單的就解耦數(shù)據(jù)避免依賴。下方視頻是項(xiàng)目中的一些層間碰撞的例子。
對(duì)于層碰撞已經(jīng)穿透的部分,我們參考了 untanging cloth 的方式,使用了一個(gè)輕量的解決辦法,通過布料分層,從布料的固定點(diǎn)出發(fā),計(jì)算不同層級(jí)的邊和三角形的交點(diǎn),因?yàn)槲覀兊馁Y產(chǎn)結(jié)構(gòu)必定為一個(gè) uniform 的網(wǎng)格,因此可以通過網(wǎng)格交點(diǎn)比較簡單的推測(cè)出其它粒子的推出三角形,最后對(duì)穿透的粒子-三角形對(duì)施加彈簧約束來解決穿透。在實(shí)踐中由于 substep 的關(guān)系,穿透的概率相對(duì)不大,因此我們采用分幀分塊執(zhí)行來減輕性能壓力。下方視頻當(dāng)中是一個(gè)三層的穿透分離測(cè)試,可以看到各層布料可以正確的從已穿透的狀態(tài)中恢復(fù)出來。
![]()
實(shí)時(shí)表演控制
實(shí)時(shí)表演在《戀與深空》中了占了相當(dāng)大的一塊部分。《戀與深空》劇情表現(xiàn)中大部分的物理表現(xiàn),都是依托于 cutscene 來實(shí)現(xiàn)的各種物理效果的控制和調(diào)節(jié)。感謝我們的工具同學(xué)開發(fā)和維護(hù)了一套非常強(qiáng)大的 cutscene 工具,在他們的基礎(chǔ)上我們開發(fā)了多種的功能軌道來具體調(diào)控物理效果。物理相關(guān)的功能軌道的種類非常多,下面是一些較為常用的功能軌道,后面我會(huì)細(xì)致的介紹一些它們的具體功能。
![]()
下方的動(dòng)圖是我們一個(gè)動(dòng)卡的 Cutscene Physcs Track 的例子,因?yàn)槲覀兠佬g(shù)同學(xué)對(duì)于畫面表現(xiàn)扣得非常細(xì),所以看上去很普通的一個(gè)鏡頭可以看到整個(gè)物理軌道的配置還是非常復(fù)雜的。從這個(gè)圖中可以看出來,美術(shù)配置了大量的軌道來保證畫面能夠達(dá)到他們預(yù)期的效果。
![]()
SmoothBlendPose Track:在表現(xiàn)當(dāng)中,一個(gè)非常常見的問題就是動(dòng)作瞬間切換帶來的物理抖動(dòng)。這個(gè)無論是在劇情表演中還是換裝中,都經(jīng)常出現(xiàn)。我們開發(fā)了一個(gè)較為通用的辦法,通過記錄初始物理姿態(tài),在切換的時(shí)候在初始姿態(tài)和當(dāng)前姿態(tài)中進(jìn)行姿態(tài)插值計(jì)算,這樣就可以大幅度的緩解抖動(dòng),當(dāng)然這個(gè)會(huì)帶來一些時(shí)間開銷,一般會(huì)在幾毫秒到幾十毫秒左右,在大多數(shù)情況下都可以接受的。我們也會(huì)額外提供一些參數(shù),例如插值次數(shù)、插值的步幅大小,來讓美術(shù)可以根據(jù)實(shí)際需要來去調(diào)整。下方是換裝中的視頻,在切動(dòng)作的時(shí)候會(huì)有大幅度切換,可以看到動(dòng)作表現(xiàn)還是相對(duì)來說比較穩(wěn)定的。
當(dāng)然,SmoothBlendPose 也存在局限性,是通過 pose 間插值得到結(jié)果,不能保證的完全順暢,特別是在一些劇情表演的復(fù)雜鏡頭切鏡下。在這種情況下,我們還提供了一個(gè)比較直接的方案,離線直接保存某個(gè)時(shí)間幀的物理狀態(tài),在播放時(shí),將保存的物理狀態(tài)直接應(yīng)用到布料上,這樣就可以完美避免切鏡帶來的一些布料抖動(dòng)的問題。下方視頻中是快速的切鏡,動(dòng)作切換的鏡頭,可以看到使用 Pose Track 就可以讓布料和頭發(fā)的表現(xiàn)相對(duì)來說非常穩(wěn)定。
參數(shù)編輯軌道:單一的物理參數(shù)是很難滿足劇情當(dāng)中的各種不同場景下的表現(xiàn)的,比如有的時(shí)候希望布料軟一些硬一些,阻尼大一些小一些。我們提供編輯參數(shù)的軌道,通過這個(gè)軌道來實(shí)時(shí)修改參數(shù),絕大部分的參數(shù)都可以覆蓋大,可以非常方便美術(shù)針對(duì)一小段時(shí)間幀進(jìn)行參數(shù)修改。這個(gè)參數(shù)修改還可以用來做一些特殊的效果,比如下方視頻當(dāng)中,美術(shù)就會(huì)利用參數(shù)軌道對(duì)約束參數(shù)進(jìn)行編輯來實(shí)現(xiàn)實(shí)時(shí)的類似于布料斷開的效果。
動(dòng)畫軌道:完全的物理效果實(shí)際上不足以支持起整個(gè)畫面方方面面的表現(xiàn)的,很多時(shí)候需要?jiǎng)赢嫼臀锢淼慕Y(jié)合來做一些互動(dòng)。我們通過動(dòng)畫軌道來實(shí)現(xiàn)動(dòng)畫和物理的銜接與融合,精細(xì)的控制不同時(shí)間幀范圍下的表現(xiàn)。在實(shí)際制作流程當(dāng)中,動(dòng)作在 DCC 里和最終進(jìn)引擎的表現(xiàn)差異是比較大的,包括一些引擎的實(shí)時(shí) rig 系統(tǒng)修改后,動(dòng)畫可能和其它地方有穿透,所以我們會(huì)在動(dòng)畫融合的基礎(chǔ)上,疊加上物理的碰撞效果,來避免一些穿插。視頻當(dāng)中展示是項(xiàng)鏈在物理和動(dòng)畫之間的交互效果,包括從物理到動(dòng)畫的狀態(tài)切換以及在不同動(dòng)畫之前的切換。
碰撞體軌道(Collider Track)與風(fēng)場軌道(Wind Track)都可以在 cutscene 中動(dòng)態(tài)地創(chuàng)建、銷毀。可以根據(jù)不同畫面需求,靈活改變碰撞體和風(fēng)場的狀態(tài)。通過角色、部件類型、還有布料的層分組來細(xì)節(jié)控制所要影響的對(duì)象范圍。并且,碰撞體和風(fēng)場軌道的絕大部分參數(shù)可以添加動(dòng)畫幀控制,包括碰撞體的形態(tài)大小、風(fēng)場的方向、范圍、強(qiáng)度、湍流等,方便美術(shù)細(xì)節(jié)地把控物理效果,精準(zhǔn)控制變化。視頻當(dāng)中是一些軌道膠囊體和風(fēng)場的表現(xiàn)例子。
基于 Unity DOTS 的開發(fā)
我們整套物理系統(tǒng)都是構(gòu)建在 DOTS上,DOTS 這套工具非常強(qiáng)大。在 C# 層就可以實(shí)現(xiàn)高性能的多線程開發(fā)功能,迭代和 Debug 都非常便利。目前來說最高可以支持 2000+ 骨骼粒子的模擬。在針對(duì)性的項(xiàng)目使用當(dāng)中我們也做了一些優(yōu)化來進(jìn)一步的提升性能。
Cache Job
模擬中的 job 數(shù)量和依賴關(guān)系確定,job data 并不頻繁變化,幀內(nèi)一般為相同數(shù)量和依賴關(guān)系的 job 組多次循環(huán)執(zhí)行,Unity Jobs 在發(fā)起任務(wù)時(shí)每次都需要重新創(chuàng)建 job,雖然可以提前發(fā)起任務(wù)緩解,在子線程上執(zhí)行,但是依然會(huì)卡主線程。基于以上的觀察,我們開發(fā)了 Cache Job 的方案,預(yù)先創(chuàng)建好 job data,然后每次執(zhí)行時(shí)復(fù)用,避免每次重新創(chuàng)建 job 帶來的性能開銷。
在實(shí)現(xiàn)上的話相對(duì)來說比較簡單。因?yàn)樗且粋€(gè)專用的結(jié)構(gòu),只考慮一些固定的使用場景。我們額外添加了一個(gè) Atomic Queue 用來存 cache job,使用 fetch and add array 來存具體的所需的 job data。下圖右側(cè)就是 worker 執(zhí)行 cache job 的流程示意圖。
![]()
Neon Intrinsics
Burst 會(huì)針對(duì)不同的平臺(tái)生成高性能的 simd code。在 Burst Inspector 中可以非常方便地查看。經(jīng)過檢查 Burst Inspector 和實(shí)機(jī)測(cè)試,在某些場合下也可以通過手寫 Arm Neon Intrinsics 來進(jìn)一步提升性能。
![]()
這里給出兩個(gè)比較常使用的例子:一是,F(xiàn)loat4 的點(diǎn)乘。對(duì)于點(diǎn)乘,我這里列出了 3 種方式,使用 neon intrinsics 相比于 mathematics 在測(cè)試用例中可以獲得相當(dāng)大的性能提升。如果目標(biāo)機(jī)型支持 armv8.2 的話,可以使用新增的規(guī)約加法指令,來進(jìn)一步地提升性能。一般來說,市場上大部分的流行機(jī)型,如果是重度游戲,都會(huì)支持 ArmV8.2 的。但是在我實(shí)際的測(cè)試當(dāng)中,最新的 Burst 實(shí)際上可以直接生成 ArmV9.0 的指令。但是在當(dāng)前,你想要使用一定要去手寫它。不過它帶來的性能提升,其實(shí)還是比較明顯的。特別是你去做一些展開,利用指令級(jí)可以得到更高的性能提升。
二是 float4×4 的轉(zhuǎn)置。對(duì)于轉(zhuǎn)置計(jì)算,可以看到 mathematics 生成的 assembly code 看起來性能是非常低的,通過手寫 neon intrinsics 就可以得到一個(gè)巨大的性能提升。如果只是純粹的需要轉(zhuǎn)置,可以直接使用交錯(cuò)讀,這樣可以得到更快的性能。這里這樣實(shí)現(xiàn)因?yàn)樵谝话愕膶?shí)際使用中,是通過對(duì) 4 個(gè) float4 轉(zhuǎn)置來將點(diǎn)乘變成矢量乘。但是在實(shí)際的使用當(dāng)中,因?yàn)?mathematics 的代碼一般被內(nèi)聯(lián)到具體的上下文當(dāng)中,最終生成的 assembly code 相對(duì)于你自己的單元測(cè)試差別會(huì)非常大。所以在具體優(yōu)化時(shí)還需要根據(jù)代碼的上下文進(jìn)行具體的優(yōu)化,可以結(jié)合 burst inspector、真機(jī)測(cè)試和 Arm 的優(yōu)化手冊(cè),來具體針對(duì)你的項(xiàng)目做出一些性能之間的比較和修改。
碰撞檢測(cè)模塊
首先要回答一個(gè)問題,就是 Unity 已經(jīng)有基于 physx 的一套非常成熟的物理模塊了,我們?yōu)槭裁匆撾x Unity 成熟的物理模塊重新開發(fā)呢?
因?yàn)椤稇倥c深空》有相當(dāng)多不同種類的玩法,玩法間的 layer 設(shè)置相對(duì)獨(dú)立,有些模塊比如戰(zhàn)斗,希望需要有特殊的 Trigger 觸發(fā)和退出機(jī)制,并且希望在底層就能夠支持,對(duì)于執(zhí)行流程也希望有更靈活的控制。我們?cè)谛阅芴剿魃希灿幸恍┳约旱南敕ā>褪钦f在僅需要碰撞測(cè)試的情況下,我們利用 DOTS 能否提升它的性能?
在《戀與深空》的實(shí)現(xiàn)當(dāng)中,我們的碰撞檢測(cè)模塊基本實(shí)現(xiàn)了 Unity 原生的所有碰撞查詢功能,針對(duì)戰(zhàn)斗和其他模塊我們定制了 Update 和 Trigger 相關(guān)邏輯。并且對(duì)于查詢接口,在底層就保證了線程安全,讓上層可以無負(fù)擔(dān)調(diào)用。結(jié)合 DOTS 進(jìn)行輕量化的實(shí)現(xiàn),以及結(jié)合一些實(shí)際需求的優(yōu)化,在性能測(cè)試當(dāng)中我們最高獲得了 15% 的性能提升。下方視頻是戰(zhàn)斗當(dāng)中的一些錄屏,整個(gè)角色的移動(dòng)、技能命中,都是用我們自己的碰撞檢測(cè)模塊來實(shí)現(xiàn)的。
查詢流程示例。下圖是一個(gè)簡單的示例。由于真機(jī)上我們實(shí)際的線程數(shù)量是固定的為 4,所以對(duì)于 memory allocator 可以預(yù)先按照線程數(shù)量分配好,在分配時(shí)可以直接根據(jù)當(dāng)前線程索引來獲取所需的內(nèi)存。我們使用基于 SAH 的 dynamic bvh 作為 broadphase 加速結(jié)構(gòu),在插入、刪除以及超出范圍的移動(dòng)時(shí),對(duì)當(dāng)前操作節(jié)點(diǎn)的鄰近的幾個(gè)層級(jí)節(jié)點(diǎn)進(jìn)行旋轉(zhuǎn)平衡。因?yàn)榕鲎矙z測(cè)的功能目標(biāo)相對(duì)概括,對(duì)于精度要求沒有那么高,所以我們也適當(dāng)?shù)臓奚恍┚群喕艘恍┡鲎矙z測(cè)算法來提升性能。
![]()
為了滿足戰(zhàn)斗模塊的需要,我們還設(shè)計(jì)了特殊的 trigger 觸發(fā)邏輯,觸發(fā) Trigger的進(jìn)入和退出必須要成對(duì)出現(xiàn),可以看到下方的流程示意圖,在 A 觸發(fā) B 的函數(shù)中移除 B 后,會(huì)觸發(fā)所有和 B 存在 overlap 的 collider。這里是和 Unity 原生的不一樣的,原生的 Unity 中在 trigger 邏輯中刪除掉 B 是不會(huì)觸發(fā)其它碰撞體的 trigger的。最后,我們通過 History 計(jì)數(shù)來標(biāo)記 collider 的版本,解決戰(zhàn)斗中角色或碰撞體等道具的復(fù)用可能會(huì)導(dǎo)致的一些潛在問題。
![]()
我的分享結(jié)束了。謝謝大家!
Unity 官方微信
第一時(shí)間了解Unity引擎動(dòng)向,學(xué)習(xí)進(jìn)階開發(fā)技能
每一個(gè)“點(diǎn)贊”、“在看”,都是我們前進(jìn)的動(dòng)力
![]()
特別聲明:以上內(nèi)容(如有圖片或視頻亦包括在內(nèi))為自媒體平臺(tái)“網(wǎng)易號(hào)”用戶上傳并發(fā)布,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。
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.