来源( 来源:unity官方 Optimizing Unity UI )

官方链接:

[1]  https://unity3d.com/cn/learn/tutorials/temas/best-practices/guide-optimizing-unity-ui

[2] https://unity3d.com/cn/learn/tutorials/topics/best-practices/fundamentals-unity-ui

[3] https://unity3d.com/cn/learn/tutorials/temas/best-practices/unity-ui-profiling-tools?playlist=30089

[4] https://unity3d.com/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input

[5] https://unity3d.com/cn/learn/tutorials/topics/best-practices/optimizing-ui-controls

Unity 官方优化实践 https://unity3d.com/cn/learn/tutorials/s/best-practices

 

一、核心目标

  • draw calls和batching花费的平衡

二、UGUI理论基础

术语:

  • Canvas, 是Unity渲染系统给层状几何体( layered geometry  )提供的可以被画入、被放在上面或者放在世界空间的底层Unity组件。Canvas负责将它包含的几何体组合成batch,生成合适的渲染命令发送给Unity图形系统。这个过程在底层的C++代码中完成,这个过程被称为一次rebatch或者一次batch build。当一个Canvas被标记为包含需要rebatch的几何体时,这个Canvas被认为是dirty的。
  • layered geometry , 由Canvas Renderer组件提供给Canvas。[ Canvas 负责进行渲染, Canvas Renderer负责采集/接收. ]
  • 动静隔离 , 一个子Canvas仅仅是一个嵌套在父Canvas中的组件,子Canvas将它的子物体和它的父Canvas隔离,一个子Canvas下dirty的子物体不会触发父Canvas的rebuild,反之亦然。(这些在某些特殊情况下是不确定的,比如说改变父Canvas的大小导致子Canvas的大小改变。).
  • Graphic , 是UGUI的C#库提供的一个基类。它是UGUI所有类的基类,给所有的UGUI类提供可以画在Canvas系统上的几何图形。大多数Unity内置的继承Graphic的类都是通过继承一个叫MaskableGraphic的子类来实现,这使得他们可以通过IMaskable接口来被隐藏。Drawable类的子类主要是image和text,已经提供了同名的组件。
  • Layout , 组件控制着RectTransform的大小和位置,经常被用于要生成具有相似的大小和位置关系内容的复杂布局。它只依靠RectTransform,只影响与其相关的RectTransform的属性。这些layout组件不依赖于Graphic类,可以独立于UGUI的Graphic组件之外使用。
  • CanvasUpdateRegistry ,  Graphic和Layout组件都依赖于CanvasUpdateRegistry类,它不会在Unity编辑器的界面中显示。这个类追踪那些Graphic和Layout组件必须被更新的时候,还有与其对应的Canvas触发了willRenderCanvases事件的时候。更新Graphic类和Layout类叫做rebuild。rebuild的过程将在本文后续讨论。

渲染细节:

  • 在使用UGUI制作UI时,请牢记Canvas中所有几何体的绘制都在一个透明队列中,这就意味着由UGUI制作的几何体将从始至终伴随着alpha混合,所以从多边形栅格化的每个像素都将被采样,即使它被完全不透明的物体所覆盖。在手机设备上,这种高等级的过度绘制将迅速超过GPU填充频率的承受能力。

Batch构建过程(Canvas)

  • Batch , 构建过程是指Canvas通过结合网格绘制它所承载的UI元素,生成适当的渲染命令发送给Unity图形流水线。Batch的结果被缓存复用,直到这个Canvas被标为dirty,当Canvas中某一个构成的网格改变的时候就会标记为dirty。
  • Canvas的网格从那些Canvas下的CnavasRenderer组件中获取,但不包含任何子Canvas。
  • 计算Batch要求按照深度排序网格,测试它们是否有重叠,共享材质等等。这个过程是多线程的,在不同的CPU架构下性能表现非常不同,特别是在手机芯片(通常CPU核心很少)和现代桌面CPU(通常拥有四核心或者更多)之间非常不同。

