1. 程式人生 > >Draw Call 理解和優化

Draw Call 理解和優化

draw call是openGL的描繪次數(directX沒怎麼研究,但原理應該差不多)
一個簡單的openGL的繪圖次序是:設定顏色→繪圖方式→頂點座標→繪製→結束。
每幀都會重複以上的步驟。這就是一次draw call

如果有兩個model,那麼需要
設定顏色→繪圖方式→頂點座標A→繪製→結束。
設定顏色→繪圖方式→頂點座標B→繪製→結束。
兩次draw calls;
也就是說在openGl繪製前,如果色彩通道(color filter),繪圖方式(shader),頂點座標(model)不同的情況下draw calls就會增加。

對openGl來說繪製引數(狀態值)的變更要比繪製大量的頂點更耗費cpu。

所謂高速繪圖就是,在儘量不改變openGl狀態值的情況下,用一次draw call完成所有繪製。
比如上面的例子:
設定顏色→繪圖方式→頂點座標A+頂點座標B→繪製→結束。
就要更加有效率。

個人估計unity3d的dynamic batch,static batch都是通過一定的方法使不同的object的頂點座標能夠結合成一個整體,達到減少draw calls的效果。
但是有一定的要求限制,比如material要相同,mesh要相同並在300個面以內等等,這些都是為了保證openGl的狀態值不改變。

Unity在 Player Setting 裡的兩個功能選項 Static Batching 與 Dynamic Batching。功能描述如下:

Static Batching 是將標明為 Static 的靜態物件,如果在使用相同材質球的條件下,Unity 會自動幫你把這兩個物件合併成一個 Batch,送往 GPU 來處理。這功能對效能上非常的有幫助,所以是需要付費才有的。
Dynamic Batching 是在物件小於300面的條件下(不論物件是否為靜態或動態),在使用相同材質球下,Unity就會自動幫你合合併成一個 Batch 送往 GPU 來處理。

Unity3D專案優化–繪製呼叫批處理unity3D Draw

在螢幕上渲染物體,引擎需要發出一個繪製呼叫來訪問圖形API(iOS系統中為OpenGL ES)。每個繪製呼叫需要進行大量的工作來訪問圖形API,從而導致了CPU方面顯著的效能開銷。

Unity在執行時可以將一些物體進行合併,從而用一個繪製呼叫來渲染他們。這一操作,我們稱之為“批處理”。一般來說,Unity批處理的物體越多,你就會得到越好的渲染效能。

Unity中內建的批處理機制所達到的效果要明顯強於使用幾何建模工具(或使用Standard Assets包中的CombineChildren指令碼)的批處理效果。這是因為,Unity引擎的批處理操作是在物體的可視裁剪操作之後進行的。Unity先對每個物體進行裁剪,然後再進行批處理,這樣可以使渲染的幾何總量在批處理前後保持不變。但是,使用幾何建模工具來拼合物體,會妨礙引擎對其進行有效的裁剪操作,從而導致引擎需要渲染更多的幾何面片。

材質

只有擁有相同材質的物體才可以進行批處理。因此,如果你想要得到良好的批處理效果,你需要在程式中儘可能地複用材質和物體。

如果你的兩個材質僅僅是紋理不同,那麼你可以通過 紋理拼合 操作來將這兩張紋理拼合成一張大的紋理。一旦紋理拼合在一起,你就可以使用這個單一材質來替代之前的兩個材質了。

如果你需要通過指令碼來訪問複用材質屬性,那麼值得注意的是改變Renderer.material將會造成一份材質的拷貝。因此,你應該使用Renderer.sharedMaterial來保證材質的共享狀態。

動態批處理

如果動態物體共用著相同的材質,那麼Unity會自動對這些物體進行批處理。

動態批處理操作是自動完成的,並不需要你進行額外的操作。

Tips:

提醒:

1、 批處理動態物體需要在每個頂點上進行一定的開銷,所以動態批處理僅支援小於900頂點的網格物體。

