Unity中的靜態批處理和動態批處理操作

前言

Unity在運行時可以將一些物體進行合並,從而用一個繪制調用來渲染他們。這一操作,我們稱之為“批處理”,能得到越好的渲染性能。

Unity中內建的批處理機制所達到的效果要明顯強於使用幾何建模工具的批處理效果,因為,Unity引擎的批處理操作是在物體的可視裁剪操作之後進行的,處理的幾何信息少很多。

材質

隻有擁有相同材質的物體才可以進行批處理,因此,你需在程序中盡可能多地復用材質。如果你的兩個材質僅僅是紋理不同,那麼你可通過紋理拼合來將這兩張紋理拼合成一張大的紋理,這樣,你就可以使用這個單一材質來替代之前的兩個材質瞭。

如果你要通過腳本來訪問復用材質屬性,那麼值得註意:改變Renderer.material將會造成一份材質的拷貝,因此,你應該使用Renderer.sharedMaterial來保證材質的共享狀態。

動態批處理

如果動態物體共用著相同的材質,那麼Unity會自動對這些物體進行批處理,動態批處理操作是自動完成的,並不需要你進行額外的操作。

Dynamic Batching啟用時,Unity將嘗試自動批量移動物體到一個Draw Call中。要使物體可以被動態批處理,它們應該共享相同的材質,但是還有一些其他約束條件:

批處理動態物體需要在每個頂點上進行一定的開銷,所以動態批處理僅支持小於900頂點的網格物體;如果你的著色器使用頂點位置,法線和UV值三種屬性,那麼你隻能批處理300頂點以下的物體;如果你的著色器需要使用頂點位置,法線,UV0,UV1和切向量,那你隻能批處理180頂點以下的物體。

盡量不要使用縮放尺度(scale)。分別擁有縮放尺度(1,1,1)和(2,2,2)的兩個物體將不會進行批處理;統一縮放尺度的物體不會與非統一縮放尺度的物體進行批處理。使用縮放尺度(1,1,1)和 (1,2,1)的兩個物體將不會進行批處理,但是使用縮放尺度(1,2,1)和(1,3,1)的兩個物體將可以進行批處理。

擁有光照貼圖的物體有其他渲染器參數,例如光照貼圖索引或光照貼圖的偏移與縮放。一般來說,動態光照貼圖的遊戲對象應該指向完全相同的光照貼圖的位置。

多通道(Pass)的shader會妨礙批處理操作。比如,幾乎unity中所有的著色器在前向渲染中都支持多個光源,並為它們有效地開辟多個通道,這就會要求額外的渲染次數,所以繪制 “額外的每像素燈”時不會被批處理。

靜態批處理

為瞭更好地使用靜態批處理,你需要明確指出哪些物體是靜止的,並且在遊戲中永遠不會移動、旋轉和縮放。想完成這一步,你隻需要在檢測器(Inspector)中將Static復選框打勾即可。隻要這些物體不移動,並且擁有相同的材質。因此,靜態批處理比動態批處理更加有效,你應該盡量低使用它,因為它需要更少的CPU開銷。

使用靜態批處理操作需要額外的內存開銷來儲存合並後的幾何數據。在靜態批處理之前,如果一些物體共用瞭同樣的幾何數據,那麼引擎會在編輯以及運行狀態對每個物體創建一個幾何數據的備份。這並不總是一個好的想法,因為有時候,將不得不犧牲一點渲染性能來防止一些物體的靜態批處理,從而保持較少的內存開銷。比如,將濃密森裡中樹設為Static,會導致嚴重的內存開銷。這就是空間和時間上的相愛相殺。

最後,如果場景自帶靜態物體,會合並批次,這個大傢都知道;如果靜態物體是場景加載後再讀取預制體動態加載進去的,就不會自動合並批次,需要你加載完後調一下手動合並批次的接口,StaticBatchingUtility.Combine 合並。

補充:在Unity3D中的渲染優化-批處理技術

在Unity3D中,常用的減少Draw call的優化技術就是批處理技術。批處理的原理是減少每一幀需要的Draw call數目。為瞭把一個對象渲染到屏幕上,CPU需要檢查哪些光源影響瞭該物體,綁定shader並設置它的參數,再把渲染命令發送給GPU。當場景中包含瞭大量的對象時,這些操作就會非常耗時。例如,如果我們需要渲染一千個三角形,把它們按一千個單獨的網格進行渲染所花費的時間要遠遠大於渲染一個包含一千個三角形的網格。在這兩種情況下,GPU的性能消耗其實並沒有多大的區別,但CPU的draw call數目就會成為性能瓶頸。因此,批處理的思想很簡單,就是在每次調用draw call時盡可能多地處理多個物體。

使用同一材質的物體可以進行批處理,因為對於使用同一材質的物體,它們之間的不同僅僅在於頂點數據的差別。我們可以把這些頂點數據合並在一起,再一起發送給GPU,這樣就可以完成一次批處理。

在Unity中支持兩種類型的批處理,一種是動態批處理,另一種是靜態批處理。對於動態批處理來說,優點是一切處理Unity自動完成的,不需要我們做任何操作,而且物體可以是移動的,缺點是限制有很多,可能一不小心就會破壞瞭這種機制,導致Unity無法動態批處理一些使用瞭相同材質的物體。而對於靜態批處理來說,它的優點是自由度很高,限制很少;但缺點是可能會占用更多的內存,而經過靜態批處理後的所有物體都不可以再移動瞭(即便在腳本中嘗試改變物體的位置也是無效的)。