Rebuild过程(Graphics)

  • Rebuild , 过程是指Layout和UGUI的C#的Graphic组件的网格被重新计算,这是在CanvasUpdateRegistry类中执行的。这是一个C#类,它的源码可以在Unity的Bitbucket上找到。
    CanvasUpdateRegistry , 类中,PerformUpdate方法,当一个Canvas组件触发它的WillRenderCanvases事件时,这个方法就会被执行。这个事件每帧调用一次。
    PerformUpdate ,  函数运行的三个步骤:
    1- 通过ICanvasElement.Rebuild函数,请求rebuild被Dirty的Layout组件。
    2- 所有被注册的裁剪组件(例如Mask),对需要被裁剪的组件进行剔除。这在ClippingRegistry.Cull中执行。
    3- dirty的Graphic组件被要求rebuild其图形元素。
  • Layout 和 Graphic 的 rebuild ,
    Layout:Layout rebuild , 三个部分 PreLayout , Layout  ,  PostLayout
    Graphic:Graphic rebuild ,两个部分 PreRender , LatePreRender
  • Layout Rebuild , 要重新计算一个或者多个Layout组件所包含的UI组件的适当位置(以及可能的大小),有必要对Layout应用层次的排序。在GameObject的hierarchy中靠近root的Layout可能会影响改变嵌套在它里面的其他Layout的位置和大小,所以必须首先计算。 为此,UGUI根据层次结构中的深度对dirty的Layout组件列表进行排序。层次结构中较高的Layout(即拥有较少的父transform)将被移到列表的前面。然后,排序好的Layout组件的列表将被rebuild,在这个步骤Layout组件控制的UI元素的位置和大小将被实际改变。关于独立的UI元素如何受Layout组件影响的详细细节,请参阅Unity Manual的UI Auto Layout章节。 [ 这就是为什么unity的布局组件一旦形成嵌套,套内组件将失效的原因 , unity也暂时未开放布局执行层级顺序的接口 , 仅在UGUI代码中可见但不未公开 ]
  • GraphicRebuild , 当Graphic组件被rebuild的时候,UGUI将控制传递给ICanvasElement接口的Rebuild方法。Graphic执行了这一步,并在rebuild过程中的PreRender阶段运行了两个不同的rebuild步骤:1.如果顶点数据已经被标为Dirty(例如组件的RectTransform已经改变大小),则重建网格。2.如果材质数据已经被标为Dirty(例如组件的material或者texture已经被改变),则关联的Canvas Renderer的材质将被更新。Graphic的Rebuild不会按照Graphic组件的特殊顺序进行,也不会进行任何的排序操作。
  • 便于理解,制作了相关关系的导图

三、UGUI性能分析工具

  • Unity Profiler

Unity Profiler的主要用途是执行性能比较分析:当Unity Profiler运行的时候进行enabling和disabling的操作,它可以迅速的缩小定位到性能问题最大的UI层级。查看profiler输出结果的“Canvas.BuildBatch”和“Canvas.SendWillRenderCanvases”。               Canvas.BuildBatch是执行Canvas的Batch build过程的底层代码计算量。Canvas.SendWillRenderCanvases包含了C#脚本对Canvas组件的willRenderCanvases事件的订阅的调用。UGUI的CanvasUpdateRegistry类接收这个事件并且通过它来执行前文所描述的rebuild过程。预计所有被标dirty的UI组件都会在这个时候更新他们的Canvas Renderer。

注意:为了更容易地看到UI性能的差异,通常建议禁用除了Rendering和Scripts以外所有trace category。这可以通过点击CPU Usage profiler左侧的名叫trace category旁边的彩色方块来完成。

还要注意,category可以在CPU profiler中重新排列,可以点击或者拖拽category向上或者向下来对他们进行重新排列。

  • Unity Frame Debugger

Unity Frame Debugger是一个减少UGUI的draw call的实用工具。这个内置的工具可以通过Unity Editor中的Window菜单来访问。当它运行的时候,它将显示包括UGUI在内的所有Unity产生的draw call。特别要注意的是,Unity Frame Debugger             在Unity Editor界面就可以更新游戏视口产生的draw call信息,因此可以用来尝试不同的UI配置而无需进入游戏模式。

UGUI的drawcall产生的位置取决于Canvas组件上被设置的渲染模式:

1.Screen Space – Overlay将出现在Canvas.RenderOverlays组中。

2.Screen Space – Camera将出现在Render.TransparentGeometry子项,所选渲染相机的Camera.Render组中。

3.World Space将出现在Render.TransparentGeometry子项,每个可以看见Canvas的World Space的摄像机中。

如果UI的shader没有被自定义的shader替换的话,那么所有UI都可以被 “Shader: UI/Default”识别,列出在哪个组和drawcall的细节。在下图中请看高亮红框标注的地方。

在调整UI的时候观察Unity Frame Debugger所显示的信息,这就相对比较简单的使Canvas中的UI元素最优的合成batch。最常见的与设计相关的打断批次的原因是UI元素间不小心造成的重叠。

所有的UGUI组件将它们的几何体生成成一系列的 quad。然而,很多sprite和text只占用用于显示它们的 quad的一小部分,留下了大量的剩余空间。这样的结果就是,UI开发者无意中使多个不同的 quad互相覆盖,它们的texture来自不同的        material,不能合成batch。

