Unity UI 优化最佳实践4 -fill-rate, Canvases 和 input

 

本章讨论构建Unity UI的更广泛的问题。

修复填充率问题

有两种方案可以用来减轻GPU片元管线上的压力:

  • 减少片元着色器的复杂性。 有关更多详细信息,请参阅“UI shaders and low-spec devices”部分。
  • 减少必须采样的像素数量。
    由于UI着色器通常是标准化的,所以最常见的问题是填充率过高。 这通常是由于大量重叠的UI元素和/或具有多个UI元素占据屏幕的重要部分的。 这两个问题都可能导致极高的overdraw水平。

为了缓解填充率过度利用和减少overdraw,请考虑以下可能的补救措施。

消除无形的用户界面

要求最少重新设计现有UI元素的方法是简单地禁用玩家不可见的元素。最常见的情况是打开具有不透明背景的全屏UI。 在这种情况下,可以禁用放置在全屏UI下的任何UI元素。

最简单的方法是禁用包含不可见UI元素的根GameObject或GameObjects。 有关替代解决方案,请参阅禁用Canvas部分。

最后,通过将它们的alpha设置为0来确保没有UI元素被隐藏,因为元素仍然会被发送到GPU并且可能需要宝贵的渲染时间。 如果一个UI元素不需要一个Graphic组件,您可以简单地将其删除,然后raycasting仍然可以工作。

简化UI结构

为了减少重建和渲染UI所需的时间,保持UI对象的数量尽可能低是很重要的。尝试尽可能多地烘焙东西。例如,不要使用混合GameObject 只是为了将色调更改为元素,而是通过材质属性来完成此操作。此外,不要创建类似文件夹的游戏对象,除了组织场景之外别无其他目的。

禁用不可见的摄像机输出

如果打开具有不透明背景的全屏UI,则世界空间相机仍然会在UI后面呈现标准3D场景。渲染器不知道全屏Unity UI会遮挡整个3D场景。

因此,如果打开一个完全全屏的UI,禁用任何和所有隐藏的世界空间相机将通过简单地消除无用的渲染3D世界的工作来帮助减轻GPU的压力。

如果UI不覆盖整个3D场景,则可能需要将场景渲染到纹理一次,然后使用它而不是连续渲染它。您将失去在3D场景中看到动画内容的可能性,但这在大多数情况下应该是可以接受的。

注意:如果Canvas被设置为“Screen Space – Overlay”,则无论在场景中活动的摄像机数量如何,它都将被绘制。

大部分被遮盖的相机

许多“全屏”UI实际上并不遮盖整个3D世界,但可以看到世界的一小部分。在这些情况下,捕获渲染纹理中可见的世界部分可能更为理想。如果世界的可见部分被“缓存”在渲染纹理中,则可以禁用实际的世界空间相机,并且可以在UI屏幕后面绘制缓存的渲染纹理,以提供3D世界的冒名顶替版本。

基于构图的用户界面

设计师通过构图来创建UI非常普遍 - 把标准背景和元素分层组合来创建最终UI。虽然这样做相对简单,并且对迭代非常友好,但由于Unity UI使用透明渲染队列,因此它是非高性能( non-performant )的。

考虑一个带有背景,按钮和按钮上的一些文本的简单UI。因为透明队列中的对象是从后到前排序的,所以在像素落入文本字形的情况下,GPU必须采样背景的纹理,然后是按钮的纹理,最后是文本图集的纹理,总共三个采样。随着UI复杂度的增长,以及更多的装饰元素被分层到背景上,采样数量可能会迅速增加。

如果发现大型UI被填充率限制,最好的方法是创建专门的UI sprites,将UI的许多装饰/不变元素合并到其背景纹理中。这减少了为了达到期望的设计必须层叠在一起的元素的数量,但是这是劳动密集型的并且增加了项目纹理图集的大小。

将创建给定的UI所需的分层元素数量聚合到特定的UI sprites的这一原则也可以用于子元素。考虑一个带有产品滚动窗格的商店UI。每个产品UI元素都有边框,背景和一些图标来表示价格,名称和其他信息。

商店用户界面需要一个背景,但由于其物品在背景上滚动,物品元素无法合并到商店用户界面的背景纹理中。但是,物品UI元素的边框,价格,名称和其他元素可以合并到物品的背景中。取决于图标的大小和数量,填充率节省可能相当大。

组合分层元素有几个缺点。特定元素不能再使用,需要额外的美术资源才能创建。大量新纹理的添加可能会显着增加容纳UI纹理所需的内存量,特别是如果UI纹理未按需加载和卸载的情况下。

UI着色器和低端设备

Unity UI使用的内置着色器包含对遮罩,裁剪和其他许多复杂操作的支持。 由于这种复杂性的增加,与较低端设备(如iPhone 4)上更简单的Unity 2D着色器相比,UI着色器性能较差。

如果针对低端设备的应用程序不需要遮罩,裁剪和其他“花哨”功能,则可以创建自定义着色器,以省略未使用的操作,例如此最小UI着色器:

Shader "UI/Fast-Default"
{
    Properties
    {
        [PerRendererData] _MainTex ("Sprite Texture", 2D) = "white" {}
        _Color ("Tint", Color) = (1,1,1,1)
    }

    SubShader
    {
        Tags
        { 
            "Queue"="Transparent" 
            "IgnoreProjector"="True" 
            "RenderType"="Transparent" 
            "PreviewType"="Plane"
            "CanUseSpriteAtlas"="True"
        }

        Cull Off
        Lighting Off
        ZWrite Off
        ZTest [unity_GUIZTestMode]
        Blend SrcAlpha OneMinusSrcAlpha

        Pass
        {
        CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "UnityUI.cginc"

            struct appdata_t
            {
                float4 vertex   : POSITION;
                float4 color    : COLOR;
                float2 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 vertex   : SV_POSITION;
                fixed4 color    : COLOR;
                half2 texcoord  : TEXCOORD0;
                float4 worldPosition : TEXCOORD1;
            };

            fixed4 _Color;
            fixed4 _TextureSampleAdd;
            v2f vert(appdata_t IN)
            {
                v2f OUT;
                OUT.worldPosition = IN.vertex;
                OUT.vertex = mul(UNITY_MATRIX_MVP, OUT.worldPosition);

                OUT.texcoord = IN.texcoord;

                #ifdef UNITY_HALF_TEXEL_OFFSET
                OUT.vertex.xy += (_ScreenParams.zw-1.0)*float2(-1,1);
                #endif

                OUT.color = IN.color * _Color;
                return OUT;
            }

            sampler2D _MainTex;
            fixed4 frag(v2f IN) : SV_Target
            {
                return (tex2D(_MainTex, IN.texcoord) + _TextureSampleAdd) * IN.color;
            }
        ENDCG
        }
    }
}

UI Canvas 重建

要显示任何UI,UI系统必须为屏幕上呈现的每个UI组件构建几何图形。这包括运行动态布局代码,生成多边形以表示UI文本字符串中的字符,并将尽可能多的几何图形合并到单个网格中,以最大限度地减少绘制调用。这是一个多步骤的过程,在本指南开始部分的基础部分详细介绍。

由于两个主要原因,Canvas重建可能会导致性能问题:

  • 如果Canvas上可绘制UI元素的数量很大,那么计算批量本身就变得非常昂贵。这是因为排序和分析元素的成本比Canvas上可绘制的UI元素的数量增长更多。
  • 如果Canvas经常被弄脏,则可能花费过多的时间来刷新变化相对较小的Canvas。
    随着Canvas上元素的数量增加,这两个问题都趋于严重。

重要提示:每当给定Canvas上的任何可绘制UI元素发生更改时,Canvas必须重新运行批处理构建过程。该过程重新分析Canvas上的每个可绘制UI元素,而不管它是否已更改。请注意,“更改”是指影响UI对象外观的任何更改,包括分配给Sprite渲染器的Sprite,变换位置和比例,包含在文本网格中的文本等。

子顺序

统一用户界面是由后到前构建的,对象在层次结构(hierarchy )中的顺序决定了它们的排序顺序。层次结构中较前的对象将在层次结构中稍后的对象后面考虑。通过从顶层到底层走行并收集所有使用相同材质,相同纹理且不具有中间层的对象来构建批。 “中间层”是具有不同材质的图形对象,其边界框与其他可合批对象重叠,并放置在两个可合批对象之间的层次结构中。中间层迫使批次被打破。

正如在Unity UI分析工具部分中所讨论的那样,UI分析器和帧调试器可用于检查中间层的UI。这种情况下,一个可绘制对象置于另外两个可批量绘制的对象之间。

当文本和sprite彼此靠近时,通常会出现此问题:文本的边界框可能会与附近的sprite重叠,因为文本字形的多边形大部分都是透明的。这可以通过两种方式解决:

  • 对可绘制对象进行重新排序,以便可批量处理的对象不会被不可批量处理的对象插入;也就是说,将不可触及的对象移动到可批量处理对象的上方或下方。
  • 调整对象的位置以消除不可见的重叠空间。
    这两种操作都可以在Unity Editor中打开并启用Unity Frame Debugger来执行。通过简单地观察Unity Frame Debugger中可见的绘图调用次数,可以找到一个顺序和位置,以最大限度地减少由于UI元素重叠而浪费的draw calls次数。

拆分Canvas
除了最微不足道的情况外,通过将元素移动到子Canvas或同级Canvas来分Canvas通常是一个好主意。

兄弟Canvas最适用于UI的某些部分必须将其绘制深度与UI的其余部分分开控制的情况下,始终高于或低于其他图层(例如教程箭头)的情况。

在大多数其他情况下,子Canvas更加方便,因为它们从其父级Canvas继承显示设置。

乍看之下,将用户界面细分为多个子Canvas是一种最佳做法,但请记住,Canvas系统也不会将不同Canvas之间的批次合并在一起。高性能的UI设计需要在最大限度地减少重建成本和最大限度地减少浪费的Draw calls之间取得平衡。

一般准则

因为任何时候Canvas都会重新绘制任何可绘制组件,所以通常最好将所有具有一定复杂度的Canvas分成至少两部分。此外,如果一些元素变化频率相同,最好尝试放在同一个Canvas上。一个例子可能是一个进度条和一个倒数计时器。这些都依赖于相同的底层数据,因此需要同时更新,因此它们应放置在同一个Canvas上。

在一个Canvas上,放置所有静态且不变的元素,例如背景和标签。当Canvas第一次显示时,这些将会批处理一次,之后不再需要重新进行修补。

在第二个Canvas上,放置所有“动态”元素 - 频繁变化的元素。这将确保这个Canvas主要是重新使用脏元素。如果动态元素的数量变得非常大,则可能需要将动态元素进一步细分为一组不断变化的元素(例如进度条,计时器读数,任何动画)以及仅偶尔改变的一组元素。

实际上这实际上是相当困难的,尤其是在将UI控件封装到preab时。许多UI选择通过将昂贵的控件分割到子画布上来细分Canvas。

Unity 5.2和优化的批处理

在Unity 5.2中,批处理代码基本上被重写,并且与Unity 4.6,5.0和5.1相比,性能更高。此外,在具有1个以上内核的设备上,Unity UI系统将大部分处理移至工作线程。总的来说,Unity 5.2减少了将UI分解成几十个子画布的需求。移动设备上的许多用户界面现在可以通过少至两个或三个画布来提高性能。
有关Unity 5.2优化的更多信息可以在此博客文章中找到。

Unity UI中的Input和Raycast

默认情况下,Unity UI使用Graphic Raycaster组件来处理输入事件,例如触摸事件和指针悬停事件。这通常由独立输入管理器组件处理。尽管有这个名字,独立输入管理器是一个“通用”输入管理器系统,它将处理指针和触摸。

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

