NGUI詭異的drawCall


看了很多關於NGUI drawCall的文章,見得比較多的一個觀點是:一個 Atlas 對應一個Drawcall。

好奇心下做了個demo,兩個panel中只用到一個Atlas,卻產生了10個drawCall,百思不得其解。尋覓已久終於找到三篇文章:

一、http://game.ceeger.com/forum/read.php?tid=14653

[NGUI]減少NGUI 3的DrawCall數量

剛升級到NGUI3, 這下不打緊,DrawCall數由5個增長到了十七八個,想想應該不會是NGUI的問題吧。后來整理了一下,發現有兩點: 
        1)對於同一Atlas,DrawCall數取決於Panel的數量(實際上是UIPanel這個腳本的數量)。比如說,我有兩個Sprite,這兩個Sprite屬於同一Atlas,但是位於不同的Panel下,這時候DrawCall 數是2, NGUI 2中則是1。使用建議就是只使用一個Panel。 
        2)對於不同Atlas,同一Panel下的Sprite,可通過Depth調節顯示層級,Z值不管用,這點跟NGUI 2中剛好相反。還有就是不同Atlas的Sprite 的Depth值盡量不要來回穿插。比如Atlas A中有兩個Sprite a 和 aa,Depth分別為1,3;Atlas B中有兩個Sprite b 和 bb, Depth分別為2,4, 則DrawCall 總數為4而不是2。(在NGUI 3中,你可以點擊Panel ,在Inspector面板中看到每一個DrawCall的調用細節 ) 


        簡單的說就是DrawCall的數量不只跟Atlas的數量有關,還跟Atlas調用順序有關,使用的時候最好只用一個Panel, 不同Atlas的Sprite Depth盡量不穿插。

 

二、http://blog.csdn.net/monzart7an/article/details/25212561

NGUI 減少drawcall

前置說明一:

Unity中的drawcall定義:

每次引擎准備數據並通知GPU的過程稱為一次Draw Call。

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

 

前置說明二:

NGUI中的UIWidget的顯示順序:

每一個UIWidget的顯示順序由depth值決定,跟z軸沒關系,而這個depth值是由兩部分組成的,一個是UIWidget所在的UIPanel的depth和UIwidget自身的depth值進行加權計算。

並且,UIPanel的權重非常大,可以認為,UIPanel的depth大的所有UIWidget比UIPanel的depth小的所有UIWidget比最后計算的depth一定大。舉個例子:

 

UIPanel1    depth  x                      UIPanel2    depth  y

UIWidget1  depth  m                      UIWidget2  depth  n

 

只要 x > y,那么不管m和n的大小,UIWidget1最后的depth一定大於UIWidget2。

 

 

減少drawcall的規則:

1、同一個UIPanel下的texture和font盡量放在同一個altals下。也表達了另外一個意思,使用同一個altals的元素盡量放在同一個UIPanel下面。

2、如果一個UIPanel下面使用了多個altals,那么盡量讓使用相同altals的元素連續,盡量避免altals交叉。

 

規則1的前半部分好理解。后半部分,參照前面顯示順序問題可以知道。如果使用同一個altals的元素在兩個不同的UIPanel下面,這就必然導致它們的drawcall分離。所以即使調整它們的depth一致,也無法合並成一個drawcall.

 

規則2的意思,舉個例子就明白了:

同一個UIPanel下有4個UIWidget,w1,w2,w3,w4。

其中 W1和W2引用altals1。

其中 W3和W4引用altals2。

 

如果它們的depth順序為  w1 : 1,w2 :2,w3 : 3,w4 : 4。

那么整個渲染需要2個drawcall,因為渲染順序為 w1,w2,w3,w4。

而w1和w2公用一個altals,所以可以合並成一個drawcall,同理w3和w4可以合並成一個drawcall。

 

而如果它們的depth順序為: w1 : 1,w2 :3,w3 : 2,w4 : 4。

