(二)unity中的渲染優化技術——————(減少draw call數目 方法二:靜態批處理、共享材質、批處理的選擇)

一、靜態批處理

相比於動態批處理來說,靜態批處理適用於任何大小的幾何模型。它的實現原理是,只在運行開始階段,把需要進行靜態批處理的模型合併到一個新的網格結構中,這意味着這些模型不可以在運行時刻被移動。但由於它只需要進行一次合併操作,因此比動態批處理更加高效。靜態批處理的缺點在於,它往往需要佔用更多的內存來存儲合併後的幾何結構,這是因爲,如果在靜態批處理前一些物體共享了相同的網格,那麼在內存中每一個物體都會對應一個該網格的複製品,即一個網格會變成多個網格再發送給GPU。如果這類使用同一網格的對象很多,那麼這就會成爲一個性能瓶頸了。例如一個使用了1000個相同樹模型的森林中使用靜態批處理,那麼就會多使用1000倍的內存,這會造成嚴重的內存影響。這種時候,解決方法要麼忍受這種犧牲內存換取性能的方法,要麼不要使用靜態批處理,而使用動態批處理技術(但要小心控制模型的頂點屬性數目),或者自己編寫批處理的方法。

現在做個實驗,我採用了靜態批處理:

在playerSetting->Other Settings中勾選Static Batching,然後運行項目查看profiler:

找到Memory->Scene Memory->Mesh可以看到有幾十M的合併網格。然後我們取消勾選,不採用靜態批處理:

Mesh的內存就減少了非常多,合併網格都不見了。 

下面我們在做一個簡單實驗,我們給出一個測試靜態批處理的場景。場景中包含了3個水壺模型,他們使用的同一個材質,同時還包含了一個使用不同材質的立方體。場景中還包含了平行光,關閉了陰影效果,以避免陰影計算對批處理數目的影響。在運行前,渲染統計數據如下圖:

從上圖看出,儘管3個水壺模型使用了相同的材質,但它們仍然沒有被動態批處理,因爲模型包含的頂點數目是393:

而它們使用的shader需要使用4個頂點屬性(頂點位置、法線方向、切線方向和紋理座標),超過了動態批處理中限定的900限制,此時想要減少draw call就需要使用靜態批處理。

	struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
			};

靜態批處理的實現非常簡單。只需要確保playerSetting->Other Settings中勾選Static Batching,然後把物體面板上的Static複選框勾選上即可(實際上只需要勾選Batching Static即可),如下圖:

下面運行程序後,如圖:

我們發現批處理數目變成了2,而Save by batching數目也顯示爲2。此時如果我們在運行時查看每個模型使用的網格,會發現他們都變成了一個名爲Combine Mesh(roo:scene)的東西:

這個網格是unity合併了所有被標識爲“Static”的物體的結果,在這個例子中就是場景中的4個模型,但是4個對象明明不是都使用了一個材質,爲什麼可以合併成一個?我們發現右下方標明瞭“4 submeshes”,也就是說這個合併後的網格其實包含了4個子網格,即場景中4個對象,對於合併後的網格,unity會判斷其中使用同一個材質的子網格,然後對它們進行批處理。

在內部實現上,unity首先把這些靜態物體變換到世界空間下,然後爲他們構建一個更大的頂點和索引緩存。對於使用了同一材質的物體,unity只需要調用一個draw call就可以繪製全部物體而對於使用了不同材質的物體,靜態批處理同樣可以提升渲染性能。儘管這些物體仍然需要調用多個draw call,但靜態批處理可以減少這些draw call之間的狀態切換,而這些切換往往是費時的操作。從合併後的網格結構中發現,儘管3個水壺模型使用了同一個網格,但合併後卻變成了3個獨立網格。而且我們可以從unity的profiler觀察到在應用靜態批處理前後VBO total(Vertex Buffer Object,頂點緩衝對象)的變化,數目會變大,這正是因爲靜態批處理會佔用更多內存的緣故。

如果場景中包含了除平行光以外的其他光源,並且在shader中定義了額外的Pass來處理它們,這些額外的Pass部分是不會被批處理的。但是處理平行光的Base Pass部分仍然會被靜態批處理,因此仍然可以節省兩個draw call

二、共享材質

無論動態批處理來說靜態批處理,都要求模型之間需要共享同一個材質。但不同模型之間需要由不同的渲染屬性,例如使用不同的紋理、顏色等。這時我們需要一些策略來儘可能地合併材質。

如果兩個材質之間只有使用的紋理不同,我們可以把這些紋理合併到一張更大的紋理中,這張更大的紋理被稱爲是一張圖集。一旦使用了同一張紋理,我們就可以使用同一個材質,再使用不同的採樣座標對紋理採樣即可。

但有時除了紋理不同外,不同的物體在材質上還有一些微小的參數變化,例如顏色不同、某些浮點屬性不同。但是不管是動態批處理還是靜態批處理,它們的前提都是要使用同一個材質,也就是說它們指向的材質必須是同一個實體。這意味着只要調整了參數,就會影響到所有使用這個材質的對象。那麼想要微小的調整怎麼辦?一種常用的方法就是使用網格的頂點數據(最常見的就是頂點顏色數據)來存儲這些參數。

前面說過,經過批處理後的物體會被處理成更大的VBO發送給GPU,VBO中的數據可以作爲輸入傳遞給頂點着色器,因此我們可以巧妙地對VBO中的數據進行控制,從而達到不同效果的目的。一個例子是,森林場景中所有的樹都使用了同一種材質,我們希望它們可以通過批處理來減少draw call,但不同的樹的顏色可能不同,這時我們可以利用網格的頂點的顏色數據來調整。

需要注意,如果我們需要在腳本中訪問共享材質,應該使用Renderer.sharedMaterial來保證修改的是和其他物體共享的材質,但這意味着修改會應用到所有使用該材質的物體上。另一個類似的API是Renderer.material,如果使用Renderer.material來修改材質,unity會創建一個該材質的複製品,從而破壞批處理在該物體上的應用,這可能並不是我們希望看到的。

三、批處理的注意事項

在選擇使用動態批處理還是靜態批處理時,有一些小建議:

1.儘可能選擇靜態批處理,但得時刻小心對內存的消耗,並且記住經過靜態批處理的物體不可以再被移動。

2.如果無法進行靜態批處理,而要使用動態批處理的話,小心上面提到的各種條件限制。例如儘可能讓這樣的物體少並且儘可能讓這些物體包含少量的頂點屬性和頂點數目。

3.對於遊戲中的小道具,例如可以撿拾的金幣等,可以使用動態批處理。

4.對於包含動畫的這類物體,我們無法全部使用靜態批處理,但其中如果有不動的部分,可以把這部分標識成“static”。

還有一些需要注意的地方。由於批處理需要把多個模型變換到世界空間下再合併它們,因此如果shader中存在一些基於模型空間下的座標運算,那麼往往會得到錯誤的結果。一種解決方法是,在shader中使用DisableBatching標籤來強制使用該shader的材質不會被批處理。另一個注意事項是使用半透明材質的物體通常需要使用嚴格的從後往前的繪製順序來保證透明混合的正確性。對於這些物體,unity會首先保證它們的繪製順序,再嘗試對它們進行批處理。這意味着,當繪製順序無法滿足時,批處理無法在這些物體上被成功應用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章