Unity手游性能优化的经验总结

Posted 肖远行

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity手游性能优化的经验总结相关的知识,希望对你有一定的参考价值。

一、定位游戏性能瓶颈

1.1 游戏循环

基本循环:游戏逻辑-渲染提交-等待渲染完成(注意:游戏逻辑指的是除去渲染之外所有的CPU运算)。
基本的游戏循环可以理解为先执行游戏逻辑,比如获得输入,然后更新玩家位置,播放动画,物理碰撞等,然后渲染引擎会将要渲染的游戏画面信息提交到GPU,CPU则等待GPU完成该一帧的渲染结果。

1.1.1 单线程渲染


上图可以看到主线程直接提交渲染命令GCMD给渲染设备。主线程需要等待图形设备渲染完成。

1.1.2 多线程渲染


主线程:游戏逻辑-提交渲染到渲染线程-等待渲染完成。
渲染线程:提交渲染命令给GPU-等待渲染完成。
GPU:等待渲染命令-执行渲染。

从上图可以看到,主线程将渲染命令封装后提交给渲染线程,渲染再提交给图形设备渲染画面。因此,渲染线程会比主线程延迟一帧。虽然新增了渲染线程来减少CPU等待渲染完成的时间,但是每一帧主线程还是会等待渲染线程完成上一帧的渲染。因此,还是可以按照基本的游戏循环来分析每一帧的运行过程。

1.2 利用工具确定执行时间

我们需要使用工具,比如UWA分析报告或者Unity的Profile等来确定主线程的游戏逻辑、渲染提交、渲染等待,这三个部分的整体执行时间。

  1. 或者利用工具获得GPU每帧耗时是多少,是否超过帧率的要求,比如30帧的话,那么一帧是33.3ms,GPU的每帧耗时就不能就不能超过33.3ms,或者不能比CPU的耗时多,否则就是GPU的性能瓶颈。
  2. 如果GPU不存在瓶颈,那么问题就在CPU上,这个时候要进一步分析CPU的瓶颈是游戏逻辑还是渲染提交上,这个UWA的性能分析报告和Unity的Profile都可以查看。
  3. 如果渲染等待时间过长,说明渲染指令过多,这个时候就需要减少渲染指令的提交,一般就是采样各种合批策略,比如SRP合批、静态合批等,或者是合并网格、减少摄像机距离或者层级Culling等。
  4. 如果渲染等待时间比较小,而游戏逻辑占用时间过长,那么要去分析CPU的耗时,具体是哪一块耗时过多,比如物理、动作、逻辑等。

二、GPU性能优化

当GPU遇到性能瓶颈时候就要着重进行GPU的性能优化,下面介绍一些常用的优化策略。

2.1 降低分辨率

很多手机的显示器分辨率过高,GPU性能却跟不上。因此,降低分辨率或者对分辨率做限制也是常见的优化手段。比如,最高档设置可以限制1080屏幕高度,依次递减,最低档的设备720就可以。

2.2 减少OverDraw

OverDraw的意思是一个像素被重复绘制的次数,也就是该像素位置重复执行像素着色器(片元着色器)的次数。我们可以利用工具,比如固定管线的Unity编辑器是可以显示场景的OverDraw的,Urp的需要做一些扩展支持,来可视化游戏的OverDraw。对OverDraw特别高的部分要想办法优化,下面介绍一些Urp管线下的优化策略。

2.2.1 自定义渲染Pass

Urp的渲染Pass是渲染顺序中优先级最高的,因此同一个Pass对应的物体都会在一起按照一定的顺序渲染。那么,为了减少OverDraw或者提高合批的命中,要合理的设计相关的渲染Pass。比如,天空盒是最远的物体,会一直被遮挡,这种就没必要在场景或者角色之前渲染,否则会有没必要的OverDraw。那么,就可以对天空盒单独一个Pass,在场景和角色之后执行。同理,如果角色一直靠近摄像机,也可以强制角色在场景之前的Pass渲染。

2.2.2 渲染队列

渲染队列是在Shader中设置的,属于同一个Pass内的渲染顺序微调。同样的,还可以设置Render的sortingOrder等。类似的,在默认管线下,通常会调整天空盒的渲染队列为不透明物体之后来避免OverDraw。

2.2.3 合理的渲染顺序

Urp渲染管线对不透明物体会按照从前到后渲染(不支持GPU隐藏面消除),透明物体按照从后到前渲染。Shader的RenderType定义是半透明还是不透明物体。另外前述的渲染Pass和渲染队列等也会影响渲染顺序。合理的渲染顺序能够提高Early-Z的命中率,减少OverDraw。