Unity3D中的動態批處理技術

動態批處理的原理是,每一幀把可以進行批處理的模型網格進行合並,再把合並後的模型數據傳遞給GPU,然後使用同一個材質對其渲染。除瞭實現方便,動態批處理的另一個好處就是,經過批處理的物體仍然可以移動,這是由於在處理每幀時Unity都會重新合並一次網格。

雖然Unity的動態批處理不需要我們進行任何額外工作,但隻有滿足條件的模型和材質才可以被動態批處理。(需要註意的是,隨著Unity版本的變化,這些條件也有一些改變)這些條件限制是:

1.能夠進行動態批處理的網格的頂點屬性規模要小於900.例如,如果shader中需要使用頂點位置,法線和紋理坐標這三個頂點屬性,那麼要想讓模型能夠被動態批處理,它的頂點數目不能超過300.需要註意的是,這個數字在未來有可能會發生變化,因此不要依賴這個數據。

2.一般來說,所有對象都需要使用同一縮放尺度(可以是(1,1,1),(1,2,3),(1.5,1.4,1.3)等,但必須都一樣)。一個例外情況是,如果所有的物體都是用瞭不同的非統一縮放,那麼他們也是可以被動態批處理的。但在Unity5中,這種對模型縮放的限制已經不存在瞭。

3.對於使用光照貼圖紋理的物體需要小心處理。這些物體需要額外的渲染參數,例如,在光照貼圖紋理上的索引和偏移量以及縮放信息等因此,為瞭讓這些物體可以被動態批處理,我們需要保證它們指向光照貼圖紋理中的同一個位置。

4.有多個Pass通道的shader會中斷批處理。在前向渲染中,我們有時需要使用額外的Pass來為模型添加更多的光照效果,但這樣一來模型就不會被動態批處理瞭。

Unity3D中的靜態批處理技術

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

在Unity中使用靜態批處理的方法是,在場景中選中需要靜態批處理的物體,在其Inspector面板的右上角勾選上Batching static靜態屬性。在內部實現上,Unity首先把這些靜態物體變換到世界空間下,然後為它們構建一個更大的頂點和索引緩存,對於使用同一材質的物體。Unity隻需要調用一個drawcall就可以繪制全部物體。而對於使用不同材質的物體,靜態批處理同樣可以提升渲染性能。盡管這些物體仍然需要調用多個draw call,但靜態批處理可以減少這些draw call之間的狀態切換,而這些切換往往是費時的操作。

我們可以在Unity的分析器中觀察到應用靜態批處理前後VBO total(Vertex Buffer Object,頂點緩存對象)的變化。在一些物體共享瞭相同的網格的情況下,我們可以看到這些物體在使用瞭靜態批處理技術後,VBO total的數目變大瞭,這正是因為靜態批處理會占用更多內存的緣故。正如上面所講,靜態批處理需要占用更多的內存來存儲合並後的幾何結構,如果一些物體共享瞭相同的網格,那麼在內存中每個物體都會對應一個該網格的復制品。

如果場景中包含瞭除瞭平行光以外的其他光源,並且在Shader中定義瞭額外的Pass來處理它們,這些額外的Pass部分是不會被批處理的,但是處理平行光的Base Pass部分仍然會被靜態批處理。

Unity3D中使用共享材質

無論是動態批處理還是靜態批處理,都要求模型之間需要共享同一個材質。但不同的模型之間總會需要有不同的渲染屬性,例如,使用不同的紋理,顏色等。這時我們需要一些策略來盡可能的合並材質。

如果兩個材質之間隻有使用的紋理不同,我們可以把這些紋理合並到一張更大的紋理中,這張更大紋理被稱為是一張圖集(atlas)。一旦使用瞭同一紋理,我們就可以使用同一材質,再使用不同的采樣坐標對紋理采樣即可。

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

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

需要註意的是,如果我們需要在腳本中訪問共享材質,應該使用Renderer.sharedMaterial來保證修改的是和其他物體共享的材質,但這意味著修改會應用到所有使用該材質的物體上。另一個類似的API是Renderer.material,如果使用Renderer.material來修改材質,Unity會創建一個該材質的復制品,從而破壞批處理在該物體上的應用。

關於在Unity3D中使用批處理的註意事項:

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

2.如果無法進行靜態批處理,而要使用動態批處理的話,那麼盡可能減少物體的數目並且讓這些物體包含少量的頂點屬性和頂點數目。

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

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

5.由於批處理需要把模型變換到世界空間下再合並它們,因此,如果shader中存在一些基於模型空間下坐標的運算,那麼往往會得到錯誤的結果。一個解決方法是,在shader中使用DisableBatching標簽強制使該shader的材質不會被批處理。

6.使用半透明材質的物體通常需要使用嚴格的從後往前的繪制順序來保證透明混合的正確性。這意味著,當繪制順序無法滿足時,批處理無法在這些物體上被成功地應用。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。如有錯誤或未考慮完全的地方,望不吝賜教。

推薦閱讀:

    None Found