NGUI复杂情况下(ui,特效穿插)的渲染顺序
结论:
影响级:SortingLayer > Panel depth > RenderQueue > (Sort Order ) > Widget depth
(Camera depth除外,它的影响是最大的,以下讨论同一Camera下)
正常情况下为了合并drawcall,应将渲染状态相同的(同一texture、sprite、atlas) UI的widget depth 设置为前后递增。
两个存在覆盖关系的Panel不能设置相同的depth。
2500是关键值,它是透明跟不透明的分界点
RenderQueue > 2500的物体绝对会在RenderQueue <= 2500的物体前面,即渲染时RenderQueue大的会挡住RenderQueue小的,不论它的SortingLayer和Panel depth怎么设置都是不起作用的。此时不遵循影响级
当两个的RenderQueue都在同一侧时,SortingLayer高的绝对会在SortingLayer低的前面,无视RenderQueue跟Panel depth。即遵循影响级。
注意,为了使SortingLayer生效,一定要勾选Sort Order前的勾。
正常情况下没有必要去设置Panel的Sort Order值。
在特效和UI穿插的情况下,有两种思路
一:将要覆盖特效的Panel的depth设置成比特效所在Panel的depth高,被覆盖同理。
这种做法不会去改变正常UI层各组件的Widget depth和Sort Order,仅新增一个Panel depth适中的Panel层,保证了UI的遮挡关系的有序。
二:新增一个SortingLayer 适中的Panel层来容纳特效,做法与上同理
至于特效所在Mesh Renderer 的 sortingOrder(order in layer),建议是尽量不要改变它,考虑之前提出的办法。因为它的值可能会无视Panel depth。如果一定要改变这个值(比如希望在一个Panel里放置多个特效,这些特效也有遮挡关系或者不能在同一层渲染)则一定要做好调试,否则可能产生不符期望的遮挡关系。
关于NGUI的渲染循序研究:

上图是一个简要的NGUI的图形工作流,UIGeometry被UIWidget实例化之后,通过UIWidget的子类,也就是UISprit,UILabel等,在OnFill()函数里算出所需的Geometry缓存(顶点数,UV,Color,法线,切线)。之所以要生成这些数据,是为了之后生成mesh来渲染。
而UIPanel,通过遍历自己子类下所有的UIWidget组件(已经按深度排序),先创建一个UIDrawCall,然后把该Widget的material,texture,shader对象以及Geometry的缓存传给UIDrawCall,如此反复循环搜索该UIPanel下的每一个Widget,只要是material,texture,shader都和上一个Widget一样的Widget,他们的缓存都传给同一个UIDrawCall,直到循环结束或者碰到一个material,texture,shader不同(渲染状态不相同)的Widget。当遇到这种Widget,循环会再创建一个新的UIDrawCall,然后传递material,texture,shader,缓存,如此这般,直到循环完全结束。
每次有新的UIDrawCall产生,UIPanel就会调用上一个UIDrawCall的UpdateGeometry()函数,来创建渲染所需的对象。这些对象分别是MeshFilter,MeshRender,和最重要的Mesh(Mesh的顶点,UV,Color,法线,切线,还有三角面)。这些对象都会像我们正常在游戏中新建Cube一样,依附在创建UIDrawCall时生成的GameObject上以便可以渲染。我们在Editor中是看不到这个GameObject的,因为创建时设置了HideFlags.HideAndDontSave
所以,NGUI的实际渲染流程,就是一个把Widget上的视觉组件生成的缓存,做成UIDrawCall之后,生成mesh来渲染的过程
渲染顺序是根据UIGeometry里传递的顶点(vertex)序列,这是一组根据Widget上的视觉组件生成的vertex(例如,一般的UISprit在simple模式下,会生成四个vertex,位置和你所看到那个编辑模式下scene视图里的可拖动锚点四个角一样,最后我会把UIDrawCall生成的mesh现实出来,一看就明白)这些vertex传入UIDrawCall之后,会计算出三角面,生成mesh。
尽可能避免运行时触发FillAllDrawCalls(),制作的时候就把所有UI部件的Prefab做好
UIPanel.FillAllDrawCalls()调用的话基本是整个Panel重绘了,还好调用条件比较苛刻,除了第一次LateUpdate,之后若有新的Widget加入进来,并且深度不在之前DrallCall的范围内,或者用了新的matiral shader texture那么就会影响之前已经布好的UI秩序,就会被重绘。之前提到的遍历Panel下的所有Widget就是这个函数,调用的时候性能会损失很大。
NGUI的调试:
Panel下面有个Show Draw Call的按钮,点击就会看到

或者使用frameDebugger观察当前帧的DrawCall
这是当前Panel中所有的DrawCall,可以看到我们场景中总共有两个DrawCall,这个panel占了一个。很清晰易懂,材质,以及包含哪些Widgets,RederQueue跟三角面数都贴出来了。Widgets可以看到我们这个DrawCall中包含了多少Widget元素,也就是包含多少UISprite跟UIlabel这些继承子UIWidget的组件,而drawCall的个数很很清晰,我们创建的时候,可以根据这个面板进行调整优化。
浙公网安备 33010602011771号