2017年8月7日 星期一

最佳實踐 - Unity碰撞效能優化

作者:William Armstrong 原文
潤稿:Kelvin Lo

Unity有一個名為Spotlight的團隊,有一群優秀的Unity開發人員一起研究並試著不斷的打破Unity的極限。針對各種複雜圖形效能和設計問題,我們不斷嘗試各種新的解決方案。

這個系列的文章會探討我們在和客戶合作時遇到的一些常見的問題。這些都是我們的合作團隊辛苦得出的經驗和教訓,我們很榮幸能夠和大家分享這些智慧。

這些問題很多只會出現在發佈到主機遊戲、手機遊戲、或者處理大量遊戲內容時才會出現。如果能在開發早期就將這些問題考慮進去,那麼開發過程就會更輕鬆,而遊戲也會做得更好。

較大的問題

有時候我們追蹤某個物理效能問題時,會歸咎到某一個問題的物件資源或設定上。常常查看Profiler並和前次執行結果比對,是發現這些問題的最佳方式。如果能儘早發現效能的減退,就可以透過查看最近所做的變更找到問題所在。

雖然運算單一簡單的物理
節(physics joint)速度很快,但背後的運算卻很複雜。本質上一個關節就是一套由剛體的位置、速度、加速度、旋轉等資訊組成的方程式。如果建立了一個帶有許多不同剛體的物件,而這些剛體又各自包含了許多會互相碰撞的關節。為了不讓這些關節不會互相穿透,要滿足這些關節的碰撞運算代價會非常大。

所以設計這種複雜關節時,要慎重考慮所需關節數量,碰撞類型以及必要的剛體數量。你可以用圖層(Layer)來過濾不必要的碰撞,也要謹慎評估哪些關節要打開"Allow Collision"來計算碰撞。透過限制關節的活動範圍可以減少碰撞檢測的量。或調整關節使它和其他關節不會發生碰撞。透過將關節和剛體作為插值方法的控制點來減少它們的使用數量。

使用Profiler可以看到某個特定時間啟動的剛體總數。觀察Rigidbody count的值
,尤其是當剛體互相接近時,會對效能產生很大的影響。在執行時放置或產生物件時,這個數字很容易會膨脹到超出預期。這就好比一罐汽水罐的碰撞很容易做,但如果罐子做成Prefab疊成超市裡的飲料塔,碰撞計算就很容易出大問題。


採用MeshCollider要非常小心,通常為了方便,開發者很容易會拿模型網格直接作為碰撞網格,但這可能會引起嚴重的效能下降,而且還不易察覺。PhsyX你給他甚麼他就算甚麼,所以如果一個小物體身上有高模或不合身的碰撞體,這時可能還好,但是如果將MeshCollider放大到容易被RayCast偵測運算的環境中,你會發現效能會驟然下降。如果非要用MeshCollider,建議是對於所有可能會有碰撞運算的物體,另外製作一組低模的碰撞網格。如果網格有問題又沒時間製作自訂網格,可以將MeshCollider設定裡的Convex打勾,並調整SkinWidth來取得一個比較適合的低面碰撞網格。

擁有多個重疊碰撞器的影子戰術NPC

較小的問題

通常開發者都會明智的避開不做大或慢的事情。但很多常見的情況是,專案所做的一系列理性決策,每個都一點一滴無形的影響了PhysX的效能。尤其是製作大規模遊戲時很容易出現這種狀況。例如你用5個方塊做關卡AI測試時表現很OK。但相同的AI放到實際的關卡時,大量的Physics.Update運算造成效能不好,而你卻不知道問題在哪? 


"明明測試都很OK的,為甚麼上正式版就壞了?" -- by 工程師


你有做足夠的測試了嗎?
可以從ProjectSettings->Physics裡找到碰撞圖層過濾(Layer Collision Matrix)來檢查是否存在不必要的碰撞規劃。透過打勾/取消打勾那些方塊,可以控制碰撞體之間的碰撞行為。這個功能會在碰撞發生前先進行計算,進而避開不必要的碰撞計算。許多遊戲使用”Is Trigger”為true的大型碰撞器來檢測角色或其他物件,這通常被稱為觸發器。通常這些觸發器會設定和預設圖層或所有物件發生碰撞計算。透過將角色放到特定層,並將這些觸發器放到某個只會與角色層發生碰撞計算的層,可以避免大體積碰撞器與複雜的世界網格或地形網格發生碰撞計算。

觸發器(Trigger)是否是使所有物件慢下來的原因?
當碰撞器屬性的“Is Trigger”設為true,它依然是一個需要進行碰撞計算的碰撞器。因此移動一個觸發器和移動一個碰撞器所產生的消耗都是一樣的,會需要做很多工作來發送碰撞和重疊事件。如果要在每幀都移動觸發器,確保它只對必須的物件進行碰撞計算。盡可能將觸發器做小,並將相似或重疊的觸發器分組。

我們常見到專案在NPC上使用多個大型觸發器用來檢查是否有互動。NPC的每種類型的遊戲物件都會有自己的碰撞器,以及一堆放在OnCollision中的程式,用來找到正確的互動物件。通常將多個觸發器合併一起會比較快,然後在OnCollision函式中用Tag或Layer或距離來進行過濾。很多情況下可以透過完全繞過碰撞計算來獲得更好的效能。而不必每幀都對球體與世界的碰撞進行計算,而是將所有希望存取的物件註冊到一個共用管理器,然後讓NPC對所有已註冊物件做簡單的距離計算。如果有一小堆需要檢測的潛在目標。這種方法要比對世界中所有的碰撞做檢測效能要高的多。

物件在Hierarchy裡的結構會導致PhysX做了額外的工作嗎?
在《ReCore》這個遊戲裡,我們發現場景中圓形旋轉平臺上的每一個平台都擁有自己的剛體。這導致了每個平台都會與其他平台發生碰撞檢測。將所有的平臺歸於同一父物件之下,將剛體賦予父物件,讓父物件進行旋轉,在Physics.Update中節省了大量的時間。
注意:將一個共用剛體下的碰撞器合併會增加Raycast對它或形狀投射檢測的開銷。

從另一個方面來說,如果一個共用的剛體父物件下已經有幾個碰撞器,你需要非常小心,不要讓它們做相對於父物件的移動。因為任何時候剛體改變形狀,會造成中心的質量和慣性張量(Inertial Tensor)
都必須重新計算,這需要大量的時間。當多個剛體連接到動畫角色的四肢時,常常會出現這種問題。可以通過設定自己的質心來關閉它。只要遊戲物件的形狀不要變化太大就沒問題。

這堆小小的系統問題累積起來是很可怕的,所以在開發中要時刻牢記,並在一開始就做好計畫規避這些問題。如果突然發現Physics時間出現一個巨大峰值,那就分析下當下的修改有哪些並思考對應方式。

最後感謝Mimimi Productions和Armature Studio 讓我們用他們的遊戲作為例子。我們還會為大家分享更多最佳實踐系列文章。

關於我自己

我的相片
Unity台灣官方部落格 請上Facebook搜尋Unity Taiwan取得Unity中文的最新資訊