您的位置:首页 > 产品设计 > UI/UE

源码分析NGUI的DrawCall合并原理

2014-05-29 16:46 465 查看
楼主自学Unity不久,有纰漏的地方请大神指正。正文如下:

NGUI为了减少GPU状态切换的消耗(比如切换material),把相同material的widget合并,减少DrawCall的数量。下文描述了NGUI如何对widget归类,以及减少DrawCall需要注意的地方。

归类widget的代码在UIPanel中的FillAllDrawCalls()里,代码如下

void FillAllDrawCalls ()

{

for (int i = 0; i < drawCalls.size; ++i)

UIDrawCall.Destroy(drawCalls.buffer[i]);

drawCalls.Clear();

Material mat = null;

Texture tex = null;

Shader sdr = null;

UIDrawCall dc = null;

if (mSortWidgets) SortWidgets();

for (int i = 0; i < widgets.size; ++i)

{

UIWidget w = widgets.buffer[i];

if (w.isVisible && w.hasVertices)

{

Material mt = w.material;

Texture tx = w.mainTexture;

Shader sd = w.shader;

if (mat != mt || tex != tx || sdr != sd)

{

if (mVerts.size != 0)

{

SubmitDrawCall(dc);

dc = null;

}

mat = mt;

tex = tx;

sdr = sd;

}

if (mat != null || sdr != null || tex != null)

{

if (dc == null)

{

dc = UIDrawCall.Create(this, mat, tex, sdr);

dc.depthStart = w.depth;

dc.depthEnd = dc.depthStart;

dc.panel = this;

}

else

{

int rd = w.depth;

if (rd < dc.depthStart) dc.depthStart = rd;

if (rd > dc.depthEnd) dc.depthEnd = rd;

}

w.drawCall = dc;

if (generateNormals) w.WriteToBuffers(mVerts, mUvs, mCols, mNorms, mTans);

else w.WriteToBuffers(mVerts, mUvs, mCols, null, null);

}

}

else w.drawCall = null;

}

if (mVerts.size != 0) SubmitDrawCall(dc);

}
复制代码

算法描述如下

先把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,并且按顺序绘制。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: