`

NGUI所见即所得之深入剖析UIPanel,UIWidget,UIDrawCall底层原理

 
阅读更多

NGUI所见即所得之深入剖析UIPanel,UIWidget,UIDrawCall底层原理

By D.S.Qiu

尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com

 

        之前项目中用的NGUI的版本是3.0.7 f3,开始的时候感觉没有什么问题,直达最近项目UI的完成度比较高时,就突然出现掉帧很严重的现象,即使只有一个UI打开(其他都是active = false的情况下),打开profier,发现UIPanel LateUpdate 竟然占了CPU使用率的50%左右,这太恐怖了,虽然之前看到过有吐槽NGUI的机制的,但是我觉得为了保证通用牺牲一些性能还是在所难免的,但是没想到这个版本竟然这么废。

        之前虽然研究过NGUI的UIWidget, UIDrawCall,UIGeometry和 UIPanel等基础脚本(NGUI所见即所得之UIWidget , UIGeometry & UIDrawCallNGUI所见即所得之UIPanel),也大概清楚了NGUI的绘制原理。但对具体的逻辑还是不够清楚,有点凤毛麟角。为了更好的改进NGUI的性能以及更加规范使用NGUI,只有把NGUI的底层吃透。

        由于在之前的文章介绍了UIGeometry,UIDrawCall和UIWidget之间的关系,以及UIPanel的管理机制,所以本文主要剖析底层的原理,主要要弄清楚一下问题:

 

               1. transform ,大小(size)的变化的底层绘制影响

               2.颜色(包括透明度)变化的底层绘制影响

               3.enable 和 disable 状态变化底层的处理

               4.UIDrawCall 和 UIPanel 机制的细节

       

        未免读者理不顺,先简单说下UIGeometry,UIDrawCall和UIWidget的关系:UIWidget是UI的基础组件(UILabel,UISprite)的基类,含有组件的基本信息(width,Height,color等),UIGeometry是UIWidget的几何数据,记录了顶点坐标,贴图的UVs和颜色等信息,UIDrawCall是将多个UIWidget的UIGeometry组合起来一起绘制,具体的UIWidget如果共用一个UIDrawCall由UIPanel控制,要想了解更多可以点击上面的链接的文章查看。

        虽然从人的求知欲角度,我们的疑问是按照上面 1-4 排列的,但是下面却是从 4开始介绍,只要把4理解透了3,2,1就自然迎刃而解了。

UIDrawCall

        UIGeometry相对简单,这里就不再浪费篇幅介绍了,UIDrawCall是绘制的基础组件,还是有必要仔细介绍下。

1.成员变量

        仅对几个比较重要又搞不明白的变量进行解析:

        a)List<UIDrawCall> mActiveList 和 mInactiveList : 为什么会有两个List,mAcitveList 保持当前激活的UIDrawCall, mInactiveList主要是用于回收UIDrawCall.Destroy()的UIDrawCall,以达到循环利用避免内存的反复申请和释放,减少GC的次数。这个机制前面介绍的 vp_Timer采用这个策略。

        b)Material mMaterial 和 mDynamicMat:不是讲究节约内存么,怎么会有两个Material,mMaterial就是我们图集的材质Material,mDynamicMat是实际采用的Material,因为UIPanel 的 Clipping有 AlphaClipp 和 SoftClip 这两个是要通过切换Shader来实现的,所以需要对应动态创建一个Material,这个就是mDynamicMat的存在。

        c)bool mRebuildMat 和 isDirty:这两者表示UIDrawCall所处的状态,当改变UIDrawCall的 Material 和 Shader ,mRebuildMat就变为 true,就会引起 RebuildMaterial()的调用。isDirty若为 true ,表示UIDrawCall要进行重写“填充”,调用Set函数

	public Material baseMaterial
	{
		get{return mMaterial;}
		set
		{
			if (mMaterial != value)
			{
				mMaterial = value;
				mRebuildMat = true;
			}
		}
	}
	public Shader shader
	{
		get{ return mShader;}
		set
		{
			if (mShader != value)
			{
				mShader = value;
				mRebuildMat = true;
			}
		}
	}

2.几个重要的函数

        a)CreateMaterial, RebuildMaterial 和 UpdateMaterial,这是三个后面包含前面,总之就是完成材质的创建或更新。

        b)Set (BetterList<Vector3> verts,BetterList<Vector3> norms,BetterList<Vector4> tans,BetterList<Vector2> uvs,BetterList<Color32> cols),根据verts,norms,tans,uvs,cols重新构建Mesh,MeshRender

mMesh.vertices = verts.buffer;
mMesh.uv = uvs.buffer;
mMesh.colors32 = cols.buffer;
if (norms != null) mMesh.normals = norms.buffer;
if (tans != null) mMesh.tangents = tans.buffer;

      c)OnEnable,Ondisable 和 OnDestroy:销毁了mDynamicMat,可以看出Material比Mesh更简单,不用太考虑内存问题,然后OnDestroy()没有发现调用。

	void OnEnable () { mRebuildMat = true; }

	void OnDisable ()
	{
		depthStart = int.MaxValue;
		depthEnd = int.MinValue;
		panel = null;
		manager = null;
		mMaterial = null;
		mTexture = null;

		NGUITools.DestroyImmediate(mDynamicMat);
		mDynamicMat = null;
	}

	void OnDestroy ()
	{
		NGUITools.DestroyImmediate(mMesh);
	}

       d)Create , Clear 和 Destroy:Create 先从mInactiveList中取出一个,在附上属性达到重复利用,Destroy是将没用的UIDrawCall从mActiveList移到mInactiveList中:

	static UIDrawCall Create (string name)
	{       
                //省略其他处理
		if (mInactiveList.size > 0)
		{
			UIDrawCall dc = mInactiveList.Pop();
			mActiveList.Add(dc);
			if (name != null) dc.name = name;
			NGUITools.SetActive(dc.gameObject, true);
			return dc;
		}
                //省略其他处理
		// Create the draw call
		mActiveList.Add(newDC);
		return newDC;
	}
	static public void Destroy (UIDrawCall dc)
	{
		if (dc)
		{
			if (Application.isPlaying)
			{
				if (mActiveList.Remove(dc))
				{
					NGUITools.SetActive(dc.gameObject, false);
					mInactiveList.Add(dc);
				}
			}
			else
			{
				mActiveList.Remove(dc);
				NGUITools.DestroyImmediate(dc.gameObject);
			}
		}
	}

 

UIPanel

       之前就介绍过UIPanel,也画了UIPanel主要函数的调用栈(点击查看),这里也简单罗列下LateUpdate的函数调用:

 LateUpdate

      UpdateSelf

                UpdateTransformMatrix : 调整 worldToLocal 矩阵用于调整其管理的UIWidget的transform,并进一步调整顶点信息,还调整clipOffset的变量

                UpdateLayers : 更新LayerMask

                UpdateWidgets : 调整UIWidget

                            UIWidget.UpdateGeometry : 调整UIWidget的几何(顶点等)信息

                                              OnFill(geometry.verts, geometry.uvs, geometry.cols): 如果颜色(透明度)和大小等改变就重新填充顶点信息

                                              geometry.ApplyTransform : transform发生改变,调整UIGeometry中顶点的位置(矩阵计算)

                FillAllDrawCalls  or FillDrawCall : 重新构建所有UIDrawCall (当UIWdiget的depth发生变化),否则只调整有UIWidget的UIDrawCall

      UpdateDrawCalls : 调整UIPanel管理的UIDrawCall 的 transform 和 clip 等属性

      越来越觉得NGUI的代码组件结构越来越清晰,虽然篇幅很长(有1600多行)但理解还是可以很简单的。

 

UIWidget

       UIWidget有一个变量 mChange 和一个函数 MarkAsChange() 很重要,这两个标记UIWidget是否变化需要进行调整的状态。

                1.当 Anchor , Pivot , Alpha 以及 UILabel 和 UISprite 的一些状态的改变 mChange = true ,即会调整Geometry信息

                2.MarkAsChange 会执行 drawCall.isDirty = true; 这样就会导致其所属的 UIDrawCall 需要重写构建 

 

针对前面 1-3 的疑问进行如下总结:

      UIWidget(UILabel , UISprite)的任何变化(transform , drawSize , width , heigth , color , pivot ,anchor 等)变化都会引起绘制该UIWidget进行重新构建——对Mesh的顶点进行刷新,尤其是depth的变化会使得所有UIDrawCall 进行重写调整,这是非常耗性能的。

       

总结:

       NGUI的好处就是:合并Mesh和图集节省DrawCall,由于影响Mesh的因素太多了,所以会“牵一发而动全身”,NGUI采取的一个通用的策略,没有对不同的情况做不同的处理,都是采用某个UIDrawCall全部刷新甚至是全部UIDrawCall的刷新,这也是大家吐槽的“重中之重”。

       D.S.Qiu认为针对不用的情况还是会有不少优化的,比如改变alpha值,可以不需要重新调整顶点verts,而只需要单独调整cols的alpha通道,改变depth也不需要全部调整UIDrawCall,这样明显是没有做到严格的管理的。

       对此,D.S.Qiu提出2点使用NGUI制作UI的建议:

                1)尽量是UIWidget静动分离,即静止的尽量合成单独一个UIPanel,会变化的就放在另外一个UIPanel

                2)尽量控制UIPanel和UIDrawCall的数量,充分利用图集的空间,对“夹层”的情况可以通过图集的调整,使得UIDrawCall变得更少

 

        由于时间关系(马上2:30了),就只能写到这里,如果你有NGUI的任何问题,欢迎和D.S.Qiu进行交流讨论。

 

        如果您对D.S.Qiu有任何建议或意见可以在文章后面评论,或者发邮件(gd.s.qiu@gmail.com)交流,您的鼓励和支持是我前进的动力,希望能有更多更好的分享。

        转载请在文首注明出处:http://dsqiu.iteye.com/blog/2025177

更多精彩请关注D.S.Qiu的博客和微博(ID:静水逐风)

 

         

 

 

 

 

 

 

 

 

 

 

 

 

 

2
1
分享到:
评论

相关推荐

    最新版本的NGUI插件NGUI Next-Gen UI 覆盖unity多个版本

    NGUI Next-Gen UI是一款功能强大、灵活性高的UI插件,是当前最新版本的NGUI插件。它可以覆盖Unity的多个版本,包括Unity 5、Unity 2017和Unity 2018等。与其他UI插件相比,NGUI Next-Gen UI具有高效的性能和优秀的...

    NGUI Next-Gen UI 3.9.0

    《NGUI Next-Gen UI 3.9.0:Unity中的高效UI解决方案》 NGUI,全称为Next-Gen User Interface,是一款专为Unity引擎设计的用户界面(UI)系统,其3.9.0版本是针对Unity 5.0及以上版本优化的升级版。在Unity游戏开发...

    最新版本的NGUI插件NGUI Next-Gen UI

    最新版本的NGUI插件NGUI Next-Gen UI

    NGUI Next-Gen UI 2020.1.5

    - 编辑器集成,所见即所得 - 本地化、数据绑定、委托、事件 - 支持所有平台 - 制作进行 1 次绘制调用的 UI - 随附完整的 C# 源代码 - 已广泛优化 - 专门团队支持 2020.1.5 - NEW: You can now specify per-symbol ...

    NGUI3.12.1 UI 最新版本

    《NGUI 3.12.1:经典UI框架的最新迭代》 NGUI,全称为Next-Gen UI,是一款广泛应用于Unity3D游戏开发中的用户界面(UI)系统。尽管随着Unity引擎的发展,Unity Technologies推出了官方的UGUI(Unity GUI),但NGUI...

    NGUI Next-Gen UI v3.6.0

    最新版unity3d扩展插件:NGUI Next-Gen UI3.6.0 运行Unity3D,解压后此压缩包,在菜单Assets中选择自定义导入。 如果无法导入时,请检查导入目录中是否存在中文字符。...所见即所得的集成编辑器,支持所有平台。

    NGUI Next-Gen UI v3.11.2 (u5)

    《NGUI Next-Gen UI v3.11.2 (u5)——Unity中的高效UI解决方案》 在Unity游戏开发中,用户界面(UI)的设计与实现是至关重要的环节,它直接影响到玩家的游戏体验。NGUI(Next-Gen UI)是一款专为Unity引擎设计的UI...

    NGUI下alpha通道的分离与合并shader及Uipanel softclip问题解决

    解决Unity下NGUI下alpha通道的分离与合并的shade 在Uipanel softclip 下裁剪的问题。下载之后解压,里面有shader和脚本扩展器,直接放到unity里面就可以用了,简单方便。不需要任何技术。

    NGUI Next-Gen UI v3.5.7

    NGUI(Next-Gen User Interface)是一款针对Unity游戏引擎的用户界面系统,旨在提供高效、易用且功能丰富的UI解决方案。版本3.5.7是该系统的某个稳定更新,为开发者提供了更多的特性和优化。 在NGUI 3.5.7中,我们...

    NGUI最新版NGUI Next-Gen UI v2.6.1e.zip

    1. **组件化设计**:NGUI将UI元素分解为多个可重用的组件,如Widget(控件)、Panel(面板)、Label(文本标签)、Button(按钮)等,这样可以方便地构建和维护UI布局。 2. **自动布局系统**:通过设置各种布局参数...

    unity ngui-next-gen-ui 2022.06.08 -2022.05.13-2021.11.30合集

    NGUI(Next-Gen UI)是Unity早期的一个流行UI系统,用于替代Unity内置的Basic GUI系统,提供更高效、更灵活的UI解决方案。 NGUI的主要特点包括: 1. **性能优化**:NUI使用了基于图元的渲染方式,提高了UI元素的...

    NGUI Next-Gen UI v3.11.4

    《NGUI Next-Gen UI v3.11.4:Unity界面开发的得力助手》 在游戏开发领域,Unity引擎以其强大的功能和易用性备受开发者青睐。而在Unity中,用户界面(UI)的设计与实现是至关重要的一环,它直接影响着玩家的游戏...

    NGUI Next-Gen UI v3.0.6

    - NEW: NGUI now has new written documentation. - NEW: NGUI now has an abundance of context-sensitive help. Just right click on an NGUI component and choose the Help option. - NEW: NGUI now has robust ...

    NGUI插件unity3d(NGUI_Next-Gen_UI.unitypackage)

    3. **层级管理**:通过Widget和Panel的概念,NGUI实现了UI元素的层级管理,可以轻松控制UI元素的显示顺序和遮挡关系。 4. **布局系统**:NGUI有强大的布局管理器,支持网格布局、流式布局、堆栈布局等多种布局方式...

    NGUI Next-Gen UI 3.9.2

    NGUI(Next-Gen UI)是一款专为Unity引擎设计的用户界面系统插件,其版本3.9.2是该插件的一个重要里程碑。这款插件以“Keep It Simple, Stupid”(KISS原则)为核心设计理念,旨在为开发者提供一个高效、易用且功能...

    [NGUI]NGUI Next-Gen UI v3.6.8 7月13日更新的最新版本

    NGUI Next-Gen UI v3.6.8 7月13日更新的最新版本 NGUI【3.6.8】更新 新增1、UIWrapContent现在加入了范围限制(用来设置索引值) 新增2、增加Transfrom.OverlayPosition,使widget用3D位置的时候更容易定位 修改1...

    NGUI Next-Gen UI 3.9.1

    - NEW: Added UICamera.first referencing the active NGUI event system. - FIX: Alpha should now work as expected with Linear lighting. - FIX: UICamera.isOverUI should now work properly for all types of ...

    NGUI 3.11.1 最新版

    NGUI 是一款强大的用户界面(UI)系统,适用于Unity3D游戏引擎。NGUI 3.11.1是该插件的一个重要版本,专为Unity5进行了优化和更新,旨在提供更高效、更灵活的UI解决方案。在这个版本中,开发者可以期待一系列改进...

    [Shader]对NGUI的UISprite和UITexture进行裁剪

    在Unity游戏开发中,NGUI(Next-Generation User Interface)是一个流行且强大的UI系统,它允许开发者创建复杂的用户界面。本篇文章将详细讲解如何利用Shader技术对NGUI的UISprite和UITexture进行裁剪,以实现遮罩...

Global site tag (gtag.js) - Google Analytics