繪制調(diào)用批處理
要在屏幕上繪制游戲?qū)ο?,引擎必須向圖形 API(例如 OpenGL 或 Direct3D)發(fā)出繪制調(diào)用。繪制調(diào)用通常為資源密集型操作,圖形 API 為每次繪制調(diào)用執(zhí)行大量工作,從而導(dǎo)致 CPU 端的性能開銷。此開銷的主要原因是繪制調(diào)用之間的狀態(tài)變化(例如切換到不同材質(zhì)),而這種情況會導(dǎo)致圖形驅(qū)動程序中執(zhí)行資源密集型驗證和轉(zhuǎn)換步驟。
Unity 使用兩種方法來應(yīng)對此情況:
__動態(tài)批處理__:對于足夠小的網(wǎng)格,此方法會在 CPU 上轉(zhuǎn)換網(wǎng)格的頂點(diǎn),將許多相似頂點(diǎn)組合在一起,并一次性繪制它們。
__靜態(tài)批處理__:將靜態(tài)(不移動)游戲?qū)ο蠼M合成大網(wǎng)格,并以較快的速度渲染它們。
與手動合并游戲?qū)ο笙啾?,?nèi)置批處理有幾個好處;最值得注意的是,仍然可以單獨(dú)剔除游戲?qū)ο?。但是,也有一些缺點(diǎn);靜態(tài)批處理會導(dǎo)致內(nèi)存和存儲開銷,動態(tài)批處理會產(chǎn)生一些 CPU 開銷。
批處理的材質(zhì)設(shè)置
只有共享相同材質(zhì)的游戲?qū)ο蟛趴梢黄鸾邮芘幚怼R虼?,如果想要實現(xiàn)良好批處理,應(yīng)在盡可能多的不同游戲?qū)ο笾g共享材質(zhì)。
如果兩種相同材質(zhì)僅在紋理上不同,可將這些紋理組合成單個大紋理。此過程通常稱為紋理鑲嵌(請參閱有關(guān)紋理圖集 (Texture atlases)的 Wikipedia 頁面以了解更多信息)。一旦紋理位于相同圖集中,即可使用單個材質(zhì)。
如果需要從腳本訪問共享材質(zhì)屬性,必須注意,修改?Renderer.material?將創(chuàng)建該材質(zhì)的副本。應(yīng)改用?Renderer.sharedMaterial?來保留共享的材質(zhì)。
陰影投射物即使材質(zhì)不同,通常也可以在渲染時接受批處理。Unity 中的陰影投射物即使具有不同材質(zhì)也可以使用動態(tài)批處理,只要陰影 pass 所需材質(zhì)中的值相同即可。例如,許多板條箱可能使用具有不同紋理的材質(zhì),但是由于渲染紋理的陰影投射物不相關(guān),所以在此情況下,它們可以一起接受批處理。
Dynamic batching (Meshes)
如果移動的游戲?qū)ο蠊蚕硐嗤馁|(zhì)并滿足其他條件,則 Unity 可自動在同一繪制調(diào)用中批處理這些游戲?qū)ο蟆討B(tài)批處理是自動完成的,無需您進(jìn)行任何額外工作。
批處理動態(tài)GameObjects的每個頂點(diǎn)具有一定的開銷,因此批處理僅應(yīng)用于包含不超過900個頂點(diǎn)屬性和不超過300個頂點(diǎn)的網(wǎng)格。
如果著色器使用頂點(diǎn)位置、法線和單個 UV,最多可以批處理 300 個頂點(diǎn),而如果著色器使用頂點(diǎn)位置、法線、UV0、UV1 和切線,則只能批處理 180 個頂點(diǎn)。
__注意__:將來可能會更改屬性數(shù)量限制。
如果游戲?qū)ο笤谧儞Q中包含鏡像,則不會對這些對象進(jìn)行批處理(例如,具有 +1 縮放的游戲?qū)ο?A 和具有 –1 縮放的游戲?qū)ο?B 無法一起接受批處理)。
即使游戲?qū)ο蠡鞠嗤褂貌煌牟馁|(zhì)實例也會導(dǎo)致游戲?qū)ο蟛荒芤黄鸾邮芘幚?。例外情況是陰影投射物渲染。
帶有光照貼圖的游戲?qū)ο缶哂衅渌秩酒鲄?shù):光照貼圖索引和光照貼圖偏移/縮放。通常,動態(tài)光照貼圖的游戲?qū)ο髴?yīng)指向要批處理的完全相同的光照貼圖位置。
多 pass 著色器會中斷批處理。
幾乎所有的 Unity 著色器都支持前向渲染中的多個光照,有效地為它們執(zhí)行額外 pass。“其他每像素光照”的繪制調(diào)用不進(jìn)行批處理。
舊版延遲(光照 pre-pass)渲染路徑會禁用動態(tài)批處理,因為它必須繪制兩次游戲?qū)ο蟆?/p>
動態(tài)批處理的工作原理是將所有GameObject(游戲物體)的頂點(diǎn)轉(zhuǎn)換為CPU上的world space(世界空間),因此只有當(dāng)工作小于draw call時才有優(yōu)勢。draw調(diào)用的資源需求取決于許多因素,主要是使用的圖形API。例如,在控制臺或像Apple Metal這樣的現(xiàn)代api上,draw call開銷通常要低得多,而且動態(tài)批處理通常根本就不是優(yōu)勢。
動態(tài)批處理(例子系統(tǒng),線性渲染,拖尾渲染)
對于具有Unity動態(tài)生成的幾何圖形的組件,動態(tài)批處理的工作方式與它在網(wǎng)格中的工作方式不同。
對于每種兼容的渲染器類型,Unity會將所有可批處理的內(nèi)容構(gòu)建到1個大的Vertex Buffer中。
渲染器設(shè)置批次的“材質(zhì)”狀態(tài)。
Unity將頂點(diǎn)緩沖區(qū)綁定到圖形設(shè)備。
對于批處理中的每個渲染器,Unity將偏移量更新到“頂點(diǎn)緩沖區(qū)”中,然后提交一個新的繪制調(diào)用。
在測量“圖形設(shè)備”調(diào)用的成本時,渲染“組件”的最慢部分是“材質(zhì)”狀態(tài)的設(shè)置。 相比之下,以不同的偏移量將繪制調(diào)用提交到共享的頂點(diǎn)緩沖區(qū)中非??臁?/p>
這種方法與使用靜態(tài)批處理時Unity如何提交繪圖調(diào)用非常相似。
靜態(tài)批處理
使用靜態(tài)批處理,引擎可減少任何大小的幾何體的繪制調(diào)用,但前提是它共享相同材質(zhì)并且不移動。這種處理方式通常比動態(tài)批處理更高效(它不會在 CPU 上轉(zhuǎn)換頂點(diǎn)),但是使用更多內(nèi)存。
為了利用靜態(tài)批處理,您需要顯式指定某些游戲?qū)ο笫庆o態(tài)對象且不會在游戲中移動、旋轉(zhuǎn)或縮放。為此,請使用 Inspector 中的?Static?復(fù)選框,將游戲?qū)ο髽?biāo)記為靜態(tài):

