NGUI内部架构
NGUI的核心架构,其实就在于3块:UIPanel、UIWidget、UIDrawcall。
其中Panel和Widget是会显示在Hierarchy层级中的,而Drawcall则不会。
所以我们先来说前面2个。
Widget是NGUI中负责界面显示的基础单位。
所有需要在屏幕上显示出来的2D UI本质上都是一个Widget——包括Label、Sprite、Texture。
Widget作为一个整体,内部包含了渲染所需要的所有原材料:mesh面片,贴图,UV,材质,位置信息等等。
而Panel,就是一个用来管理Widget的控件。
每一个Widget都必定从属于一个Panel,不可例外——NGUI的根节点Root本身也包含一个Panel,而你不可能在Root之外创建Widget。
需要注意的是,Hierarchy中的UI层级关系,与NGUI自己内部的层级关系,并不一致。
比如这样一个结构。 Panel_D是Widget_2的儿子,是Panel_A的孙子。
但是对于NGUI内部来说:
Panel_A和Panel_D是平级的,Widget_2和Panel_D并没有父子关系。
在NGUI的架构中,首先存在一个静态的static List
其大致的结构图如下:
static List
好了,现在我们来聊聊Drawcall
一次Drawcall,就是CPU调用GPU干一次活。 如果要说得更具体一点的话,我们可以把GPU想象为一个庞大系统中高度独立,功能单一,但是非常重要的模块——比如说黑帮中专门负责砍人的部门。这个部门其他什么都不管,就负责接到指令,然后出去砍人。 而CPU,就是下达指令的单位。砍谁,去哪儿砍,需要什么物资,事前需要处理什么,事后需要处理什么,这些都需要CPU来安排。CPU给GPU一次指令,GPU办一次事情,就是一次Drawcall。 Drawcall是衡量渲染负担的一个重要指标。而且通常情况下,主要就是衡量CPU的负担。 因为GPU是针对砍人这件事情高度定制优化的。一辈子就做这么一件事情。因为专注,所以效率。 而CPU就不一样了,要负担的责任太多,不可能针对某个单一需求高度定制架构。对于CPU来说,发10条指令,每条指令砍1个人,其负担要远远大于发一条指令一次砍10个人。 所以,优化Drawcall主要就是尽可能的合并指令,让一个指令包含尽可能多的内容,以此降低CPU的负担。 当然,有时候我们也会先让目标合体,比如说砍一个合体葫芦娃,比起砍7个小葫芦娃,CPU和GPU双方的工作量都可以减少。 好了,闲话扯完,最后我们来一句正经的总结: 所谓Drawcall,就是CPU加载并整理好美术资源(包括模型、贴图、材质等)及相关指令后,调用GPU进行工作的过程。
NGUI的Drawcall处理
当一个Panel的List
注:各个Panel的List
渲染序列
NGUI里的每一个Panel都有一个Render Q的设置选项。 RenderQ有两种主要模式: Automatic —— 无参数 StartAt —— 需要设置一个整数参数 大多数时候我们不需要动这个东西。如果全部都默认的Automatic的话,NGUI会自己帮我们按照Panel的depth为第一优先级,Widget的depth为第二优先级来处理好渲染序列。 不过有时候我们难免会遇到想人为调整RenderQ的情况——比如说想在两个Panel之间插入特效之类的。这个时候可能就需要去设置RenderQ的type和数值。 对于StartAt模式的Panel,会以用户配置的参数作为第一个Drawcall的渲染序列,之后每个Drawcall递增1。这个很明确,不会有什么幺蛾子。 而对于Automatic的Panel,会以Max ( 3000 , ActiveList中的最大RenderQ+1)作为第一个Drawcall的渲染序列,之后每个Drawcall递增1。 比如说,如果当前最大序列是2500,那么处理到当前Panel的时候,渲染序列还是会从3000开始,每个Drawcall递增1;但是如果当前最大序列是3500,那么处理到当前Panel的时候,就会从3501开始,每个Drawcall递增1。 而如果用户想要在两个Panel之间插入想要渲染的东西,只需要把要插入的东西的渲染序列设置为渲染序列较高的那个Panel的StartAt值-1就可以了。
补充阅读——Hierarchy中的父子关系
前面我们说到,NGUI内部的逻辑和Hierarchy中的层级关系并不一致,就算把一个Panel挂在一个Widget下面,Panel也不会认Widget当爹。 不过在处理起来的时候,这个Panel还是会从自己的父对象那里去获取一些参数(U3D通用规则)。
比如说Panel_D,他还是需要从Widget_2那里去获取位置、缩放、旋转、Alpha,gameobjcet. Activeself等信息,以便处理自己的状态。 此外,值得注意的是,在当前最高版本的NGUI里,Panel还会一级一级向上的不断去尝试找另一个Panel,如果能找到,就会进行一次裁剪区域的交集处理。 比如Panel_D,他最终会找到Panel_A,然后用Panel_A的clip剪裁区域和自己的clip剪裁区域求交集,作为最终实际生效的裁剪区域。