2、 如果你的著色器使用頂點位置,法線和UV值三種屬性,那麼你只能批處理300頂點以下的物體;如果你的著色器需要使用頂點位置,法線,UV0,UV1和切向量,那你只能批處理180頂點以下的物體。

3、請注意:屬性數量的限制可能會在將來進行改變。

4、 不要使用縮放尺度(scale)。分別擁有縮放尺度(1,1,1)和(2,2,2)的兩個物體將不會進行批處理。

5、 統一縮放尺度的物體不會與非統一縮放尺度的物體進行批處理。

使用縮放尺度(1,1,1)和 (1,2,1)的兩個物體將不會進行批處理,但是使用縮放尺度(1,2,1)和(1,3,1)的兩個物體將可以進行批處理。

6、 使用不同材質的例項化物體(instance)將會導致批處理失敗。

7、擁有lightmap的物體含有額外(隱藏)的材質屬性,比如:lightmap的偏移和縮放係數等。所以,擁有lightmap的物體將不會進行批處理(除非他們指向lightmap的同一部分)。

8、 多通道的shader會妨礙批處理操作。比如,幾乎unity中所有的著色器在前向渲染中都支援多個光源,併為它們有效地開闢多個通道。

9、預設體的例項會自動地使用相同的網格模型和材質。

靜態批處理

相對而言,靜態批處理操作允許引擎對任意大小的幾何物體進行批處理操作來降低繪製呼叫(只要這些物體不移動,並且擁有相同的材質)。因此,靜態批處理比動態批處理更加有效,你應該儘量低使用它,因為它需要更少的CPU開銷。

為了更好地使用靜態批處理,你需要明確指出哪些物體是靜止的,並且在遊戲中永遠不會移動、旋轉和縮放。想完成這一步,你只需要在檢測器(Inspector)中將Static複選框打勾即可,如下圖所示:

bubuko.com,布布扣

使用靜態批處理操作需要額外的記憶體開銷來儲存合併後的幾何資料。在靜態批處理之前,如果一些物體共用了同樣的幾何資料,那麼引擎會在編輯以及執行狀態對每個物體建立一個幾何資料的備份。這並不總是一個好的想法,因為有時候,你將不得不犧牲一點渲染效能來防止一些物體的靜態批處理,從而保持較少的記憶體開銷。比如,將濃密森裡中樹設為Static,會導致嚴重的記憶體開銷。

靜態批處理目前只支援Unity iOS Advanced。

Unity3D - 效能優化之Draw Call

nity(或者說基本所有圖形引擎)生成一幀畫面的處理過程大致可以這樣簡化描述:引擎首先經過簡單的可見性測試,確定攝像機可以看到的物體,然後把這些物體的頂點(包括本地位置、法線、UV等),索引(頂點如何組成三角形),變換(就是物體的位置、旋轉、縮放、以及攝像機位置等),相關光源,紋理,渲染方式(由材質/Shader決定)等資料準備好,然後通知圖形API——或者就簡單地看作是通知GPU——開始繪製,GPU基於這些資料,經過一系列運算,在螢幕上畫出成千上萬的三角形,最終構成一幅影象。

在Unity中,每次引擎準備資料並通知GPU的過程稱為一次Draw Call。這一過程是逐個物體進行的,對於每個物體,不只GPU的渲染,引擎重新設定材質/Shader也是一項非常耗時的操作。因此每幀的Draw Call次數是一項非常重要的效能指標,對於iOS來說應儘量控制在20次以內,這個值可以在編輯器的Statistic視窗看到。

Unity內建了Draw Call Batching技術,從名字就可以看出,它的主要目標就是在一次Draw Call中批量處理多個物體。只要物體的變換和材質相同,GPU就可以按完全相同的方式進行處理,即可以把它們放在一個Draw Call中。Draw Call Batching技術的核心就是在可見性測試之後,檢查所有要繪製的物體的材質,把相同材質的分為一組(一個Batch),然後把它們組合成一個物體(統一變換),這樣就可以在一個Draw Call中處理多個物體了(實際上是組合後的一個物體)。