2.2.4 减少大范围或者全屏特效

大范围的特效是OverDraw的杀手,尤其特效反复重叠的情况,项目中最好从设计层面规避这种情况的出现,实在没办法的再想办法优化特效本身的执行效率,比如特效的粒子数或者面片数、Shader复杂度等。

2.2.5 减少UI的重叠层数

由于UI是按照半透明物体渲染的,因此要尽可能减少UI的重叠。下面的UI要隐藏而不是被覆盖。不过,这一点通常要在UI框架中做好解决,因为不仅仅是有OverDraw,UI的网格计算等CPU消耗也很大。

2.3 提高Early-Z Test的命中率

理论上来说,从前到后渲染就是尽可能的提高Early-Z的命中率,从而降低OverDraw。所以,这一项优化通常是和优化OverDraw是一起进行的。

2.3.1 减少Alpha测试

但是Alpha-Test,也就是在Shader中丢弃像素,即在片元着色器中执行Discard指令,可能会破坏Early-Z Test,因为硬件不执行片元着色器就无法预测最终的深度。因此,要尽量避免大范围的Early-Z Test,除非特殊的渲染要求,比如溶解、植物、头发等,不要使用Alpha-Test。

2.4 减少半透明物体

半透明物体一个是渲染顺序必须从后到前,因此OverDraw严重,性能肯定比不上不透明物体。按照虚幻的官方文档,不透明最快,蒙版(Alpha测试)其次,半透明最慢(OverDraw严重)。

2.5 降低GPU带宽

2.5.1 压缩纹理

  1. 纹理压缩格式:比如现今基本都支持ASTC纹理压缩,通过合理设置不同资源的压缩率,尽可能压缩纹理大小。
  2. 纹理大小:一些贴图根本不需要过大的尺寸,但是美术导入的原始资源通常过大,因此可以在资源导入脚本中强制压缩到一定的尺寸或者写工具扫描压缩一遍。
  3. 去除不必要的纹理通道:比如Alpha通道或者灰度图改成单通道

2.5.2 减少纹理采样次数

  1. 尽可能关闭各向异性纹理
  2. 降低纹理质量
  3. UI或者角色展示场景关闭Mipmap

其中1和2都可以在工程设置的Quality中选择,可以根据不同的设备等级来选择不同的设置。3的话需要在贴图中设置,可以通过贴图导入脚本来设置。

2.5.3 提高纹理缓存命中率

  1. 减少贴图尺寸
  2. 开启Mipmap
    GPU的片内缓冲大小是有限的,因此尽可能小的贴图或者Mipmap才更可能被缓存命中。纹理被缓存命中,那么读取速度会比从内存中读取快一个数量级。

2.5.4 压缩网格

  1. 尽可能减少网格大小:比如限制面数、顶点数
  2. 开启顶点压缩
    网格的大小也会影响带宽,但是更多的是会影响渲染面数,从而增大GPU的负担。

2.5.5 减少全屏Blit

尽可能减少全屏特效,或者合并全屏特效的计算,减少全屏Blit的次数。

2.6 Shader优化

2.6.1 降低Shader中的数值精度

现在版本的Shader中已经不需要使用fixed类型,实际上Urp的Shader中也会编译失败。尽可能使用half类型,减少float类型的使用,float类型主要是用在postion和uv上,对应颜色等属性值尽量都用half。精度更大的话,计算时间就更多。

2.6.2 减少Shader中的分支和循环

尽可能避免分支出现,尽量不要使用循环。分支会破坏Shader的并行,严重影响Shader的执行效率。尽可能用其它方式替换,比如条件操作符、乘法等。

2.6.3 降低Shader的计算复杂度

分析一些OverDraw高或者屏幕占比高的Shader,尽可能或者根据项目要求来简化其计算复杂度,或者利用Shader LOD,写几个简化版本的Shader来对应中低端机器。

2.6.4 减少纹理的读取次数

在Shader中尽量减少纹理的读取次数,比如控制贴图对应的纹元可以一次性读取到变量中,不要反复读取。

2.6.5 Shader LOD

Unity的Shader支持LOD,可以针对中低端机器编写简化版本的SubShader,然后针对性的运行时切换到简化Shader运行。

2.7 尽可能剔除不必要的物体渲染

  1. 合理设置摄像机远平面距离,不渲染远处的物体
  2. 给物体设置不同的Layer,给不同的Layer设置不同的摄像机裁剪距离
  3. 使用Unity自带的遮挡剔除(要烘焙数据,占用额外的包体和内存,可能占用额外的CPU,一般不建议使用)
  4. 自定义的剔除算法,比如检测到物体超过摄像机多远,不渲染或者只渲染部分效果(可以参考项目内的WorldCullingManager,这个是利用Unity自带的CullingGroup来检测角色的距离变化,从而缩放角色的渲染效果)。

2.8 LOD

2.8.1 模型LOD

模型也可以使用Lod,默认情况下是随着摄像机距离变大切换到更简单的Lod。不过,也可以根据任意条件来切换Lod,比如机型匹配,帧率下降等。

2.8.2 动画LOD

可以在切换模型LOD时候,选择更简单的动画状态机,这个Unity是支持分层动画和Mask的,具体参考相关文档。

2.8.3 渲染LOD

  1. 不同的距离开启不一样的渲染效果,类似模型LOD,远处的物体采用更简单的渲染方式。
  2. 不同档次设置采用不同级别的渲染效果,比如切换Shader Lod,关闭一些Pass等。
  3. 不同重要度的物体,比如小怪等,可以关闭一些渲染特性或者效果,或者使用更低的Shader Lod等
  4. 同理,类似的其它一些缩放方式。

2.9 Fake渲染

2.9.1 假阴影

比如角色下面的影子是一个黑色面片,或者使用平面阴影(Planar Shadow)。

2.9.2 公告板

比如公告板技术实现的远处面片树木、面片房子等

三、CPU性能优化

按照第一节的游戏循环的说法,CPU的性能优化主要包括:游戏逻辑和渲染提交。或者更准确的说,应该把CPU性能优化分为渲染优化和其它的优化;其它的优化,主要指的是游戏逻辑相关的优化,具体包括:物体、动画、粒子、UI、资源加载、游戏逻辑(游戏层脚本)、GC等。下面,我们来一个个的做一些经验介绍。

3.1 渲染优化

CPU上的渲染主要包括两个部分,一个是计算需要渲染的物体,另一个是提交渲染和等待渲染完成。

3.1.1 Culling优化

Culling也可以叫做裁剪,实质上裁剪有很多种算法或者方式。从现今的游戏引擎来说,一般是使用层次包围盒(Bounding Volume Hierarchy),来粗略的和摄像机的可视范围做交集来进行裁剪。这一部分通常是在游戏引擎内的,因此我们不能控制。但是,SRP渲染管线提供了一些可能性来做优化。

  1. 隔帧Culling:如果游戏场景更新不频繁,那么可以隔帧或者过几帧才计算一次Culling。
  2. 修改Urp的Renderer中的SetupCullingParameters:该函数控制渲染引擎的裁剪参数,可以尝试修改该函数以提高特定项目的裁剪效率。
  3. 在主线程中自定义Culling逻辑:和2.6的第四点意思一样。

3.1.2 渲染批次优化

本质上是优化主线程提交给渲染线程的指令数目,从而减少渲染线程提交给图形接口的渲染指令数目。对应到Unity上则是各种合批策略的体现。

  1. 自定义Pass:对于要合批的物体,尤其是动态物体,比如特效,最好是定义一个专门的Pass,就不会被其它不相干物体打断动态合批(对于SRP Batcher没发现这种限制)。
  2. SRP Batcher:尽可能所有的Shader兼容SRP Batcher,除非特别低端的机器,这种合批方式对性能的提高很大。
  3. 静态合批:静态合批通常用于场景(SRP Batcher实际上也兼容静态合批),不过静态合批需要打开网格读写,静态合批只合并材质一样的物体,因此静态合批需要美术那边尽量提高材质的重用度后使用的意义才大;同时因为静态合批会合并网格(比如,同样的网格和材质出现多次,会将网格复制多次合并成一个更大的网格)可能会导致包体和内存显著增长;因此,静态合批最适合材质重复度高,网格重复很少的场景。
  4. 动态合批:静态合批的运行时版本,因为有运行时的CPU消耗,类似静态合批也可能会增大内存消耗;有限制,比如要求顶点属性之和不超过900等;适合于动态的小物体的合批,比如粒子特效、小道具等。
  5. Draw Instancing:也叫实例化渲染,适合的场景是网格重复多次(只是朝向、缩放等不一样),材质一样(或者材质属性基本一致的)的情况,比如大规模渲染树木和草地。
  6. 手动合并网格和材质:用软件来离线合并场景内的模型和材质;理论上来说,最自由但是最繁琐,如果场景小的话可以这样试试;合理控制的话,不会显著增大内存和包体,也没有运行时消耗,美术乐意的话,何乐而不为?

3.2 物理优化

3.2.1 降低Unity的物理更新频率

可以在工程设置的Physics选项中关闭Auto Simulation,然后选择在框架更新的时候降低频率(比如2倍的Fixed Timestep)来调用Physics.Simulate来更新物理。同样可以直接设置Fixed Timestep来降低更新频率。

3.2.2 少用或者不用MeshCollider

3.2.3 减少频繁射线检测的使用

可以缓存计算结果或者用更快速的检测方式替代,比如boxcast。

3.2.4 关闭碰撞矩阵中没必要的部分

3.3 动画优化

3.3.1 限制骨骼数目

要求美术制作时候在规定的骨骼数目范围内,骨骼数目会影响动画大小也会影响执行效率。通常80-100已经非常足够了。

3.3.3 动画的CPU性能优化

参考UWA的文章:Unity性能优化 — 动画模块
参考Unity文档:性能和优化

3.4 粒子优化

可以参考UWA的文章:粒子系统优化——如何优化你的技能特效
特效的优化一般在项目的中后期,快上线的时候,针对性的对战斗这种特效集中度很高的场景进行测试和优化。

3.4.1 限制特效的最大粒子数

通常会限制普通特效只能有5-10个粒子或者更小。

3.4.2 限制特效的批次

最好是一个特效能在几个批次或者1个批次内渲染完成。

3.4.3 限制特效使用的贴图尺寸

特效尽量使用小贴图,比如不超256或者512的,尽量都是128或者更细的贴图或者贴图合集。

3.4.3 限制特效的重叠层数和范围

这一部分应该算GPU的优化,可以减少OverDraw和GPU的计算。

3.5 UI优化

参考UWA的文章:Unity性能优化 — UI模块
优化UI的基本原则是:

  1. 减少UI变化重新生成网格:因为UI本质上也是网格加贴图绘制出来的,因此要避免各种操作或者设置到UI频繁变化导致网格重复生成。
  2. 减少UI的射线检测
  3. UI的图集控制:比如一个界面最多2个图集等
  4. 战斗这种3D场景可以不使用UGUI来绘制3D的UI,而是直接用3D网格来绘制,避免UGUI的各种性能消耗。

3.6 资源加载优化

参考UWA的文章:Unity性能优化系列—加载与资源管理

3.6.1 Shader变体的预热

Shader变体的预热比较耗时,可能需要拆分处理。Shader需要优化关键字数目,尤其是全局关键字数目,这个会显著影响Shader的包体和加载进来的内存。

3.6.2 游戏对象池

尽量使用对象池,对象池回收时候可以隐藏GO,也可以选择移动到远处(关闭组件)。

3.6.3 资源管理方案推荐YooAsset

3.7 游戏脚本优化

这里主要讲的是游戏框架和游戏逻辑的代码优化,包括C#和Lua。

3.7.1 游戏框架优化

框架应该尽可能优化,尽可能减少对使用者(游戏逻辑层)带来的性能损耗。

  1. 框架代码尽量不要有GC
  2. 框架代码尽量不要占用额外的大内存
  3. 框架代码尽量不要消耗过多的CPU,使用者不用担心性能消耗

3.7.2 游戏逻辑优化

具体的游戏逻辑优化,跟实际的游戏类型有关,需要针对性优化。

3.7.3 常见的脚本优化策略

  1. 缓存计算结果:中间结果或者初始参数尽量预计算好
  2. 不产生GC:任何引用类型的对象都尽量使用缓存池内的
  3. 对缓存友好的存储方式:比如尽量使用小数组存储数据,而不用链表或者字典
  4. 限帧法:限制部分逻辑的更新频率,比如2-3帧更新一次
  5. 多线程:部分独立性很强的逻辑,可以考虑多线程处理
  6. 主次法:比如非关键的角色或者物体,使用更少的计算逻辑
  7. 减少项目的MonoBehavior的更新函数入口,游戏逻辑尽量保持一个更新入口。

3.7.4 Lua代码优化

参考UWA的文章:Unity性能优化系列—Lua代码优化

3.8 GC优化

由于Unity的Mono堆在超过最大值或者一定数值后会自动扩容,而且扩容后无法往回缩,因此必须非常关注Mono堆的峰值。

  1. 降低Mono堆的峰值:这样可以避免Mono堆一直增长或者过大
  2. 降低GC的频率:减少不必要的CPU消耗,尽量使用缓存池中的引用对象

四、资源优化

4.1 纹理优化

这个在优化GPU代码有提到。

  1. 合理降低纹理大小
  2. 尽量使用更高的压缩格式(ASTC更高压缩率)
  3. UI或立绘关闭Mipmap
  4. 减少纹理通道
  5. 提高纹理复用(单色图复用,重复图案复用)

4.2 网格优化

  1. 关闭网格读写:除了特效外的网格关闭读写
  2. 开启项目的顶点压缩:会降低内存和GPU消耗,应该不会影响资源大小
  3. MeshCompression:开启据说会降低网格的资源占用,但是不影响内存占用
  4. 尽量减少面数和顶点数:和美术制作规范,正式资源要符合要求

4.3 动画优化

  1. 压缩方式选择Optimal:官方推荐的方式
  2. 如果没有缩放,去除Scale曲线。
  3. 网上的一些剔除动画原始数据的方法以实测为准,可能剔除后动画文件变小,包体变小了,但是内存中大小不变。

五、内存优化

5.1 优化资源

资源本身都会加载进入内存,因此优化资源本身大小对优化内存大小非常关键,第四节已经讲到。

5.2 优化打包和资源管理

减少打AB包时候的重复,以及智能的资源加载管理方案,可以减少AB包加载后的内存占用,以及去除没必要的资源常驻现象,同时也可以优化资源加载的CPU消耗。

5.3 优化Mono堆

前述已经提到Mono堆只增不涨,因此优化C#的Mono堆内存非常必要。

5.4 优化Native内存

一些插件包括引擎都会占用Native的内存,因此合理使用插件或者检测插件占用的Native内存在某些时候也有意义。如果插件造成的Native内存占用过多,是否可以考虑更换插件?比如音频插件等。

5.5 优化Lua内存

Lua同样有虚拟机有自己管理的堆内存,同样是不能无限增长的。因此,Lua代码也要避免频繁创建新的对象造成GC严重或者导致堆内存一直上升。
UWA和UPR都有检测Lua内存的选项,可以试试。

5.6 减少包体二进制大小

应用都会加载到内存中才会运行,因此更小的二进制包体自然会占用更小的内存。可以尝试剔除一些没有使用的代码(引擎代码或者C#脚本代码),这个Unity打包时候有相关设置。

5.7 配置优化

游戏项目到中后期配置可能会增大非常严重,如果一次性加载可能会造成加载时间过长,同时造成Mono内存增长过大。

  1. 避免一次性加载全部配置到C#中
  2. 如果内存占用过高,考虑其它压缩存储方式,比如二进制存储,不要使用Json
  3. Lua加载配置速度更快,但是配置过大同样内存占用高
  4. 实在占用过高,可以考虑小型数据库存储配置
  5. 是不是该让策划清理或者使用工具清理重复配置,配置本身是否严重冗余?

六、二进制包体优化

6.1 代码裁剪

Unity可以设置裁剪引擎代码,和脚本层代码。经过测试开启引擎代码裁剪问题不大,但是脚本层代码裁剪设置过高可能引起代码丢失问题,可能可以通过link.xml中的设置解决。

6.2 安卓架构

通常ARMv7和ARM64只需要打一个架构,当前ARM64的性能更好但是兼容性不够,关闭一个架构能减少包体。

6.3 其它跟App打包相关的方法

具体请查阅相关文档,参考并且实验是否有效。

七、其它

性能优化是一个迭代的长期工作,关键是底子打好,后期优化压力就小很多;或者明白优化的思路,能够快速定位关键的性能瓶颈。

如何快速优化手游性能问题?从UGUI优化说起

WeTest 导读
 
本文作者从自身多年的Unity项目UI开发及优化的经验出发,从UGUI,CPU,GPU以及unity特有资源等几个维度,介绍了unity手游性能优化的一些方法。
 

在之前的文章《手游内存占用过高?如何快速定位手游内存问题》中提到,Mono内存和native内存是PSS内存主要的组成部分,mono内存更多的起到内存调用的功能,因此常常成为了开发人员优化内存的起点;而在游戏的其他的进程中,同样有很多因素影响着游戏的性能表现。本文将从UGUI的优化角度,介绍unity游戏性能优化的一些内容。

 

一、UGUI简介

UGUI是Unity官方推出的UI系统,集成了所见即所得的UI解决方案, 其功能丰富并且使用简单,同时其源代码也是开放的,下载地址:https://bitbucket.org/Unity-Technologies/ui/src

 

相比于NGUI,UGUI有以下几个优点:

1. 所见即所得的编辑方式,在Scene窗口中即可编辑。

2. 智能的Sprite packer可以将图片按tag自动生成图集而无需人工维护,生成的图集合并方式比较合理,无冗余资源。

3. 渲染顺序与GameObject的Hierarchy顺序相关,靠近根节点显示在底层,而靠近叶子节点显示在顶层;这样的渲染方式使得调整UI的层级比较方便和直观。

4. RectTranForm及锚点系统更适合于2D平面布局,并且非常方便多分辨率屏幕自适配。

 

二、UI制作规范和指导方法

本文是关于UGUI优化的,或许你会觉得UI的制作规范及指导方法与优化无关,其实很多性能问题往往是资源的不合理使用造成的,比如使用了尺寸过大的图片、引用了过多的图集以及加载了不必要的资源等。如果从设计和制作UI一开始就遵守特定的规范,则可以规避不必要的性能开销。笔者根据参与的多个项目总结了以下几点通用的规范和指导方法(这些规范适用于所有项目,不管你使用UGUI还是NGUI)。

 

1. 合理的分配图集

合理的分配图集可以降低drawcall和资源加载速度;具体细节如下:

● 同一个UI界面的图片尽可能放到一个图集中,这样可以尽可能的降低drawcall。

 

● 共用的图片放到一个或几共享的图集中,例如通用的弹框和按钮等;相同功能的图片放到一个图集中, 例如装备图标和英雄头像等;这样可以降低切换界面的加载速度。

 

● 不同格式的图片分别放到不同的图集中,例如透明(带Alpha)和不透明(不带Alpha)的图片,这样可以减少图片的存储空间和占用内存。(UGUI的sprite packer会自动处理这种情况)

 

2. resources目录中应该只保存prefab文件,其它非prefab文件(例如动画,贴图,材质等)应放到resource目录之外

因为随着项目的迭代,可能会导致部分资源(动画,贴图)等失效,如果这些文件放在resource目录下,在打包时,unity会将resource目录下文本全部打成一个大的AssetBundle包(非resouce目录下的文件只有在引用到时才会被打到包里),从而出现冗余,增加不必要的存储空间和内存占用。可以通过以下代码(Mac环境下)在控制台窗口中查看当前目录下所有非prefab资源的代码:

find . -type f | egrep -v "(prefab|prefab\\.meta|meta)$"

 

例如在笔者的一次扫描中,发现在了如下结果:

 

 

3. 关卡内的UI资源不要与外围系统UI资源混用

在关卡内,需要加载大量的角色及场景资源,内存比较吃紧,一般在进入关卡时,都会手动释放外围系统的资源,以便使关卡内有更多的内存可以使用。如果战斗内的UI与外围系统的UI使用相同图集里的图片,则有可能会使得外围系统的图片资源释放不成功。对于关卡内与外围共用的UI资源需要特殊处理,一般来说复制一份出来专门给关卡内使用是比较好的选择。

 

4. 适当的降低图片的尺寸

有时UI系统的背景可能会使用全屏大小的图片,比如在Iphone上使用1136*640大小的图片;使用这样尺寸的图片代价是很昂贵的,可以和美术同学商量适当的降低图片的精度,使用更低尺寸的图片。

 

5. 在android设备上使用etc格式的图片

目前,几乎所有android设备都支持etc1格式的图片,etc1的好处是第个像素点只战用0.5个字节而普通rgba32的图片每个像素点占4个字节,也就说一张1024*1024图片如果使用rgba32的格式所占用的内存为4M而etc1格式所占用的内存仅为0.5M。但是使用etc1格式的图片有两个限制——长和宽必须是POT的(2的N次方)并且不支持alpha通道,因此使用etc1时需要额外的一张图来存储alpha通道,并且使用特殊的shader来对alpha采样。具体的细节可参考:http://malideveloper.arm.com/resources/sample-code/etcv1-texture-compression-and-alpha-channels/

 

6. 删除不必要的UI节点、动画组件及资源

随着项目的迭代,可能有部分ui节点及动画已经失效,对于失效的节点及动画一定要删除,在很多项目中,有部分同学为了方便省事,只是将失效的节点及动画disable了。这样做虽然在运行时不会对cpu造成太多负担,但是在加载时会增加不必要的加载时间以及内存占用。对于废弃的UI图片资源,虽然未放到Resource目录最终不会打到包里,但是在Editor模式下仍然会打到图集中从而影响优化决策。笔者写了一个扫描未使用到UI贴图资源的工具,代码地址:https://github.com/neoliang/FindUnUsedUITexture;

 

另外,对于废弃的脚本,可能还会有某些对象持有对它的引用,而加载这样的对象也比较耗时,笔者也写了一个扫描废弃脚本的工具,代码地址:https://github.com/neoliang/MissingScriptFinder

 

三、CPU优化

一般来说,优化cpu性能应该先用profiler定位到性能热点,找到消耗最高的函数,然后再想办法降低它的消耗。经过笔者多次使用profiler对UGUI的分析来看,其CPU性能开销高主要原因之一是Canvs对UI网格的重建,有很多情况会触发Canvas对网格的重建,例如Image,Text等UI元素的Enable及UI元素的长、宽或Color属性的变化等。Canvas中UI Mesh顶点较多的话,则该项将会出现较高的CPU开销。在Unity的Profiler中则对应的是Canvas.SendWillRenderCanvases或Canvas.BuildBatch占用过多的时间。

 

Canvas.BuildBatch主要功能是合并Canvas节点下所有UI元素的网格,合并后的网格会缓存起来,只有其下面的UI元素的网格发生改变时才会重新合并。而UI元素的网络变化主要是因为Canvas.SendWillRenderCanvases调用时,rebuild了Layout或者craphic。该函数的调用过程时序图如下:

 

 

1.该过程由CanvasUpdateRegistry监听Canvas的WillRenderCanvases(上图中1)而执行,主要是对前标记为dirty的layout和craphic执行rebuild。引起layout和graphic的dirty主要原因是因为Canvas树形结构下的UI元素发生了变化(例如增加删除UI对象,UI元素的顶点,rec尺寸改变等)调用了Graphic.SetDirty(实际上最终都会调用CanvasUpdateRegistry.RegisterCanvasElementForLayoutRebuild)。

 

2. 在rebuild layout之前会对Layout rebuild queue中的元素依据它们在heiarchy中的层次深度进行排序(上图中的2),排列的结果是越靠近根的节点越会被优先处理。

 

3. rebuild layout(上图中的3),主要是执行ILayoutElement和ILayoutController接口中的方法来计算位置,Rect的大小等布局信息。

 

4. rebulid graphic(上图中的4),主要是调用UpdateGeometry重建网格的顶点数据(上图中5)以及调用UpdateMeterial更新CanvasRender的材质信息(上图中6)。

 

基于以上UGUI的网格更新原理,我们可以做以下优化:

a. 使用尽可能少的UI元素;在制作UI时,一定要仔细查检UI层级,删除不不必要的UI元素,这样可以减少深度排序的时间(上图中的2)以及Rebuild的时间(上图中的3,4)。

 

b. 减少Rebuild的频率,将动态UI元素(频繁改变例如顶点、alpha、坐标和大小等的元素)与静态UI元素分离出来,放到特定的Canvas中。

 

c. 谨慎使用UI元素的enable与disable,因为它们会触发耗时较高的rebuild(图中的3、4),替代方案之一是enable和disableUI元素的canvasrender或者Canvas。

 

d. 谨慎使用Text的Best Fit选项,虽然这个选项可以动态的调整字体大小以适应UI布局而不会超框,但其代价是很高的,Unity会为用到的该元素所用到的所有字号生成图元保存在atlas里,不但增加额外的生成时间,还会使得字体对应的atlas变大。

 

 

 

e.谨慎使用Canvas的Pixel Perfect选项,该选项会使得ui元素在发生位置变化时,造成layout Rebuild。(比如ScrollRect滚动时,如果开启了Canvas的pixel Perfect,会使得Canvas.SendWillRenderCanvas消耗较高)

 

 

f. 使用缓存池来保存ScrollView中的Item,对于移出或移进View外的的元素,不要调用disable或enable,而是把它们放到缓存池里或从缓存池中取出复用。

 

g. 除了rebuild过程之外,UGUI的touch处理消耗也可能会成为性能热点。因为UGUI在默认情况下会对所有可见的Graphic组件调用raycast。对于不需要接收touch事件的grahic,一定要禁用raycast。对于unity5以上的可以关闭graphic的Raycast Target而对于unity4.6,可以给不需要接收touch的UI元素加上canvasgroup组件。

 

unity5.x 

 

 

unity4.6

 

 

 

四、GPU优化

一般来说,造成GPU性能瓶颈主要有两个原因:复杂的vertext或pixel shader计算以及overdraw造成过多的像素填充。在默认情况下UGUI中所有UI元素使用都使用UI/Defaut shader,因此在优化时可优先考虑解决Overdraw问题。Overdraw主要是因为大量UI元素的重叠引起的,查看overdraw比较简单,在scene窗口中选择overdraw模式,场景中越亮的地方表示overdraw越高(如下图)。

 

为了降低overdraw,可以做如下优化:

1. 禁用不可见的UI,比如当打开一个系统时如果完全挡住了另外一个系统,则可以将被遮挡住的系统禁用。

2. 不要使用空的Image,在Unity中,RayCast使用Graphi作为基本元素来检测touch,在笔者参与的项目中,很多同学使用空的image并将alpha设置为0来接收touch事件,这样会产生不必要的overdraw。通过如下类NoDrawingRayCast来接收事件可以避免不必要的overdraw。

3. public class NoDrawingRayCast : Graphic

4. {

5.     public override void SetMaterialDirty()

6.     {

7.     }

8.     public override void SetVerticesDirty()

9.     {

10.     }

11.     protected override void OnFillVBO(List<UIVertex> vbo)

12.     {

13.         vbo.Clear();

14.     }

}

 

五、总结

优化UGUI性能没有万能的方法,笔者这些经验总结也只能作为参考。优化性能往往是在各种选择之间做出平衡,比如drawcall与rebuild平衡、内存战胜与cpu消耗平衡以及UI图片精度与纹理大小的平衡等。每一次优化都有可能使得瓶颈出现在其它的环节上,要善于使用profiler,找到性能热点,对症下药。

 

六、关于资源占用问题

UI资源优化是UGUI性能优化的重点,腾讯WeTest也在资源方面提供了性能的测试。以下通过“纹理”资源,介绍腾讯WeTest性能测试在资源方面的测试情况。

1、登录http://wetest.qq.com/cube/ ,点击“Android版 下载”,或者在页面末尾扫描二维码直接下载腾讯WeTest的手游客户端性能分析工具Cube。打开工具,选择“Unity资源分析”。

2、上传测试报告后,我们可以通过测试报告,了解unity游戏的资源情况。

 

 

资源结论概况

进入资源数据的报告之后,首先可以看到所有资源数据的概况结果,总体上了解存在问题的数据,继续下拉,可以了解该指标的具体情况。

 

 

资源数据概况

 

下面将以“纹理资源”为例,对cube资源测试报告进行解读。

 

纹理资源

Cube测试报告的“纹理资源”,根据腾讯标准,是期望<50MB的,从下图可见,如果超出红色虚线,就说明纹理资源存在超标。

 

点击具体数据点,获取具体资源数据

 

另外,点击图表中的绿色线条中的具体数据点,可以看到这个点的当前数据,所有数据根据资源大小进行排序

 

所有数据根据资源大小进行排序

 

在这个表之下,有一个“资源大小top20”的表格,罗列了资源排名前20的资源内容。其中资源大小超过建议值的会呈现红色,资源大小非2的n次幂的呈现黄色。点击任意一个资源名称,可以在图表上观察这个资源所影响的区域:

 点击具体资源了解影响区域

 

 

了解资源调用的影响区域

 

 

 



 

针对手游的性能优化,腾讯WeTest平台的Cube工具提供了基本所有相关指标的检测,为手游进行最高效和准确的测试服务,不断改善玩家的体验。目前功能还在免费开放中

 

体验地址:http://wetest.qq.com/cube/ 

帮助中心:http://wetest.qq.com/help/documentation/10096.html 

如果对使用当中有任何疑问,欢迎联系腾讯WeTest企业qq:800024531

以上是关于Unity手游性能优化的经验总结的主要内容,如果未能解决你的问题,请参考以下文章

[Unity优化] Unity CPU性能优化 (难度3 推荐4)

Unity性能优化专题—腾讯牛人分享经验 (难度1 推荐3)

浅谈Unity的渲染优化: 性能分析和瓶颈判断(上篇)

Unity游戏项目性能优化总结 (难度3 推荐4)

Unity3D性能优化总结

小松教你手游开发unity实用技能unity性能问题查找方法