填充率问题的补救
填充率是指GPU每秒渲染的屏幕像素的数量。
这又两个方法可以减少GPU fragment管线的压力
- 减少fragment shader的复杂度。
- 减少取样像素的数量
随着UI shader的逐渐标准化,最大的问题是过高的填充率。这个问题是由于大量的重叠的UI元素和UI元素的相乘占据屏幕的主要部分。这些问题可能导致额外的高频率重绘。 为了减轻过高的重绘和减少填充率过高,可以考虑使用下面的措施。
清除不可见的UI
这个方法要求禁用玩家看不见的UI。常见的场景是不透明的全屏UI背景。在这种情况下,可以禁用在全屏UI下面的UI元素。 最简单的方法是直接将不可见的UI元素的根游戏物体进行禁用。 最后确保没有UI元素被隐藏通过设置他们的alpha为0,这些元素仍然被送到GPU可能话费宝贵的渲染时间。如果UI元素不需要Graphic组件,可以移除Graphic组件让射线检测仍然保留。
简化UI结构
减少rebuild和渲染UI的时间,保证UI元素的数量尽可能的少。在可以的地方多使用烘焙。比如要混合游戏物体的色调,不要通过几个游戏物体进行混合,尽量使用材质来实现。不要创建仅仅作为文件夹没有其他意义的节点。
禁用不可见的摄像机输出结果
如果一个全屏UI带有不透明的背景,世界空间的摄像机仍然会渲染标准的3D场景在UI前面,渲染器不知道在渲染全屏UI之前会渲染整个3D场景。 如果全屏UI打开,禁用世界坐标摄像机将简单的通过减少3D空间无用的渲染,减少GPU的压力。 如果UI没有覆盖整个的3D场景,可以通过将场景渲染到一张贴图上代替持续的渲染整个场景。但是这样就不能看到场景中的动画了。 如果Canvas被设置为"Screen Space - Overlay",不管场景中的激活摄像机数量如何,都将绘制它。
多数隐藏 摄像机
很多全屏UI并没有真正的屏蔽全部的3D世界,但是留有一小部分的世界是可见的,在这种情况下,将可见的一小部分世界使用一张渲染贴图进行替换。如果这一小部分的可见世界缓存到渲染贴图中,之后真实的世界空间摄像机可以关闭,缓存的渲染贴图可以在UI屏幕之后绘制提供一个冒名顶替的3D世界。
UI的基本组成
设计者创建UI通过给标准的背景和元素进行组合和分层来创建最终的UI。这是相对简单的,对迭代是友好的,但是由于Unity UI使用的透明渲染队列,这种是不建议使用的方式。 如果一个大的UI遇到了填充率的问题,最好的解决方法是专门创建UI精灵图片合并装饰/不变的元素到背景别图中。这将减少元素的数量之前必须放大背景图上的以实现期望的设计。但是这种操作将增大项目图集的大小。 一个实例情况:在我们创建一个商店的UI的时候,一个物品可能有比边框、背景和一些小图片来定义价格,名称和一些其他信息。这些都取决于图标的大小、数量和可接受的填充率。 这里有一些合并UI元素的缺点,特定的元素不能再次被使用,需要创建额外的美术资源。增加大量新图片的时候可能增加为了保存UI贴图的内存使用,特别是在没有很好的按照需求loaded和unloaded的时候。
UI shader和低规格设备
Unity UI使用的内置shader支持masking,clipping和许多其他的负责操作。由于添加了复杂性,UI shader相比Unity 2D shader在低性能终端上表现将更差一些。 如果masking、clipping和其他"fance"效果在低端设备上不需要的话,可以自定义shader省略未使用的操作。
UI Canvas rebuilds
要显示任何UI,UI系统必须为屏幕上的每个UI元素构建几何图元。这个过程包括动态布局,生成多边形代表UI的字符串,尽可能的合并多边形到一个简单的网格中,去尽可能的减少draw calls。这是一个多步骤的过程。 Canvas重建可以改善性能问题有两个基本的原因:
- 如果可绘制UI元素的数量在canvas中是巨大的,计算batch的过程将是消耗巨大的。这是因为排序和分析的元素的消耗随着元素的增加是超过线性的。
- 如果Canvas经常被标记为dirty,将消耗过多的时间用于Canvas的刷新,即使相对较少的变化。
这些问题都会随着元素的数量急剧增加。 重要提示:无论何时Canvas上的可绘制UI元素发生变化,Canvas必须重新运行batch构建的过程。这个过程重新分析在Canvas重新绘制的每个元素,无论这个元素是否改变了。这里的改变包括UI 对象的表现,精灵图片的渲染,transform的位置和大小,文本网格的文本。
子物体顺序
Unity UI的构建是从后向前的,与对象在hierarchy中的顺序是一样。对象在hierarchy中靠前的对象被认为是在hierarchy中靠后的对象的前面。中间层是一个图形对象有不同的材质,并且与其他两个可batch对象有边缘覆盖。并且在hierarchy中在两个可batchable对象的中间。中间层次将被迫被破坏。 UI profiler和frame debugger可以检查UI的中间层。
这个问题通常发生在文本和精灵图片彼此靠的比较的近的时候,本文的边界框与精灵图片的边界框重叠了,由于文本的多边形是透明的,可以通过以下两个方法来解决:
- 重新进行排序让不可合并的材质移动到两个可合并材质的上方或者下方。
- 改变对象的位置以消除覆盖的空间。 使用Unity Frame Debugger可以很容易的发现可见的draw call数量。
Splitting Canvases(分割Canvas)
在一些不重要的情况下,将Canvas进行分割是一个不错的主意,将Canvas上的元素移动到子Canvas上。 分割Canvas适用于必须将UI中部分的深度与其他部分进行区分的情况。 大多数情况下,子Canvas继承他们的父Canvas是很方便的。 Canvas系统不会将几个不同的Canvas中的元素进行batch。强大的UI设计需要平衡重建的最小代价和draw calls的最小消耗。
通常的导航 Canvas将在任何绘制组件发生变化的时候进行重新绘制,所以最好将Canvas分割成两个部分。最好将可预期变化的部分放在同一个Canvas下面。
在一个Canvas上,放置全部的静态不会改变的元素,比如背景和标签。他们将一次全部batch,在Canvas第一次显示的时候,之后不需要rebatch。
在第二个Canvas上,放置全部的动态元素。Canvas将rebitch全部的dirty元素。如果动态元素增长的非常的快,那么需要进一步的才分动态元素那些是持续要变化的和只发生一次变化的。
这些在实践中是困难的,特别是将UI元素封装到预制体的时候。许多UI将被划分到子Canvas中。
Unity UI中的Input和raycasting
默认情况下,Unity UI使用Graphic Raycaster组件处理输入事件,像点击事件和指针悬停事件。这通常是独立的输入管理组件进行处理。正如字面上名字的意思,独立的输入感里组件是通用的输入管理系统,将处理pointers和touches。
Raycast 优化 Graphic Raycaster实现相对简单,直接通过遍历全部Graphic 组件,将'Raycast Target'设置为true,遍历全部Raycast Target,Raycaster执行一系列测试。 如果Raycast目标通过了所有测试,则会将其添加到命中列表中。
Raycast实现细节: The tests are:
- Raycast Target是否可用、被激活和被绘制
- 输入点在RectTransform的范围内
- Raycast Target有没有继承ICanvasRaycastFilter组件
然后Raycast Target会按深度进行排序,过滤掉反转的目标和过滤以确保移除在相机后面呈现的元素(即在屏幕中不可见)。
Raycasting 优化建议: 鉴于全部的Raycast必须测试全部Graphic Raycaster,最佳做法是尽在必须启用'Raycast Target'的UI对象上启用设置。Raycast Targets越小,遍历hierarchy的层次越浅,遍历每个Raycast的速度越快。 对于具有必须响应指针事件的多个可绘制UI对象的复合UI控件,例如希望其背景和文本都改变颜色的按钮,通常最好将单个Raycast目标放在复合UI的根部 控制。 当该单个Raycast目标接收到指针事件时,它可以将事件转发到复合控件内的每个感兴趣的组件。
Hierarchy depth and raycast filters: 在搜索光线投射过滤器时,每个Graphic Raycast都会遍历Transform层次结构。 此操作的成本与层次结构的深度成比例地线性增长。 必须测试附加到层次结构中每个Transform的所有组件,看它们是否实现了ICanvasRaycastFilter,因此这不是一个廉价的操作。 有几个标准的Unity UI组件使用ICanvasRaycastFilter,例如CanvasGroup,Image,Mask和RectMask2D,因此这种遍历不能简单地删除。
Sub-canvases and the OverrideSorting property: Sub画布上的overrideSorting属性将导致Graphic Raycast测试停止攀爬变换层次结构。 如果可以在不导致排序或光线投射检测问题的情况下启用它,则应该使用它来降低光线投射层次结构遍历的成本。