但Draw Call Batching存在一個缺陷,就是它需要把一個Batch中的所有物體組合到一起,相當於建立了一個與這些物體加起來一樣大的物體,與此同時就需要分配相應大小的記憶體。這不僅會消耗更多記憶體,還需要消耗CPU時間。特別是對於移動的物體,每一幀都得重新進行組合,這就需要進行一些權衡,否則得不償失。但對於靜止不動的物體來說,只需要進行一次組合,之後就可以一直使用,效率要高得多。

Unity提供了Dynamic Batching和Static Batching兩種方式。Dynamic Batching是完全自動進行的,不需要也無法進行任何干預,對於頂點數在300以內的可移動物體,只要使用相同的材質,就會組成Batch。Static Batching則需要把靜止的物體標記為Static,然後無論大小,都會組成Batch。如前文所說,Static Batching顯然比Dynamic Batching要高效得多,於是,Static Batching功能是收費的……

要有效利用Draw Call Batching,首先是儘量減少場景中使用的材質數量,即儘量共享材質,對於僅紋理不同的材質可以把紋理組合到一張更大的紋理中(稱為Texture Atlasing)。然後是把不會移動的物體標記為Static。此外還可以通過CombineChildren指令碼(Standard Assets/Scripts/Unity Scripts/CombineChildren)手動把物體組合在一起,但這個指令碼會影響可見性測試,因為組合在一起的物體始終會被看作一個物體,從而會增加GPU要處理的幾何體數量,因此要小心使用。

對於複雜的靜態場景,還可以考慮自行設計遮擋剔除演算法,減少可見的物體數量同時也可以減少Draw Call。

總之,理解Draw Call和Draw Call Batching原理,根據場景特點設計相應的方案來儘量減少Draw Call次數才是王道,其它方面亦然。

U3D DrawCall優化手記

在最近,使用U3D開發的遊戲核心部分功能即將完成,中間由於各種歷史原因,導致專案存在比較大的問題,這些問題在最後,恐怕只能通過一次徹底的重構來解決

現在的遊戲跑起來會有接近130-170個左右的DrawCall,遊戲執行起來明顯感覺到卡,而經過一天的優化,DrawCall成功縮減到30-70個,這個效果是非常顯著的,並且這個優化並沒有通過將現有的資源打包圖集來實現,圖集都是原有的圖集,如果從全域性的角度對圖集再進行一次優化,那麼DrawCall還可以再減少十幾個

本次優化的重點包括:層級關係和特效

對於U3D,我是一個菜鳥,對於U3D的一些東西是一知半解,例如DrawCall,我得到的是一些並不完全正確的資訊,例如將N個紋理打包成一個圖集,這個圖集就只會產生一個DrawCall,如果不打成圖集,那麼就會有N個DrawCall,這個觀點在很多人的認識裡都是正確的,因為可以通過簡單的操作來驗證,但嚴格來說,這個觀點是錯誤的,因為它還受層級關係影響!

渲染順序

U3D的渲染是有順序的,U3D的渲染順序是由我們控制的,控制好U3D的渲染順序,你才能控制好DrawCall

一個DrawCall,表示U3D使用這個材質/紋理,來進行一次渲染,那麼這次渲染假設有3個物件,那麼當3個物件都使用這一個材質/紋理的時候,就會產生一次DrawCall,可以理解為一次將紋理輸送到螢幕上的過程,(實際上引擎大多會使用如雙緩衝,快取這類的手段來優化這個過程,但在這裡我們只需要這樣子認識就可以了),假設3個物件使用不同的材質/紋理,那麼無疑會產生3個DrawCall

接下來我們的3個物件使用2個材質,A和B使用材質1,C使用材質2,這時候來看,應該是有2個DrawCall,或者3個DrawCall。應該是2個DrawCall啊,為什麼會有3個DrawCall???而且是有時候2個,有時候3個。我們按照上面的DrawCall分析流程來分析一下:

1.渲染A,使用材質1
2.渲染B,使用材質1
3.渲染C,使用材質2

在這種情況下是2個DrawCall,在下面這種情況下,則是3個DrawCall

