【Unity技巧】Unity中的優化技術
寫在前面
這一篇是在Digital Tutors的一個系列教程的基礎上總結擴展而得的~Digital Tutors是一個非常棒的教程網站,包含了多媒體領域很多方面的資料,非常酷!除此之外,還參考了Unity Cookie中的一個教程。還有很多其他參考在下面的鏈接中。
這篇文章旨在簡要地說明一下常見的各種優化策略。不過對每個基礎有非常深入地講解,需要的童鞋可以自行去相關資料。
還有一些我認為非常好的參考文章: Performance Optimization for Mobile Devices 4 Ways To Increase Performance of your Unity Game Unite 2013 Optimizing Unity Games for Mobile Platforms Unity optimization Tips
影響性能的因素
首先,我們得了解,影響遊戲性能的因素哪些,才能對癥下藥。對於一個遊戲來說,有兩種主要的計算資源:CPU和GPU。它們會互相合作,來讓我們的遊戲可以在預期的幀率和分辨率下工作。CPU負責其中的幀率,GPU主要負責分辨率相關的一些東西。
總結起來,主要的性能瓶頸在於:
- CPU
- 過多的Draw Calls
- 復雜的腳本或者物理模擬
- 頂點處理
- 過多的頂點
- 過多的逐頂點計算
- 像素(Fragment)處理
- 過多的fragment,overdraws
- 過多的逐像素計算
- 帶寬
- 尺寸很大且未壓縮的紋理
- 分辨率過高的framebuffer
對於CPU來說,限制它的主要是遊戲中的Draw Calls。那麽什麽是Draw Call呢?如果你學過OpenGL,那麽你一定還記得在每次繪圖前,我們都需要先準備好頂點數據(位置、法線、顏色、紋理坐標等),然後調用一系列API把它們放到GPU可以訪問到的指定位置,最後,我們需要調用_glDraw*命令,來告訴GPU,“嘿,我把東西都準備好了,你個懶家夥趕緊出來幹活(渲染)吧!”。而調用_glDraw*命令的時候,就是一次Draw Call。那麽為什麽Draw Call會成為性能瓶頸呢(而且是CPU的瓶頸)?上面說到過,我們想要繪制圖像時,就一定需要調用Draw Call。例如,一個場景裏有水有樹,我們渲染水的時候使用的是一個material以及一個shader,但渲染樹的時候就需要一個完全不同的material和shader,那麽就需要CPU重新準備頂點數據、重新設置shader,而這種工作實際是非常耗時的。如果場景中,每一個物體都使用不同的material、不同的紋理,那麽就會產生太多Draw Call,影響幀率,遊戲性能就會下降。當然,這裏說得很簡單,更詳細的請自行谷歌。其他CPU的性能瓶頸還有物理、布料模擬、粒子模擬等,都是計算量很大的操作。
而對於GPU來說,它負責整個渲染流水線。它會從處理CPU傳遞過來的模型數據開始,進行Vertex Shader、Fragment Shader等一系列工作,最後輸出屏幕上的每個像素。因此它的性能瓶頸可能和需要處理的頂點數目的、屏幕分辨率、顯存等因素有關。總體包含了頂點和像素兩方面的性能瓶頸。在像素處理中,最常見的性能瓶頸之一是overdraw。Overdraw指的是,我們可能對屏幕上的像素繪制了多次。
了解了上面基本的內容後,下面涉及到的優化技術有:
- 頂點優化
- 優化幾何體
- 使用LOD(Level of detail)技術
- 使用遮擋剔除(Occlusion culling)技術
- 像素優化
- 控制繪制順序
- 警惕透明物體
- 減少實時光照
- CPU優化
- 減少Draw Calls
- 帶寬優化
- 減少紋理大小
- 利用縮放
優化幾何體
這一步主要是為了針對性能瓶頸中的”頂點處理“一項。這裏的幾何體就是指組成場景中對象的網格結構。 3D遊戲制作都由模型制作開始。而在建模時,有一條我們需要記住:盡可能減少模型中三角形的數目,一些對於模型沒有影響、或是肉眼非常難察覺到區別的頂點都要盡可能去掉。例如在下面左圖中,正方體內部很多頂點都是不需要的,而把這個模型導入到Unity裏就會是右面的情景:使用LOD(Level of detail)技術
LOD技術有點類似於Mipmap技術,不同的是,LOD是對模型建立了一個模型金字塔,根據攝像機距離對象的遠近,選擇使用不同精度的模型。它的好處是可以在適當的時候大量減少需要繪制的頂點數目。它的缺點同樣是需要占用更多的內存,而且如果沒有調整好距離的話,可能會造成模擬的突變。 在Unity中,可以通過LOD Group來實現LOD技術:使用遮擋剔除(Occlusion culling)技術
遮擋剔除是用來消除躲在其他物件後面看不到的物件,這代表資源不會浪費在計算那些看不到的頂點上,進而提升性能。關於遮擋剔除,Unity Taiwan有一個系列文章大家可以看看(需FQ): Unity 4.3 關於Occlusion Culling : 基本篇 Unity 4.3 關於Occlusion Culling : 最佳做法 Unity 4.3 關於Occlusion Culling : 錯誤診斷 具體的內容大家可以自行查找。 現在我們來談像素優化。 像素優化 像素優化的重點在於減少overdraw。之前提過,overdraw指的就是一個像素被繪制了多次。關鍵在於控制繪制順序。 Unity還提供了查看overdraw的視圖,在Scene視圖的Render Mode->Overdraw。當然這裏的視圖只是提供了查看物體遮擋的層數關系,並不是真正的最終屏幕繪制的overdraw。也就是說,可以理解為它顯示的是如果沒有使用任何深度檢驗時的overdraw。這種視圖是通過把所有對象都渲染成一個透明的輪廓,通過查看透明顏色的累計程度,來判斷物體的遮擋。控制繪制順序
需要控制繪制順序,主要原因是為了最大限度的避免overdraws,也就是同一個位置的像素可以需要被繪制多變。在PC上,資源無限,為了得到最準確的渲染結果,繪制順序可能是從後往前繪制不透明物體,然後再繪制透明物體進行混合。但在移動平臺上,這種會造成大量overdraw的方式顯然是不適合的,我們應該盡量從前往後繪制。從前往後繪制之所以可以減少overdraw,都是因為深度檢驗的功勞。 在Unity中,那些Shader中被設置為“Geometry” 隊列的對象總是從前往後繪制的,而其他固定隊列(如“Transparent”“Overla”等)的物體,則都是從後往前繪制的。這意味這,我們可以盡量把物體的隊列設置為“Geometry” 。 而且,我們還可以充分利用Unity的隊列來控制繪制順序。例如,對於天空盒子來說,它幾乎覆蓋了所有的像素,而且我們知道它永遠會在所有物體的後面,因此它的隊列可以設置為“Geometry+1”。這樣,就可以保證不會因為它而造成overdraws。時刻警惕透明物體
而對於透明對象,由於它本身的特性(可以看之前關於Alpha Test和Alpha Blending的一篇文章)決定如果要得到正確的渲染效果,就必須從後往前渲染(這裏不討論使用深度的方法),而且拋棄了深度檢驗。這意味著,透明物體幾乎一定會造成overdraws。如果我們不註意這一點,在一些機器上可能會造成嚴重的性能下面。例如,對於GUI對象來說,它們大多被設置成了半透明,如果屏幕中GUI占據的比例太多,而主攝像機又沒有進行調整而是投影整個屏幕,那麽GUI就會造成屏幕的大量overdraws。 因此,如果場景中大面積的透明對象,或者有很多層覆蓋的多層透明對象(即便它們每個的面積可以都不大),或者是透明的粒子效果,在移動設備上也會造成大量的overdraws。這是應該盡量避免的。 對於上述GUI的這種情況,我們可以盡量減少窗口中GUI所占的面積。如果實在無能為力,我們可以把GUI繪制和三維場景的繪制交給不同的攝像機,而其中負責三維場景的攝像機的視角範圍盡量不要和GUI重疊。對於其他情況,只能說,盡可能少用。當然這樣會對遊戲的美觀度產生一定影響,因此我們可以在代碼中對機器的性能進行判斷,例如首先關閉所有的耗費性能的功能,如果發現這個機器表現非常良好,再嘗試開啟一些特效功能。減少實時光照
實時光照對於移動平臺是個非常昂貴的操作。如果只有一個平行光還好,但如果場景中包含了太多光源並且使用了很多多Passes的shader,那麽很有可能會造成性能下降。而且在有些機器上,還要面臨shader失效的風險。例如,一個場景裏如果包含了三個逐像素的點光源,而且使用了逐像素的shader,那麽很有可能將Draw Calls提高了三倍,同時也會增加overdraws。這是因為,對於逐像素的光源來說,被這些光源照亮的物體要被再渲染一次。更糟糕的是,無論是動態批處理還是動態批處理(其實文檔中只提到了對動態批處理的影響,但不知道為什麽實驗結果對靜態批處理也沒有用),對於這種逐像素的pass都無法進行批處理,也就是說,它們會中斷批處理。 例如,下面的場景中,四個物體都被標識成了“Static”,它們使用的shader都是自帶的Bumped Diffuse。而所有的點光源都被標識成了“Important”,即是逐像素光。可以看到,運行後的Draw Calls是23,而非3。這是因為,只有“Forward Base”的Pass時發生了靜態批處理(這裏的動態批處理由於多Pass已經完全失效了),節省了一個Draw Calls,而後面的“Forward Add” Pass,每一次渲染都是一個單獨的Draw Call(而且可以看到Tris和Verts數目也增加了):使用Lightmaps
Lightmaps的很常見的一種優化策略。它主要用於場景中整體的光照效果。這種技術主要是提前把場景中的光照信息存儲在一張光照紋理中,然後在運行時刻只需要根據紋理采樣得到光照信息即可。 當然與之配合的還有Light Probes技術。風宇沖有一個系列文章講過,但是時間比較久遠,但教程我相信網上有很多。使用God Rays
場景中很多小型光源效果都是靠這種方法模擬的。它們一般並不是真的光源產生的,很多情況是通過透明紋理進行模擬。具體可以參見之前的文章。 CPU優化減少Draw Calls
批處理(Batching)
這方面的優化教程想必是最多的了。最常見的就是通過批處理(Batching)了。從名字上來理解,就是一塊處理多個物體的意思。那麽什麽樣的物體可以一起處理呢?答案就是使用同一個材質的物體。這是因此,對於使用同一個材質的物體,它們之間的不同僅僅在於頂點數據的差別,即使用的網格不同而已。我們可以把這些頂點數據合並在一起,再一起發送給GPU,就可以完成一次批處理。 Unity中有兩種批處理方式:一種是動態批處理,一種是靜態批處理。對於動態批處理來說,好消息是一切處理都是自動的,不需要我們自己做任何操作,而且物體是可以移動的,但壞消息是,限制很多,可能一不小心我們就會破壞了這種機制,導致Unity無法批處理一些使用了相同材質的物體。對於靜態批處理來說,好消息是自由度很高,限制很少,壞消息是可能會占用更多的內存,而且經過靜態批處理後的所有物體都不可以再移動了。 首先來說動態批處理。Unity進行動態批處理的條件是,物體使用同一個材質並且滿足一些特定條件。Unity總是在不知不覺中就為我們做了動態批處理。例如下面的場景:- 頂點屬性的最大限制為900,而且未來有可能會變。不要依賴這個數據。
- 一般來說,那麽所有對象都必須需要使用同一個縮放尺度(可以是(1, 1, 1)、(1, 2, 3)、(1.5, 1.4, 1.3)等等,但必須都一樣)。但如果是非統一縮放(即每個維度的縮放尺度不一樣,例如(1, 2, 1)),那麽如果所有的物體都使用不同的非統一縮放也是可以批處理的。這個要求很怪異,為什麽批處理會和縮放有關呢?這和Unity背後的技術有關系,有興趣的可以自行谷歌,比如這裏。
- 使用lightmap的物體不會批處理。多passes的shader會中斷批處理。接受實時陰影的物體也不會批處理。
- 盡可能選擇靜態批處理,但得時刻小心對內存的消耗。
- 如果無法進行靜態批處理,而要使用動態批處理的話,那麽請小心上面提到的各種註意事項。例如:
- 盡可能讓這樣的物體少並且盡可能讓這些物體包含少量的頂點屬性。
- 不要使用統一縮放,或者都使用不同的非統一縮放。
- 對於遊戲中的小道具,例如可以撿拾的金幣等,可以使用動態批處理。
- 對於包含動畫的這類物體,我們無法全部使用靜態批處理,但其中如果有不動的部分,可以把這部分標識成“Static”。
合並紋理(Atlas)
雖然批處理是個很好的方式,但很容易就打破它的規定。例如,場景中的物體都使用Diffuse材質,但它們可能會使用不同的紋理。因此,盡可能把多張小紋理合並到一張大紋理(Atlas)中是一個好主意。利用網格的頂點數據
但有時,除了紋理不同外,還有對於不同的物體,它們在材質上還有一些微小的參數變化,例如顏色不同、某些浮點參數不同。但鐵定律是,不管是動態批處理還是靜態批處理,它們的前提都是要使用同一個材質。是同一個,而不是同一種,也就是說它們指向的材質必須是同一個實體。這意味著,只要我們調整了參數,就會影響到所有使用這個材質的對象。那麽想要微小的調整怎麽辦呢?由於Unity中的規定非常死,那麽我們只好想些“歪門邪道”,其中一種就是使用網格的頂點數據(最常見的就是頂點顏色數據)。 前面說過,經過批處理後的物體會被處理成一個VBO發送給GPU,VBO中的數據可以作為輸入傳遞給Vertex Shader,因此我們可以巧妙地對VBO中的數據進行控制,從而達到不同效果的目的。一個例子是,還是之前的森林,所有的樹使用了同一種材質,我們希望它們可以通過動態批處理來實現,但不同樹的顏色可能不同。這時我麽可以利用網格的頂點數據來調整。具體方法,可以參見後面會寫的一篇文章。 但這種方法的缺點就是會需要更多的內存來存儲這些用於調整參數用的頂點數據。沒辦法,永遠沒有絕對完美的方法。 帶寬優化減少紋理大小
之前提到過,使用Texture Atlas可以幫助減少Draw Calls,而這些紋理的大小同樣是一個需要考慮的問題。在這之前要提到一個問題就是,所有紋理的長寬比最好是正方形,而且長度值最好是2的整數冪。這是因為有很多優化策略只有在這種時候才可以發揮最大效用。 Unity中查看紋理參數可以通過紋理的面板:利用縮放
很多時候分辨率也是造成性能下降的原因,尤其是現在很多國內山寨機,除了分辨率高其他硬件簡直一塌糊塗,而這恰恰中了遊戲性能的兩個瓶頸:過大的屏幕分辨率+糟糕的GPU。因此,我們可能需要對於特定機器進行分辨率的放縮。當然,這樣會造成遊戲效果的下降,但性能和畫面之間永遠是個需要權衡的話題。 在Unity中設置屏幕分辨率可以直接調用Screen.SetResolution。實際使用中可能會遇到一些情況,雨松MOMO有一篇文章講了這種技術,可以去看看。寫在最後
這篇文章是總結性質的,因此對每種技術都沒有進行非常詳細的解釋。強烈建議大家閱讀文章開頭給出的各種鏈接,寫得都很好。 原文地址:http://blog.csdn.net/candycat1992/article/details/42127811【Unity技巧】Unity中的優化技術