那么整個渲染需要4個drawcall,因為渲染順序為 w1,w3,w2,w4。

因為w1和w3不是公用一個altals,所以只能分開渲染。同理w3和w2,w2和w4也只能分開渲染。

 

三、http://bbs.9ria.com/thread-282804-1-1.html

[GUI] 源碼分析NGUI的DrawCall合並原理

樓主自學Unity不久,有紕漏的地方請大神指正。正文如下:

NGUI為了減少GPU狀態切換的消耗(比如切換material),把相同material的widget合並,減少DrawCall的數量。下文描述了NGUI如何對widget歸類,以及減少DrawCall需要注意的地方。

歸類widget的代碼在UIPanel中的FillAllDrawCalls()里,代碼如下

  1. void FillAllDrawCalls ()
  2.         {
  3.                 for (int i = 0; i < drawCalls.size; ++i)
  4.                         UIDrawCall.Destroy(drawCalls.buffer[i]);
  5.                 drawCalls.Clear();
  6.                 Material mat = null;
  7.                 Texture tex = null;
  8.                 Shader sdr = null;
  9.                 UIDrawCall dc = null;
  10.                 if (mSortWidgets) SortWidgets();
  11.                 for (int i = 0; i < widgets.size; ++i)
  12.                 {
  13.                         UIWidget w = widgets.buffer[i];
  14.                         if (w.isVisible && w.hasVertices)
  15.                         {
  16.                                 Material mt = w.material;
  17.                                 Texture tx = w.mainTexture;
  18.                                 Shader sd = w.shader;
  19.                                 if (mat != mt || tex != tx || sdr != sd)
  20.                                 {
  21.                                         if (mVerts.size != 0)
  22.                                         {
  23.                                                 SubmitDrawCall(dc);
  24.                                                 dc = null;
  25.                                         }
  26.                                         mat = mt;
  27.                                         tex = tx;
  28.                                         sdr = sd;
  29.                                 }
  30.                                 if (mat != null || sdr != null || tex != null)
  31.                                 {
  32.                                         if (dc == null)
  33.                                         {
  34.                                                 dc = UIDrawCall.Create(this, mat, tex, sdr);
  35.                                                 dc.depthStart = w.depth;
  36.                                                 dc.depthEnd = dc.depthStart;
  37.                                                 dc.panel = this;
  38.                                         }
  39.                                         else
  40.                                         {
  41.                                                 int rd = w.depth;
  42.                                                 if (rd < dc.depthStart) dc.depthStart = rd;
  43.                                                 if (rd > dc.depthEnd) dc.depthEnd = rd;
  44.                                         }
  45.                                         w.drawCall = dc;
  46.                                         if (generateNormals) w.WriteToBuffers(mVerts, mUvs, mCols, mNorms, mTans);
  47.                                         else w.WriteToBuffers(mVerts, mUvs, mCols, null, null);
  48.                                 }
  49.                         }
  50.                         else w.drawCall = null;
  51.                 }
  52.                 if (mVerts.size != 0) SubmitDrawCall(dc);
  53.         }
復制代碼

算法描述如下

先把UIPanel中的Widget按depth從小到大排序,如果depth相同那按照material的ID來排序。然后遍歷每個元素,把material相同的Widget歸類到同一個drawCall。合並之后的結果如下圖

最后生成了3個DrawCall,並按順序提交GPU繪制。

為何要采用這個算法呢?因為NGUI的Material是透明材質,不會寫入深度緩存(但是會進行深度測試,以保證與非透明物體的層次正確),我們可以看NGUI材質所使用的Unlit/Transparent Colored這個Shader,里面有一句ZWrite Off。所以widget的前后關系與z坐標是沒有關系的,而是與DrawCall的繪制順序有關。所以如果要按照上圖的depth來顯示widget,必然只能分成3個DrawCall,並且按順序繪制。

 

 

其實合並drawCall的過程,正式游戲引擎“渲染隊列”的職責:

  簡要分析Ogre的渲染隊列實現原理

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM