unity 官方ui优化,记录

1  一个脏的子Canvas不会强制他的父Canvas进行重绘操作,反之亦然。 (那就是父亲重绘时候不会强制子canvas重绘)。除非父canvas的变化导致子Canvas的大小调整时候
2  Canvas是可以嵌套的
Graphic 是ui中 最基础的类
4 layout组件,只依赖于RectTransform,并且只会 影响相关的RectTransfrom属性。 不依赖于Graphic , 可以独立于unityui的图形组件使用
5 layout和 graphic都依赖于 CanvasUpdateRegistry
6 layout 和 graphic 组件的更新都被称重构
 
渲染
7 unity ui的渲染方式是alpha混合的方式, 从后往前进行渲染
8 每个像素都会被采样, 即使是被其他不透明的ui覆盖。 在移动设备上,需要考虑优化, 不要超过gpu的填充率
 
 
canvas绘制
1 将ui元素的mesh和并且来, 发送渲染命令到unity的图形渲染管道。这个过程的结果会被缓存并且重复使用。 直到canvas被标记为脏的,或者是只要其组成的网格之一发生变化就会进行重绘
2 计算批次 ,需要按照深度对网格进行排序, 这个操作事多线程的,跟设备有很大关系
 
 
重构 graphic
1 重绘,重构,是在ui的布局或者是mesh发生变化的时候进行
2 CanvasUpdateRegistry中的PerformUpdate。 每当canvas组件调用WillRenderCanvases的时候, 都会被调用。 这个事件美珍都会调用一次
PerformUpdate三部走:
   · ICanvasElement.Rebuild  触发,脏layout被请求重构的时候
    ·剪裁组件mask,都被要求提出任何建材等额组件是通过  ClippingRegistry.Cull
    ·脏Graphic要求重构的时候
4 layout的重构和graphic的重构都被分成了多个步骤
    ·layout分成了三部: (PreLayout, Layout and PostLayout)
    ·graphic分成了两部 :( PreRender and LatePreRender
 
详细展开 layout rebuild
在unity的gameobject层次结构中,离着根节点更近的可能会改变嵌套在其中的任何布局的位置和大小,所以必须要先进性计算
对脏数据进行排序,根据在层级列表中的深度,如果深度高会被移动到前列。排序之后在进行重构
排序之后的layout组件会重构他们的布局,这就是layout组件控制ui元素位置和大小实际被改变的地方
 
详细展开Graphic rebuild
1 如果顶点数据被标记为脏数据, 那么网格将被重建
2 如果材质数据被标记为脏, 那么附加的canvas renderer的材质将会被更新
图形重构,并不以任何特定的顺序进行图形组件的列表,也不需要任何排序操作
 

优化工具使用
 
unity profile
 
学习使用upr
 
 
 

Fill Rate , Canvases and input 填充率, 画布和输入
 
填充率的问题
有两个方案可以用来减少GPU的管道的压力。
·降低片段着色器的复杂性。参见 "UI shaders and low-spec devices"一节以了解更多细节。
·减少必须进行采样的像素数量。
 
通常ui使用的是shader是标准化的, 最常见的问题是填充率过度的问题。 引起的原因是多个ui重叠和多个ui占据了屏幕的主要部分
 
 
为了减少ui的填充率和过度使用的问题,下面的两个解决方案
 
1  消除不可见的ui界面
最简单的方案是关闭gameobject,进行禁用。如果设置alpha为0来隐藏的话,其实该元素还是会被发送到GPU,还是会占用渲染时间。
 
2  简化ui机构
为了减少重建和渲染ui界面所需要的时间,尽可能的减少ui的数量
尽量不要使用 blended来改变游戏对象的颜色,尽量使用改变材质球的方式
不要像创建文件夹似的创建游戏对象,除非是为了构建组织你的场景
 
3  禁用不可见的相机输出
如果一个Canvas被设置为 "Screen Space – Overlay",那么无论场景中活跃的摄像机数量如何,它都会被绘制。
如果打开了一个不透明背景的全屏UI,世界空间相机仍然会渲染UI后面的标准3D场景。渲染器并不知道全屏的Unity UI会掩盖整个3D场景。
因此,如果一个完全全屏的UI被打开,禁用任何和所有被遮挡的世界空间相机将有助于减少GPU的压力,只需消除渲染3D世界的无用工作。
 
4 Majority-obscured cameras
许多 "全屏 "UI实际上并没有遮挡住整个3D世界,而是留下了一小部分世界的可见部分。在这种情况下,将世界的可见部分捕捉到渲染纹理中可能是更理想的做法。
如果世界的可见部分被 "缓存 "在一个渲染纹理中,那么实际的世界空间相机可以被禁用,而缓存的渲染纹理可以被画在用户界面屏幕的后面,以提供一个3D世界的假象。
 
5 基于构图的ui
将装饰元素放在背景上
如果一个图上面有一个带文字的按钮,那么gpu要采样三次,先是背景纹理的采样,再按钮背景的采样,最后是按钮上文字的采样。如果把装饰和背景放在一张图上,就会减少采样次数,提高效率
减少ui元素
弊端是专门的ui元素不能重复利用,会增加额外的内存
增加大型的新纹理可能会大大增加容纳UI纹理所需的内存量,特别是如果UI纹理不是按需加载和卸载的。    
 
6 ui着色器和低规格的 设备
Unity UI使用的内置着色器包含了对遮蔽、剪裁和许多其他复杂操作的支持。由于增加了这种复杂性,在低端设备(如iPhone 4)上,UI着色器与更简单的Unity 2D着色器相比,表现得很差。
如果遮蔽、剪切和其他 "花哨 "的功能对于针对低端设备的应用程序来说是不需要的,那么可以创建一个自定义着色器,省略不使用的操作,例如这个最小的UI着色器。
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 } }
 
 
 
 
UICanvas 重构
重构成为性能的问题是有两个原因
1 如果 Canvas 上可绘制的 UI 元素的数量很大,那么计算批处理本身就会变得非常昂贵。这是因为排序和分析元素的成本与 Canvas 上可绘制的 UI 元素的数量呈线性增长。
2 如果画布经常被弄脏,那么刷新变化相对较少的画布就会花费过多时间。
随着canvas上元素数量的增加,那么这两个问题将会变得很严重
 
重要提醒。每当某个 Canvas 上的任何可绘制 UI 元素发生变化时,该 Canvas 必须重新运行批量构建流程。
这个过程会重新分析 Canvas 上的每一个可绘制的 UI 元素,不管它是否有变化。
请注意,"变化 "是指影响 UI 对象外观的任何变化,包括分配给精灵渲染器的精灵、变换位置和比例、文本网格中的文本等。
 
Child orders 子对象的排列
Unity UIs是从后往前构建的,对象在层次结构中的顺序决定其排序。
层次结构中较早的对象被认为在层次结构中较晚的对象之后。批次的建立是通过从上到下的层次结构,收集所有使用相同材质、相同纹理且没有中间层的对象。
一个 "中间层 "是一个具有不同材质的图形对象,其边界框与两个可批处理的对象重叠,并被放置在两个可批处理的对象之间的层次结构中。中间层迫使批次被打破。
调整层级关系,尽量可以让层级上的游戏对象和批处理
 
Canvas 分割
重构中,分割画布是重构中有效的解决方案之一 ,但是canvas之间是不会进行和批处理的
需要衡量
 
由于 Canvas 在其任何组成的可绘制组件发生变化时都会进行重批,因此通常最好将任何非实质性的 Canvas 分成至少两部分。
此外,如果预期元素会同时发生变化,最好尝试将这些元素放在同一个 Canvas 上。一个例子是一个进度条和一个倒数计时器。这两个元素都依赖于相同的底层数据,因此需要同时更新,所以它们应该放在同一个 Canvas 上。
在一个Canvas上,放置所有静态和不变的元素,如背景和标签。这些元素将在Canvas首次显示时批处理一次,之后就不再需要重新批处理。
在第二个Canvas上,放置所有 "动态 "元素--那些经常变化的元素。这将确保这个Canvas主要是对脏元素进行重新匹配。如果动态元素的数量非常多,可能有必要将动态元素进一步细分为一组不断变化的元素(如进度条、计时器读数、任何动画)和一组仅偶尔变化的元素。
这在实践中其实是相当困难的,尤其是在将UI控件封装成预制板的时候。许多UI选择通过将成本较高的控件拆分到子画布上来细分画布。
 
 
UI中的输入和射线
unityui中使用Graphic Raycast处理输入的事情
5.4以前的版本即使没有鼠标输入,也会检测鼠标位置,5.4之后的版本没有了
Raycast优化
Graphic Raycaster是一个相对直接的实现,它在所有将 "Raycast Target "设置为 "true "的Graphic组件上进行遍历。
对于每一个广播目标,Raycaster执行一系列的测试。如果一个Raycast Target通过了所有的测试,那么它就会被添加到点击列表中。
 射线的实施:
如果可被射线检测的对象,在他的上面有被不能被射线检测的对象覆盖的话, 那么射线是穿透不了 的
对于不可见的游戏对象,也会被从可被检测的列表中移除
 
射线的优化
只在允许被用户操作的游戏对象上面设置为Raycast Target
Raycast目标的列表越小,必须穿越的层次越浅,每个Raycast测试就越快。
对于有多个必须响应指针事件的可绘制UI对象的复合UI控件,
例如一个希望其背景和文本都改变颜色的按钮,通常最好在复合UI控件的根部放置一个Raycast Target。
当这个单一的Raycast Target收到一个指针事件时,它可以将事件转发给复合控件中的每个感兴趣的组件。
 
层次结构深度和射线传输滤波器
在搜索射线广播过滤器时,每个图形射线广播都要遍历Transform层次结构,直到根部。
这一操作的成本与层次结构的深度成线性增长。所有在层次结构中发现的附属于每个Transform的组件都必须经过测试,看它们是否实现了ICanvasRaycastFilter,所以这不是一个便宜的操作。
有几个标准的Unity UI组件使用了ICanvasRaycastFilter,如CanvasGroup、Image、Mask和RectMask2D,所以这个遍历不能简单地消除。
子画布和 OverrideSorting 属性
子画布上的overrideSorting属性将导致Graphic Raycast测试停止攀登变换层次。如果可以启用它而不引起排序或射线广播检测问题,那么就应该使用它来降低射线广播层次结构的遍历成本。
 

UI Control
Ui组件大体都差不多,uitext,uitext mesh pro 这两个比较耗费性能
 
UIText:
文本字形实际上是作为单独的四边形被渲染的,每个字符一个。这些四边形往往在字形周围有大量的空隙,这取决于它的形状,而且很容易将文本定位在这样一种方式,即无意中破坏了其他UI元素的批处理。
文本网格的重建 
文本组建改变的时候,会进行重构
当任何父级的gameobject被打开或者禁用的时候也都会被重构
这种行为对于任何显示大量文本标签的用户界面都是有问题的,最常见的是排行榜或统计屏幕
动态字体和字体图集:
在Unity的实现中,这些字体在运行时根据UI Text组件中遇到的字符建立一个字形图集。
如果同一个字,但是如果一个显示正常,一个显示加粗的话, 那也是会被分在两个图集,包括字体大小不一致的话也被分在两个图集
每当一个具有动态字体的UI Text对象遇到一个尚未被光栅化到字体纹理图集中的字形时,字体的纹理图集必须被重建。
如果新的字形适合当前的图集,它就会被添加,并且图集会被重新加载到图形设备上。但是,如果当前的图集太小,那么系统就会尝试重建图集。它分两个阶段进行。
请注意,字体图集的重建是为每个被改变的UI Text组件单独触发的。
当填充大量的文本组件时,收集文本组件内容中的所有独特的字符并为字体图集填充可能是有利的。这将确保字形图集只需要重建一次,而不是每次遇到新的字形时都要重建一次。
还要注意的是,当一个字体图集的重建被触发时,任何目前不包含在一个活动的UI Text组件中的字符将不会出现在新的图集中,理解也就是说如果当前图集重构的时候uitext没有当前字符的话,即使之前有也不会被构建进去
即使它们最初是由于对Font.RequestCharactersInTexture的调用而被添加到图集中的。为了解决这个限制,可以订阅Font.textureRebuilt委托,并查询Font.characterInfo,以确保所有想要的字符都被引出。
public void TextureRebuiltCallback(Font rebuiltFont) { /* ... */ }
 
什么后备字体看不懂
Fallback fonts and memory usage
For applications that must support a large character-set, it is tempting to list a large number of fonts in the “Font Names” field of a font importer. Any fonts listed in the “Font Names” field will be used as fallbacks if a glyph cannot be located within the primary font. The fallback order is determined by the order in which the fonts are listed in the “Font Names” field.
However, in order to support this behavior, Unity will keep all fonts listed in the “Font Names” field loaded into memory. If a font’s character set is very large, then the amount of memory consumed by fallback fonts can become excessive. This is most often seen when including pictographic fonts, such as Japanese Kanji or Chinese characters.
 
最佳适配和性能
一般来说,UI文本组件的 “Best Fit”设置最好不要使用。
“Best Fit"动态地将字体的大小调整为最大的整数点尺寸,该尺寸可以在文本组件的边界框内显示而不会溢出,并夹在一个可配置的最小/最大点尺寸中。
然而,由于Unity为显示的每个不同大小的字符在字体图集中渲染一个不同的字形,使用 “Best Fit"会使图集迅速被许多不同的字形尺寸所淹没。
频繁的字体图集重建会迅速降低运行时的性能,也会导致内存碎片化。设置为最佳匹配的文本组件的数量越多,这个问题就越严重。
 
Text Mesh Pro 
使用Signed Distance Field (SDF)作为其主要的文本渲染管道,使得它可以在任何点的大小和分辨率下干净地渲染文本
 
字体和内存的使用:
tmp没有动态字体功能,必须依靠fallback font
tmp中字形的发现是递归进行的, 也就是说 ,当tmp字体资产中缺少一个字形的时候吗, tmp会从列表中的第一个回退开始,通过他们自己的回退, 遍历当前分配或者是激活的回退字体
如果还是找不到会搜索文本对象的spirte,以及分配给这个sprite的任何fallbacks
如果仍然找不到的话, 将会在tmp设置文件中指定的一般fallbask中进行递归搜索,然后是默认的sprites
如果还是找不到,就搜索在tmp设置中指定的默认字体资产,最后手段是, tmp将使用并且显示tmp设置文件中定义的确实字形替换符号
 
tmp的字体资源在场景或者是项目中被引用时候加载
此外,当TextMeshPro组件在一个给定的场景中引用了一个字体资产,并且没有通过TMP设置加载,那么一旦该组件被激活,被引用的字体资产及其所有后备字体资产将被递归加载。
建议采用模块化的方式加载字体资源
 
最佳适配 Best Fit 和性能问题
因为textmeshpro没有动态字体的功能,所以不会存在text中创建多个图集的情况
唯一要考虑的问题就是, 要用二进制搜索来寻找正确的尺寸, 当使用文本自动调整大小时候,最好是是测试最长、最大文本的最佳尺寸。一旦确定了这个最佳尺寸,在给定的文本对象上禁用自动尺寸,然后在其他文本对象上手动设置这个最佳点尺寸。这样做的好处是可以提高性能,避免一组文本对象使用不同的点尺寸,这被认为是不良的视觉/排版做法。
 
Scroll Views
继填充率的问题之后, 滚动视图是运行时性能问题第二个常见的来源
滚动视图通常需要大量的UI元素来表示其内容。有两种基本的方法来填充一个滚动视图。
1 用所有必要的元素来表示滚动视图的所有内容来填充它
2 集中这些元素,根据需要重新定位,以表示可见的内容。建立pool?
这两种解决方案都有问题。
第一个解决方案需要越来越多的时间来实例化所有的UI元素,因为需要表示的项目数量增加了,
同时也增加了重建滚动视图的时间。如果在一个滚动视图中只需要少量的元素,例如在一个只需要显示少量文本组件的滚动视图中,那么这种方法因其简单性而受到青睐。
第二种解决方案需要大量的代码才能在当前的UI和布局系统下正确实现。
 
当任何ui元素被重新设置父节点,或者改变兄弟姐妹的顺序时,该元素以及所有的子元素都被标记为脏,并且强制重构Canvas。其原因是Unity没有分离回调,用于重新parent一个变换和改变其兄弟姐妹的顺序。这两个事件都会触发一个OnTransformParentChanged回调。
通过弄脏Graphic,系统确保Graphic将在下一帧渲染前重建其布局和顶点。
可以将画布分配给Scroll View中每个元素的根RectTransform,这样就可以将重建限制在只有重新parent的元素,而不是Scroll View的整个内容。
//可以理解为scrollview的每一个item给一个canvas,当单个item上元素数量比较多的时候比较适用
然而,这往往会增加渲染滚动视图所需的绘制调用的数量。此外,如果滚动视图中的单个元素很复杂,由十几个图形组件组成,特别是如果每个元素上有大量的布局组件,那么重建它们的成本往往很高,足以明显降低低端设备上的帧速率。
 
基于位置的滚动视图
为了避免上述问题,我们可以创建一个滚动视图,通过简单地移动其包含的UI元素的RectTransforms来汇集其对象。
这就避免了在移动的RectTransforms的尺寸没有改变的情况下重建其内容的需要,从而大大提高了滚动视图的性能。
为了实现这一点,一般来说,最好是编写一个自定义的Scroll View子类或编写一个自定义的Layout Group组件。
后者通常是比较简单的解决方案,可以通过实现Unity UI的LayoutGroup抽象基类来完成。
自定义Layout Group可以分析底层源数据,以检查有多少数据元素必须被显示,并可以适当地调整Scroll View的Content RectTransform的大小。
然后,它可以订阅滚动视图的变化事件,并使用这些事件来相应地重新定位其可见元素。

其他ui优化技术和技巧  Other UI Optimization Techniques and Tips
 
layout组件是相对来说比较昂贵的, 每一次标记为脏的时候,重新计算子元素的大小和位置
 
禁用画布:
当ui的根部启用或禁用的时候,会导致canvas丢弃他的vbo(顶点缓存数据),重新启用需要canvas运行重构和重构匹配过程,如果这种情况经常发生,增加的cpu使用率会导致应用程序的帧率停止
一个方案就是把要显示、隐藏的ui放到他自己的canvas上,然后只在这个对象上启用,禁用canvas组件。
这种方法会导致ui网格不被绘制,但是仍然驻留在内存中。
 这不会隐藏ui中的任何monobehaviours, 因此这些monobehaviours仍然会接受unity生命周期的回调。
为了避免这个问题,以这种方式禁用的ui上的monobehaviours不应该直接实现unity的生命周期回调,应该从ui的根部gameobject上的“callbackmanager”接受他的回调。 可以在ui显示隐藏时候得到通知, 
并且可以确保生命周期事件在必要时候被传播或不传播。
 
 
 
 
 
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值