Unity官方博文翻译——UGUI优化04

原文链接:https://unity3d.com/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input

            填充率、Canvas和输入

        这一章节讨论了UGUI 构建过程中广泛存在的问题。

修复填充率问题

        对于减轻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着色器:(着色器代码见原网页)


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上。


Unity5.2和优化Batch

        在Unity5.2中,进行batch的代码被充分重构,与Unity4.6、5.0、5.1版本相比性能更加高效。而且,在多核设备(一个核心以上)上,UGUI系统将处理工作移动到工作线程中。一般来说,Unity5.2减少了将UI拆分成几十个子Canvas的偏激需求。移动设备上的许多UI现在可以只用2、3个Canvas就可以提高性能。

        有关Unity 5.2优化的更多信息可以在这篇博客文章中找到。(链接见原网页)

UGUI中的输入和射线

        默认情况下,UGUI使用Graphic Raycaster组件来处理输入事件,例如触摸事件和点击保持事件。这通常由Standalone Input Manager组件来处理。尽管Standalone Input Manager叫这个名字,但它是一个通用的输入管理系统,点击和触摸它都会处理。


移动设备上的错误的鼠标检测(5.3)

        在Unity 5.4之前,只要当前没有正确的触摸输入,每个拥有Graphic Raycaster的激活的Canvas每帧都将进行一次射线检测来确定点击点的位置。不论任何平台,这都将进行。无鼠标的iOS和Android设备仍然会确定鼠标位置,并尝试发现该位置下有哪些UI元素。(这种情况的发生是为了确定是否有任何鼠标悬停事件需要触发)

        这浪费了CPU的时间,至少浪费了Unity应用程序的5%或者更多的CPU帧执行时间。

        Unity 5.4版本解决了这个问题。从5.4以后,没有鼠标的设备将不会查询鼠标位置,也不会发射不必​​要的射线。

        如果使用的是早于5.4的Unity版本,强烈建议移动开发人员创建自己的输入管理类。这非常简单,从UGUI开源代码中拷贝Unity的Standard Input Manager代码,注释掉ProcessMouseEvent方法和所有对这个方法的调用。


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层级。如果启动它不会带来排序或者射线检测的问题,那么就应该使用它来降低射线进行层级遍历的性能成本。

你可能感兴趣的:(Unity官方博文翻译——UGUI优化04)