在Unity 5.4之前,只要当前没有可用的触摸输入,每个附带Graphic raycaster的活动Canvas将每帧运行一次raycast ,以检查指针的位置。无论平台如何,这都会发生。无鼠标的iOS和Android设备仍然会查询鼠标的位置并尝试发现该位置下的哪些UI元素,以确定是否需要发送悬停事件。

这浪费了CPU时间,并且目睹了Unity应用程序CPU帧时间的5%或更多。

Unity 5.4中解决了这个问题。从5.4开始,不带鼠标的设备将不会查询鼠标位置,也不会执行不必要的光线播放。

如果使用的是早于5.4的Unity版本,强烈建议移动开发人员创建自己的输入管理器类。这可以像从Unity UI 源码复制Unity标准输入管理器一样简单,并注释掉ProcessMouseEvent方法以及对该方法的所有调用。

Raycast优化

Graphic Raycaster是一个相对直接的实现,它遍历所有将“Raycast Target”设置为true的Graphic组件。对于每个Raycast Target,Raycaster执行一组测试。如果Raycast Target通过了所有测试,则会将其添加到点击列表中。

Raycast实现细节

测试是:

  • 如果Raycast目标处于活动状态,启用并绘制(即具有几何图形)
  • 如果输入点位于Raycast目标attach到的RectTransform内
  • 如果Raycast目标具有或是任何ICanvasRaycastFilter组件的子级(任意深度),并且Raycast Filter组件允许Raycast。
    然后,按照深度对 Raycast Targets列表进行排序,对逆向目标进行过滤,并进行过滤,以确保在相机后面渲染的元素(即屏幕中不可见)被删除。

如果在图形Raycaster的“阻挡对象(“Blocking Objects)”属性上设置了相应的标志,则图形Raycaster也可以将光线投射到3D或2D物理系统中。 (从脚本中,该属性被命名为blockingObjects。)

如果启用2D或3D阻挡对象,则在射线阻挡物理层上的2D或3D对象下绘制的任何Raycast目标也将从命中列表中消除。

最后返回最终的匹配列表。

Raycasting优化技巧

鉴于所有Raycast Targets都必须由Graphic Raycaster进行测试,因此最佳做法是仅在必须接收指针事件的UI组件上启用“Raycast Target”设置。 Raycast目标列表越小,必须穿越的等级越浅,每个Raycast测试的速度越快。

对于具有多个必须响应指针事件的可绘制UI对象(例如希望其背景和文本都会更改颜色的按钮)的复合UI控件,通常最好将单个Raycast目标放置在复合UI的根目录控制。当单个Raycast目标接收到指针事件时,它可以将该事件转发给复合控件中的每个感兴趣组件。

层次深度(Hierarchy depth)和射线投射过滤器(raycast filters)

每个Graphic Raycast在搜索raycast过滤器时都会遍历Transform层次结构。该操作的成本与层次结构的深度成线性增长。attach到层次结构中每个Transform的所有组件都必须经过测试以确定它们是否实现了ICanvasRaycastFilter,因此这不是一个便宜的操作。

有几个使用ICanvasRaycastFilter的标准Unity UI组件,例如CanvasGroup,Image,Mask和RectMask2D,所以这种遍历不能被轻易消除。

Sub-Canvas和OverrideSorting属性

Sub-canvas上的overrideSorting属性将导致Graphic Raycast测试停止攀登变换层次结构。如果可以在不导致排序或光线投射检测问题的情况下启用它,则应该使用它来降低光线层次结构遍历的成本。

翻译自unity官方https://unity3d.com/cn/learn/tutorials/topics/best-practices/fill-rate-canvases-and-input

Pingbacks are open.

Trackback URL

Comments

1 Comments

  • faseomiCe
    19 Jul 2019, 20:09 | Reply
    viagra online without prescription http://genericonlineviaqra.com/ - cheap viagra online canadian pharmacy viagra online without prescription <a href="http://genericonlineviaqra.com/#">best place to buy generic viagra online</a> cheap viagra online canadian pharmacy http://genericonlineviaqra.com/
Post your comment

cancel reply