由于UGUI的操作完全在透明队列中,任何有不能合batch的quad在它上边的quad必须在不能合batch的quad之前绘制,因此它不能与放在不能合batch的quad上的quad合batch。(翻译这段我尽力了,但是估计还是不清楚。我讲一下大意:就是两个能合batch的quad中间夹了一个不能合batch的quad,造成这两个quad也不能合batch了)

考虑一个情景,有三个quadA、B、C。假设这三个quad彼此覆盖,并且A和C使用了相同的Material,B使用了单独的Material。B不能和A、C合成batch。

如果在层级结构中从上到下的是A、B、C,那么A、C也不能合batch,因为B必须绘制在A的上面,C的下面。然而,如果B被放在可被合batch的quad前面或者后面,那么可以被合batch的quad就能构成batch。B只需要在batch的quad之前或者之后绘制,而不会介入其中。

关于这个问题更深入的探讨,请看Canvas章节的Child order部分。

  • Instruments & VTune

XCode的Instruments和Intel的VTune各自可以非常深入的分析UGUI的rebuild和Canvas的batch计算在Apple设备和Intel CPU上的性能。方法名称几乎和我们之前介绍过的Unity Profiler的标签完全相同。它们是:

Canvas::SendWillRenderCanvases是一个C++父类调用C#中的Canvas.SendWillRenderCanvases方法,并控制 Unity Profiler中该行显示。它包含了用于进行rebuild过程的代码,这已经在上一章节详细介绍了。

Canvas::UpdateBatches几乎和Canvas.BuildBatch完全相同,但是增加了Unity Profiler页面并不包括的代码引用。它运行上文描述的Canvas的batch建立的实际过程。

当通过IL2CPP构建一个Unity APP时,这些工具可以被用于更深入的查看C#中Canvas::SendWillRenderCanvases的编译。(注意:编译的方法的名字是近似的。)

IndexedSet_Sort和CanvasUpdateRegistry_SortLayoutList是用于排序显示在标为dirty的Layout组件被重新计算之前的一个列表。如上文所述,这包括了计算每个Layout组件的父transform数量。

ClipperRegistry.Cull调用所有IClipRegion接口注册的实现者。内置的实现者包括使用IClipRegion接口的RectMask2D组件。当ClipperRegistry.Cull被调用时,RectMask2D组件将遍历在它层级下的所有要被裁剪的UI元素,更新他们的剔除信息。

所有可嵌套元素,并要求它们更新其剔除信息。

Graphic_Rebuild包含所有要显示的Image,Text或其他Graphic派生的组件所需要的网格的实际计算性能开销。在这之下有其他一些方法,如Graphic_UpdateGeometry,最值得注意的是Text_OnPopulateMesh。

-当Best Fit勾选时,Text_OnPopulateMesh通常是一个热点。这将在本指南后面详细讨论。

-网格修饰符,比如Shadow_ModifyMesh和Outline_ModifyMesh也在这里运行。通过这些方法可以看到shadow,       outline和其他特殊效果组件的计算性能开销。

  • Xcode Frame Debugger和Intel GPA

底层的Frame Debugger对监测UI不同独立部分的batch性能开销和UI过度绘制开销非常重要

  • Xcode Frame Debugger的使用
    为了测试一个给定的UI是否过度榨取GPU资源,可以使用Xcode内置的GPU诊断工具。首先将项目配置为使用Metal或OpenGLES3,然后进行构建并打开生成的Xcode项目工程。如果Unity在OpenGLES 2下运行,则Xcode不能对Unity进行分析,因此这些技术不能用于较旧的设备。注意:在某些版本的Xcode中,为了使图形分析器工作,有必要在Build Scheme中选择适当的Graphics API。为此,请转到Xcode中的Product菜单,展开Scheme菜单项,然后选择Edit Scheme ….选择Run target并转到Options页面。更改GPU Frame Capture选项来使API适配您的工程。假设Unity工程设置了自动选择图形API,则大多数新一代的iPad将默认选择Metal。如果有疑问,请启动项目并查看Xcode中的调试日志,前面的几行应该会指出哪个渲染路径(Metal,GLES3或GLES2)正在被初始化。注意:上述调整在Xcode 7.4中应该不是必需的,但在Xcode 7.3.1和更旧的版本中仍然偶尔会被发现是必须的。在iOS设备上构建并运行项目。GPU profiler显示在Xcode的Navigator边栏中,点击FPS条目。(图请参见原网页)GPU分析器中第一个重要的是屏幕中的三个条目:“Tiler”、“Renderer”、“Device”。这些表示:

    “Tiler”是对GPU生成几何体(包括在顶点着色器中的花费时间)过程中压力的衡量。

    ——一般来讲,“Tiler”值高表明顶点着色器计算过慢或者是绘制的顶点过多。

    “Renderer”是对GPU的像素流水线压力的衡量。

    ——一般来讲,“Renderer”值高表明应用程序超过了GPU的最大填充率,或是片段着色器效率低下。

    “Device” 是GPU使用的综合衡量标准,包括“Tiler”和“Renderer”的性能分析。它通常可以被忽略,因为它大体上跟踪监测“Tiler”和“Renderer”的较高者。

    有关Xcode GPU  Profiler的更多信息,请参阅此文档(链接见原网页)。

    Xcode’s Frame Debugger可以通过点击隐藏在GPU Profiler底部的小“相机”图标来打开。在下面的屏幕截图中,通过箭头和红色框突出显示。(截图见原网页)

    暂停一下之后,Frame Debugger的摘要视图就会出现,如下所示(截图见原网页):

    在使用默认UI着色器时,假设默认UI着色器没有被自定义着色器替换,那么由UGUI系统生成的渲染几何图形的开销将显示在“UI / Default”着色器通道下。在上面的截图中可以看到这个渲染管线的默认的UI着色器是“UI / Default”。

    UGUI只产生quad,所以顶点着色器不太可能给GPU Tiler流水线产生压力。出现在这个着色器中的任何问题都应归结于填充率问题。

  • 分析分析器结果

    1- 如果Canvas.BuildBatch或Canvas :: UpdateBatches占用了过多的CPU时间,则可能的问题是单个Canvas上的Canvas Renderer组件数量过多。请参阅“Canvas”一章的“Splitting Canvases”章节。

    2- 如果GPU过度的时间花费在绘制UI上,并且frame debugger表明片段着色器流水线是瓶颈,那么应该是UI的像素填充率超过了GPU的能力,最可能的原因是UI的过渡绘制。请参考Fill-rate, Canvases and input章节的Remediating fill-rate issues部分。

    3- 如果Graphic的rebuild占用了过多的CPU,如在Canvas.SendWillRenderCanvases或者Canvas::SendWillRenderCanvases中看到了大量的CPU时间占用,那么就需要进行深层分析,应该与Graphic的rebuil过程中的一些部分有关。

    4- 如果大量的WillRenderCanvas花费在IndexedSet_Sort或是CanvasUpdateRegistry_SortLayoutList上,时间花费在对dirty的layout组件列表进行排序,那么就要考虑减少Canvas中的Layout组件数量。请在Replacing layouts with RectTransforms和Splitting Canvases部分中也许会找到补救措施。

    5- 如果过多的时间花在Text_OnPopulateMesh上,那么Text网格的生成就是罪魁祸首。请参阅Best Fit和 Disabling Canvas Renderers部分,也许会找到补救措施。并考虑Splitting Canvases中的建议,如果正在重建的大部分文本实际上并未更改其基础字符串数据,text大量rebuild实际上并没有改变其基础的字符串数据。

    6- 如果时间花在内置的Shadow_ModifyMesh或Outline_ModifyMesh(或任何其他使用的ModifyMesh),则问题在于花费在计算修饰性网格过多的时间。考虑删除这些组件,并通过静态图像实现其视觉效果。

    7- 如果Canvas.SendWillRenderCanvas中没有特定的热点,或者它看起来每帧都在运行,那么问题可能是动态元素与静态元素混合在一起,致使整个Canvas过于频繁地重建。参见Splitting Canvases部分。

四、UGUI优化 : 填充率、Canvas和输入

修复填充率问题:

对于减轻GPU片段流水线上的压力有两种行动方案:

1.减少片段着色器的复杂性。

——有关更多详细信息,请参阅“UI着色器和低规格设备”部分

2.减少必须采样的像素数量。

由于UI着色器通常是标准化的,最常见的问题就是填充率的过度使用。导致这个问题最普遍的原因是UI元素大量重叠,或是有多个UI元素占据大部分的屏幕。这些问题都会导致高等级的过度绘制。

  • 消除看不见的UI

    简单的禁用玩家看不见的元素是对现有UI元素重新设计要求最小的方法,对于这种方法最常见的情况是打开了一个具有不透明背景的全屏UI。此时,在全屏UI下的任何UI元素都可以被禁用。最简单的方法是禁用根GameObject或是包含UI元素的GameObject。有关替代解决方案,请参阅Disabling Canvas Renderers部分。

  • 禁用不可见的摄像机输出

    如果在UGUI中打开了一个拥有不透明背景的全屏UI,world-space摄像机仍然会对在UI后面的独立的3D 场景进行渲染。渲染器并不知道全屏UGUI会遮挡整个3D场景。因此如果打开了一个不透明的全屏UI,禁用任何或者是全部的world-space摄像机将减少渲染3D世界的无用工作,从而减少 GPU的压力。注意:如果Canvas被设置为Screen Space – Overlay,不管场景中可用的摄像机有多少,Canvas都将被绘制。

  • 大部分被遮挡的摄像机

许多“全屏”UI并不实际上遮挡整个3D世界,而是留下了一个小的部分可以看到3D世界。在这种情况下,使用一个渲染的纹理来拍摄这部分3D世界可能更为理想。如果这部分可见的3D世界被缓存在渲染纹理中,那么实际的world-space摄像机就可以被禁用,此时被缓存的渲染纹理就作为3D               世界的冒充版本显示在UI屏幕后面。

  • 基于构图的UI

    在设计者中,基于构图来合并与层叠独立的背景与UI元素来构成最终的UI是非常普遍的。虽然这样做相对简单,而且易于迭代,但是由于UGUI使用的是透明渲染队列,所以无法高效工作。

    考虑一个简单的UI,有一个背景、一个按钮和一些文字在按钮上。在像素显示文字的情况下,GPU必须先采样背景纹理,然后是按钮的纹理,最后是字体的纹理,这三层全部都要采样。当UI的复杂性增加时,更多装饰性的元素将被层叠在背景之上,需要采样的数量将迅速增加。

    如果发现一个大的UI被填充率所束缚,最好的解决方案就是创建一个单独的UI Sprite,它融合了许多装饰性的或者是不变的UI元素在它的背景纹理之上。这样做减少了为了达到设计目的而必须重叠防止的元素数量,但是这样做也耗费劳动力并且也增加了项目图集的大小。

    这种将创建给定UI的需要重叠的元素合并到特定的UI Sprite上的做法也适用于子元素。考虑一个商店UI带有产品滚动的窗格,每个产品UI元素有一个边框、一个背景和一些图标来表示价格、名字和其他信息。

    这个商店UI需要一个背景,但是由于产品要在背景上滑动,产品UI元素无法融合到商店UI的背景纹理之上。然而,边框、价格、名字和产品UI元素的其他元素可以融合到产品的背景上。根据图标的大小和数量,填充率的节省相当可观。

    合并分层元素有一些缺点。特殊的元素不能再重复利用,这就需要额外的艺术家人力资源来创建。增加大的新纹理可能会显著增加需要来存储UI纹理的内存数量,特别是UI纹理未能按需求加载和卸载的情况下。

  • UI着色器和低规格设备

    UGUI使用的内置着色器包含了对隐藏、裁剪和许多其他复杂操作的支持。由于这种复杂性的增加,在iPhone4这种较低端设备上,UI着色器的表现较简单的Unity2D着色器相比表现较差。

    如果一个针对低端设备的应用程序不需要隐藏、裁剪和其他奇特的功能,那么就可以创建一个自定义的着色器来省略没有使用的操作,比如下面这个最简单的UI着色器:(着色器代码见原网页) [https://unity3d.com/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input]

  • UI Canvas rebuild

    要显示任何UI,UI系统必须要为显示在屏幕上的每个UI组件构建几何体。这包括了运行动态布局代码,生成多边形来变现UI文本中字符串的字符,还有融合尽可能多的几何体到单个网格中来最小化draw call。这个过程有很多步骤,在本指南开始的基础基础概念部分有详细介绍。

    Canvas rebuild成为性能问题有两个主要原因:

    1.如果一个Canvas上有大量要绘制的UI元素,那么计算batch本身就变的非常昂贵。这是因为在排列和分析这些元素上的花费比在Canvas上绘制这些UI元素的增长更多。

    2.如果Canvas的dirty特别频繁,那么就有可能花费更多的时间在刷新一个Canvas相对较小的改变上。

    随着一个Canvas上元素数量的增加,上面两个问题会越来越严重。

    重要提示:在给定的Canvas上任何要绘制的UI元素改变,这个Canvas必须重新进行batch的build过程。该过程重新分析Canvas上的每个可绘制UI元素,而不管它是否已经改变。请注意,“改变”是指影响UI对象外观的任何改变,包括Sprite Renderer中指定的Sprite、transform的position和scale变化、包含在文本网格中的文本等等。

  • 子物体排序

    UGUI的建立是从后至前的,子对象在层级中的排序决定了它们的建立顺序。在层级循序中靠前的物体将被建立在层级顺序中靠后物体的后面。batch的build是从层级顺序的上走到下,并收集具有相同材质的游戏物体,即有相同纹理且没有中间层的对象(“中间层”是具有不同材质的图形对      象,其边界框与另外可合batch的对象重叠,并放置在两个可batch对象之间的层次结构中)。中间层的存在导致batch被打断。

    正如Unity Frame Debugger部分所述, Frame Debugger可以用来检查中间层的UI。就是上述这种情况,一个要绘制的对象插入到另外两个要绘制的原本可batch的对象之间。

    这个问题最为常发生于当text和sprite位于彼此靠近时:text的边界框可能不可见地重叠附近的sprite,因为text字形的多边形大多数都是透明的。这个问题可以通过两种方式解决:

    1.对要绘制的对象进行重新排序,以确保两个可以合batch的对象不会被不能合batch的对象打破。也就是说,移动不可合batch的对象到可合batch的对象的上方或者下方。

    2.调整各个对象的位置来消除不可见空间的重叠。

    上述两个操作都可以在Unity Frame Debugger打开并可用的情况下在Untiy Editor中执行。通过简单地观察Unity Frame Debugger中可见的drawcall次数,就可以找到一个最合适的顺序和位置来使由于UI元素重叠而导致的drawcall浪费减少到最小。

  • 拆分Canvas

    除了一些特殊的情况,将Canvas拆分通常是一个好主意。可以将元素移动到子Canvas或者是同级Canvas中。

    同级Canvas最常适用于UI中的某一部分必须与其他部分区分绘制深度,经常在其他层的上面或者下面。(例如教程中的箭头)

    在其他大多数情况下,子Canvas可以更方便的从父Canvas继承显示设置。

    乍看之下,将整个UI拆分为多个子Canvas是一种最佳做法,但要知道,Canvas系统也不会在分离的Canvas之间合成batch。高性能的UI设计要求在最小化rebuild和最小化drawcall浪费中取得一个平衡。

  • 一般准则

    由于Canvas的rebatch过程在任何时候都会包含所有要绘制的子组件的改变,所以最好将那些不是特殊情况的Canvas拆分成至少两部分。另外,如果一些元素可能会同时改变,最好将他们放到同一个Canvas中。比如这里有一个进度条和一个倒数计时器,它们俩依赖同样的底层数据,并且将同时被更新,所以它们应该被放在同一个Canvas上。

    在一个Canvas上,放置所有静态的不改变的元素,比如背景和标签。当Canvas一开始显示时它们将会被batch一次,然后它们就不会再需要被rebatch了。

    在第二个Canvas上,放置所有的动态的、频繁变化的元素。这个Canvas主要是用来rebatch被标为dirty的元素的。如果动态元素的数量变得非常多,那就要对所有动态元素进行更细的拆分,一些是经常会改变的(例如进度条、计时器显示、所有动画),还有一些是偶尔改变的。

    事实上这些在实际使用中是非常困难的,尤其是将UI控件封装成prefab的时候。许多UI转而选择拆分Canvas,将更消耗性能的控件拆分到子Canvas上。

  • Raycast优化

    Graphic Raycaster是一个相对直接的实现,它迭代所有Raycast Target设为true的Graphic组件。对于每一个Raycast Target设为true的Graphic组件,Graphic Raycaster会执行一系列测试。如果该组件通过了所有测试,则会被添加到命中的列表中。

    Raycast实现细节

    上述的测试是:

    1.如果检测到的目标的GameObject是激活的、UI组件是可用的,那就绘制(即具有几何体)。

    2.如果输入点在被检测到的UI元素的RectTransform范围内。

    3.如果被检测到的目标拥有,或者其任意深度的子物体拥有任何实现ICanvasRaycastFilter的组件,并且这个组件允许进行射线检测。

    接着检测目标列表会对元素按照深度排序,调整顺序不对的目标,并确认要在摄像机后面渲染的元素(即在屏幕中不可见)被移除了。

    如果3D或者2D的物理系统各自的Graphic Raycaster的“Blocking Objects”属性被标记,那么Graphic Raycaster也会向它们投射射线(在脚本中,该属性被命名为blockingObjects)。

    如果3D或者2D的的“Blocking Objects”被启动,那么任何绘制在一个射线遮挡物理层上的2D或是3D物体下的被检测到的目标将会被从列表中移除。

    返回最终的列表。

  • 射线优化技巧

    鉴于所有射线检测目标都必须由Graphic Raycaster进行测试,因此最好的做法是仅在必须接收点击事件的UI组件上启用“Raycast Target”设置。检测目标列表越小,必须遍历的层级越浅,每次射线检测的速度越快。

    对于那些有多个必须对点击事件响应的UI物体的复合UI控件,比如一个按钮它希望同时改变它的Text和背景颜色,这种情况一般最好在复合UI控件的根物体上设置一个单独的检测目标。当单个的检测目标接收到了点击事件,那么它可以将这个事件发送给这个复合控件中要响应的组件。

  • 层级深度与raycast filter

    当寻找raycast filter的时候,每个Graphic Raycast都会对根物体层级进行从头至尾的遍历。这个操作的性能消耗与层级的深度呈线性增长关系。层级中所有拥有Transform的组件必须经过检查,看它们是否实现了ICanvasRaycastFilter,所以这个操作的性能耗费并不廉价。

    有一些独立的UGUI组件实现了ICanvasRaycastFilter,比如CanvasGroup, Image, Mask和RectMask2D,所以这个遍历不会简单的结束。

  • 子Canvas和OverrideSorting属性

    子Canvas中的OverrideSorting属性将会造成Graphic Raycast测试停止遍历Transform层级。如果启动它不会带来排序或者射线检测的问题,那么就应该使用它来降低射线进行层级遍历的性能成本

五、其他 : 

其他UI优化技术和提示

很多时候并没有一个简洁的方法来优化UI。本章节包含了一些可能会提高UI性能的建议,但是一些是在结构上不简洁的,或是难于维护,或是有一些不好的边际效应。其他的是一些使UI初始开发变成简单的行为的解决方案,但是也会更容易造成一些性能问题。

基于RectTransform的布局

Layout组件性能消耗相对昂贵,因为它们必须在其每次标记dirty时重新计算其子元素的大小和位置(有关详细信息,请参阅Fundamentals章节的Graphic rebuild部分)。如果给定Layout中的元素数量相对较少且数量固定,并且Layout结构相对简单,则可以使用基于RectTransform的Layout替换Layout。

通过分配一个RectTransform的锚点,RectTransform的位置和大小就能基于其父级进行缩放。例如,两个RectTransform就能实现一个简单的两列的布局:

左列的锚点应该是X:(0,0.5)和Y:(0,1)(覆盖左边屏幕)

右列的锚点应该是X:(0.5,1)和Y:(0,1)(覆盖右边屏幕)

RectTransform的大小和位置的计算将由Transform系统本身在本机代码中驱动。这通常比依靠Layout系统更高效。编写基于RectTransform的布局的MonoBehaviour脚本也是可以的。但是,这是一项相对复杂的工作,也超出了本指南的范围。

禁用Canvas Renderer

当显示或隐藏一个UI分立的部分时,通常会启用或者禁用这个UI的根游戏物体。这确保了在这个禁用的UI中没有组件接收输入或是执行Unity的回调函数。

然而,这也会导致Canvas抛弃其VBO(顶点缓冲对象)数据。重新启用Canvas会使Canvas(包括所有的子Canvas)强制进行rebuild和rebatch进程。如果这种情况发生的非常频繁,增加的CPU使用会造成应用程序的帧率卡顿。

一种可能的方法是将UI的显示和隐藏控制在其Canvas和子Canvas中,仅仅是启用或者禁用关联到Canvas或是子Canvas的Canvas Renderer组件(并不是指真的Canvas Renderer组件,而是指依赖Canvas Renderer组件的image,text等组件)。

这将UI的网格就不会被绘制,它们将会保持驻留在内存中,它们的原始batch将会被保存。此外,在UI的层级中将不会有OnEnable或是OnDisable回调函数执行。

但请注意,这样的方法将不会消除GraphicRegistry中UI的图形,所以它们仍然会出现在Graphic Raycast要检查的组件列表中。这种方法不会禁用任何隐藏UI中的MonoBehaviour脚本,所以这些MonoBehaviour脚本将会接收Unity的生命周期回调,比如说Update。

为了避免这个问题,将以这种方式禁用的UI上的MonoBehaviour脚本不应该直接来实现Unity的生命周期回调函数,而是应该通过挂载到UI根游戏物体上的“Callback Manager” MonoBehaviour脚本来接收回调函数。每当UI被显示和隐藏的时候,就会通知Callback Manager,这会确保生命周期事件根据需要传播或是不传播。关于Callback Manager模式的进一步解释超越了本指南的范围。

分配事件摄像机

如果使用Unity的内置Input Manager并且Canvas的渲染模式设置为World Space或是Screen Space – Camera模式,始终分别的设置事件摄像机和渲染摄像机非常重要。在脚本中,它始终作为worldCamera属性公开。

如果这个属性没有设置,那么UGUI将会在挂有摄像机的游戏物体中通过主摄像机标签来寻找主摄像机,至少每个World Space或是Camera Space的Canvas都会发生这种查找。GameObject.FindWithTag众所周知非常慢,所以强烈建议所有的World Space和Camera Space的Canvas都在设计时或初始化时设置其Camera属性。

这个问题不会发生在Overlay的Canvas上。

Unity UGUI优化与原理【unity官方】相关推荐

  1. Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译

    http://www.cnblogs.com/alan777/p/6135703.html Unity性能优化(2)-官方教程Diagnosing performance problems using ...

  2. unity UGUI 优化

    一.图片打成图集 drawCall  打断问题 当渲染一组UGUI时,中间有使用其他图集(或者UGUI自带组件)时,会打断drawcall 的批处理 但,当unity检测到中间图集延后渲染不影响原有效 ...

  3. Unity Ugui 优化 DrawCall

    1.使用Frame Debug 2.不要出现 如果有 给他替换成一像素透明 常见的是 image里的source没有赋值 3.mask 有两次dc 并且里外不能合并 可以用rect mask 2d 代 ...

  4. UNITY性能优化✨ProtoBuf 在 Unity 中的详细使用教程

    文章目录

  5. Unity基础知识学习五,UGUI优化相关

    1.什么是UGUI优化,UGUI优化的理论基础 1.1理论基础 Canvas, 是Unity渲染系统给层状几何体( layered geometry  )提供的可以被画入.被放在上面或者放在世界空间的 ...

  6. Unity 性能优化(力荐)

    开始之前先分享几款性能优化的插件: 1.SimpleLOD : 除了同样拥有Mesh Baker所具有的Mesh合并.Atlas烘焙等功能,它还能提供Mesh的简化,并对动态蒙皮网格进行了很好的支持. ...

  7. Unity 性能优化基础

    文章目录 前言 一.代码层面 二.减少Rebatch和Rebuild 1.Rebatch 2.Rebuild 3.优化点 三.降低OverDraw 总结 前言 最近笔者在找工作,面试过程经常被问到工作 ...

  8. Unity游戏优化[第二版]学习记录6

    以下内容是根据Unity 2020.1.01f版本进行编写的 Unity游戏优化[第二版]学习记录6 第6章 动态图形 一.管线渲染 1.GPU前端 2.GPU后端 3.光照和阴影 4.多线程渲染 5 ...

  9. Unity性能优化 :合批篇

    前言 本系列为一些性能优化的小知识,是日常游戏开发中与性能表现的一些点,本篇为该系列文章的第二篇,前篇链接: 第一篇: Unity性能优化:资源篇 在早期Unity中,对于合批的处理手段主要是下面三种 ...

最新文章

  1. 在Oracle VM VirtualBox中如何安装64位虚拟机系统
  2. php实现 统计输入中各种字符的个数
  3. 基于SSH实现教务管理系统
  4. log4j.logger java_java – Log4JLogger的根本原因是找不到还是不可用?
  5. hrbust 1616 密码锁(广搜)
  6. 关于1970-1-1 00:00.000的知识【转】
  7. Linux驱动编程中EXPORT_SYMBOL()介绍
  8. influxdb无法实现关联表_双汇:从棘手的InfluxDB+Redis到TDengine
  9. 计算机系统 过程调用
  10. oracle升级12.2,Oracle 11.2.0.4升级到12.2.0.1
  11. 计算机中那些事儿(八):再历装系统之终身学习
  12. protobuf java linux_linux下安装protobuf及其使用
  13. 人工智能和金融是天作之合的5个理由
  14. 电工学(上)-电工技术 秦曾煌(7版)
  15. Loadrunner 报错: Error: The table 'E:\*性能测试脚本\login1\userName.dat' does not exist.
  16. outlook 日历共享_如何共享您的Outlook日历
  17. STM32如何快速驱动一款12864LCD液晶模块 3分钟点亮 STM32例程
  18. iOS查看系统所有字体(带效果图)
  19. 大厂HR:我让你测试一个(电梯、杯子、笔、桌子、洗衣机),你会怎么测试它?
  20. BUC冰川算法的python实现

热门文章

  1. 网络游戏外挂制作 (转贴)
  2. SmartThings
  3. 天之痕MV-三个人的时光
  4. 单片机 DS1302 公历年月日 换算出星期、农历月份、农历日期 判断闰年
  5. 逆向链接服务器(分布式服务器之前的协调通信,同步,消息的转发)
  6. Ubuntu是什么?
  7. linux设置rx8010时间,EPSON 实时时钟芯片RX-8010SJ Application Manual应用手册.pdf
  8. 指定DIV局部刷新的简单实现,很简单,但是网上搜到的大部分都很复杂
  9. |sex[]_sum[],G_ans|L2-028 秀恩爱分得快
  10. Springboot+MybatisPlus整合poi实现导出导入Excle表格