使用靜態(tài)批處理需要額外的內(nèi)存來存儲組合的幾何體。如果多個游戲?qū)ο笤陟o態(tài)批處理之前共享相同幾何體,則會在 Editor 中或運(yùn)行時為每個游戲?qū)ο髣?chuàng)建幾何體的副本。這可能并非總是好辦法;有時您必須避免為某些游戲?qū)ο筮M(jìn)行靜態(tài)批處理,這樣會犧牲渲染性能,但可保持較小的內(nèi)存占用量。例如,在茂密森林關(guān)卡中,將樹標(biāo)記為靜態(tài)可能會產(chǎn)生嚴(yán)重的內(nèi)存影響。
在內(nèi)部,靜態(tài)批處理的工作原理是將靜態(tài)游戲?qū)ο筠D(zhuǎn)換為世界空間,并為它們建立一個共享的頂點(diǎn)和索引緩沖區(qū)。如果你已經(jīng)啟用了優(yōu)化的網(wǎng)格數(shù)據(jù)(在播放器設(shè)置中),那么Unity會刪除任何頂點(diǎn)元素,這些頂點(diǎn)元素在創(chuàng)建頂點(diǎn)緩沖區(qū)時不會被任何著色器變量使用。有一些特殊的關(guān)鍵字檢查來執(zhí)行這個;例如,如果Unity沒有檢測到LIGHTMAP_ON關(guān)鍵字,它將從批處理中刪除lightmap uv。然后,對于同一批中的可見游戲?qū)ο?,Unity執(zhí)行一系列簡單的draw調(diào)用,每個調(diào)用之間幾乎沒有狀態(tài)變化。從技術(shù)上講,Unity并不保存API draw調(diào)用,而是保存它們之間的狀態(tài)變化(這是資源密集型部分)。批處理限制在大多數(shù)平臺上是64k個頂點(diǎn)和64k個索引(OpenGLES上是48k個索引,macOS上是32k個索引)。
提示
當(dāng)前,僅對網(wǎng)格渲染器、軌跡渲染器、線渲染器、粒子系統(tǒng)和精靈渲染器進(jìn)行批處理。這意味著不會對蒙皮網(wǎng)格、布料和其他類型的渲染組件進(jìn)行批處理。
渲染器僅與其他相同類型的渲染器一起接受批處理。
半透明著色器通常要求游戲?qū)ο蟀凑諒暮蟮角暗捻樞蜻M(jìn)行渲染,從而實現(xiàn)透明性。Unity 首先按此順序?qū)τ螒驅(qū)ο笈判颍缓髧L試對它們進(jìn)行批處理,但是因為必須嚴(yán)格滿足順序,所以這通常意味著可以實現(xiàn)比不透明游戲?qū)ο蟾俚呐幚怼?/p>
手動組合彼此接近的游戲?qū)ο罂梢允抢L制調(diào)用批處理的極好替代方法。例如,一個帶有大量抽屜的靜態(tài)櫥柜通常只需在 3D 建模應(yīng)用程序中或者使用?Mesh.CombineMeshes?來組合成一個網(wǎng)格。