1.渲染A,使用材質1
2.渲染C,使用材質2
3.渲染B,使用材質1

因為我們沒有控制好渲染順序(或者說沒有去特意控制),所以導致了額外的DrawCall,因為A和B不是一次性渲染完的,而是被C打斷了,所以導致材質1被分為兩次渲染

那麼是什麼在控制這個渲染順序呢?首先在多個相機的情況下,U3D會根據相機的深度順序進行渲染,在每個相機中,它會根據你距離相機的距離,由遠到近進行渲染,在UI相機中,還會根據你UI物件的深度進行渲染

那麼我們要做的就是,對要渲染的物件進行一次規劃,正確地排列好它們,規則是,按照Z軸或者深度,對空間進行劃分,然後確定好每個物件的Z軸和深度,讓使用同一個材質的東西,儘量保持在這個空間內,不要讓其他材質的物件進入這個空間,否則就會打斷這個空間的渲染順序

在這個基礎上,更細的規則有:

場景中的東西,我們使用Z軸來進行空間的劃分,例如背景層,特效層1,人物層,特效層2
NGUI中的東西,我們統一使用Depth來進行空間的劃分
人物模型,當人物模型只是用一個材質,DrawCall只有1,但是用了2個以上的材質,DrawCall就會暴增(或許對材質的RenderQueue進行規劃也可以使DrawCall只有2個,但這個要拆分好才行),3D人物處於複雜3D場景中的時候,我們的空間規則難免被破壞,這隻能在設計的時候儘量去避免這種情況了
使用了多個材質的特效,在動畫的過程中,往往會引起DrawCall的波動,在視覺效果可以接受的範圍內,可以將特效也進行空間劃分,假設這個特效是2D顯示,那麼可以使用Z軸來劃分空間
打包圖集

每個材質/紋理的渲染一定是會產生DrawCall的,這個DrawCall只能通過打包圖集來進行優化

製作圖集一般遵循幾個規則:

從功能角度進行劃分,例如UI可以劃分為公共部分,以及每個具體的介面,功能上,顯示上密切相關的圖片打包到一起
不要一股腦把所有東西打包到一個圖集裡,特別是那些不可能同時出現的東西,它們就不應該在一個圖集裡,這樣的圖集意義不大,減少不了DrawCall,並且一個你不需要顯示的圖片,會一直佔用你的記憶體,這讓我非常不爽
注意控制圖集的大小,不要讓圖集太大,一個超級大圖集的DrawCall消耗或許頂的上十幾個小圖集的消耗
字元圖集,在使用BMFont或者其他工具生成圖片字的時候,我們往往是直接匯入一大串文字,然後直接生成圖片,但實際上這上面的操作也有優化空間,例如BMFont生成的圖片大小,是可以設定的,有兩個規則,一個規則是匯出的圖片儘量小,另一個是匯出的圖片儘量少,預設的大小應該是512x512,假設你生成的圖片256x256就可以容納,那麼多做一個操作你可以節省這麼多空間,另外當你輸入多幾個字,就導致增加一張圖片時,例如1024變成2048,那麼你可以考慮使用3張512的圖片,這樣也會節省空間

經過精心劃分的圖集在加上精心規劃的渲染順序,DrawCall會有一個質的優化

特效清理

U3D提供了非常便捷的方法讓我們很輕易地使用美術給過來的特效,懶惰的U3D程式猿會直接放入U3D,甚至不去看這是個什麼特效,我們的特效一般都是一瞬間的事情,例如技能特效,或者其他什麼特效,那麼特效播放完,這個特效我們就看不到了,但假設這個特效在播放結束的時候,沒有將自身的Active屬性設定為false,那麼它就會繼續佔用你的DrawCall,消耗你裝置的計算能力,所以程式需要保證當一個特效播放完之後,能夠被消耗,或者設定為非啟用的狀態,可以使用一些公共方法來完成特效播放完之後的清理工作(自己實現2個靜態函式,一個播放完銷燬,一個播放完設定未啟用)

完成DrawCall的優化之後,接下來就是記憶體的優化了,(記憶體優化手記 待續)