1. 纹理格式的选择
- 图片是够进行了有效压缩
- 关于Android平台,ETC1(不带透明通道)> ETC2(带透明通道) > RGBA16(显示质量增加) > RGBA32。
- Crunched,是Unity针对Android平台实现的一套压缩方式,比如RGBA Crunched ETC2,会先使用ETC2进行压缩哦,再使用Crunched压缩一边。缺点是需要额外解压缩一遍,优点是加载更快,包体更小。
- IOS平台,PVRTC>ASTC>RGBA16>RGBA32。ASTC 55(不带透明通道),ASTC44(带透明通道)
- ETCI、ETC2以及ASTC4x4要求图片宽和高必须被4整除。IOS的PVRTC要求图片宽高必须相等并且是2的整数次幂。ETC2和ASTC对硬件也有一些显示,ETC2只支持OpenGL ES 3.0以上的Android手机,ASTC只支持苹果A8以后的设备
ASTC纹理压缩格式详解
2. 研发流程中的UI资源问题
打开一个界面只需要把对应的图集加载到内存中,如果该界面引用到其他图集中图片,哪怕是一张,对应的整个图集都会加载到内存中,假设该图集占用内存为18m,那我们就相当于白白浪费了18m的内存。
- 尽可能的将多系统公用的图片放在公共图集中
- 对于系统图集来说,宽高一边超过256或者128的图片应该从图集中拿出来单独加载它(?)
- 对于系统UI来说,接受DC的开销来换取打开速度
比较图片文件的MD5可以判断图片是否相等
3.UI操作卡顿
常见问题:
- APK包体过大
- 内存占用过大(致应用闪退)
- Draw Call过多导致性能问题
- 界面之间切换不流畅
- 动画卡顿掉帧
- 顶点过多造成游戏卡顿
等等
一些优化思路:
- 资源界面预加载
- Http图片加载与下载(公告大图片的问题,需要确认是否清除过)
- 内存泄漏
- 声名无用静态非静态变量。
- 无用的引用没有置空(上下线的时候!)
- 数据集合或资源列表没有合理的进行清理
- 集合中储存着无引用的图片资源
- 内存中加载多份相同的资源
- 无引用的资源或图片还保留在内存中(可以通过Profile查看资源引用情况)
- 场景跳转没有进行合理的内存释放与清理(比如大厅跳游戏,如果不进行释放与清理就会导致大量大厅的资源常驻在内存中)
- 过量的资源或Bundle预加载
- 进行动静分离拆分Canvas,把频繁移动的放到动态Canvas下,一方面可以降低静态元素的更新频率,另一方面可以减小重建时涉及到的Mesh大小(重建是以Canvas为单位进行的)【还没有运用过】
- 避免频繁的SetActive界面,可以用CanvasGroup进行代替
- 尽可能的避免使用Unity自带的OutLine组件,该组件会使得文本的Mesh增加4倍,导致UI重建开销明显增大
- 尽可能的避免使用Unity自带的Shadow组件,该组件同样会使得Mesh增加顶点增多。
- 谨慎使用Text的Best Fit选项,代价很高,增加额外生成时间。(禁Best Fit)
- Mask换Rect Mask 2D或者网上的Mask组件。mask以内和以外把UI分割成两个“世界”,依次计算两个“世界”的drawcall,然后再相加。所有能不用就不用。
- 避免Image Text 不需要RayCast Target的组件开启该功能,因为UGUI在手指点击的时候,其实是Cavas上挂的一个Graphic Raycaster组件去遍历整个Cavas下所有UI元素的RayCast Target属性是否允许点击,我们把不需要点击的组件勾掉,会节省不小的性能开销。【最好提供脚本自动检测修正】
- 图集一定要规范,不要一个界面用很多图集中的图片, 影响drawcall数量的根本是batch(批处理数),而batch是根本一个一个图集来进行批处理的,如果相邻的两张Image不是一个图集,那么就会造成两个Batch。
- 图文交叉,比如 image1 image2 Text 这样的顺序下是两个Drawcall,如果换成 Image1 Text Imgae2 由于Text会打断合批,所以即使两个Image是同一个图集中的图片,也会产生3个Drawall。
- 避免Text RichText 属性,当Text组件勾选了Rich Text的时候,其实每次文本显示或赋值时都会先判断一下是否拥有这个属性,然后做正则匹配。如果频繁赋值,也是一笔不小的开销。
- 无用的Image以及Sprite为空的Image会打断合批,要谨慎使用。【最好提供脚本自动检测修正】
- 如果界面不显示了,该界面的一切周期函数比如Update一定要停掉。
- Image颜色渐变和可以用material控制,本质是更改Tint属性,这样既能满足颜色渐变,又能避免网格频繁重建造
- 避免频繁调用GameObject.SetActive(解决办法可以对频繁切换激活状态的UI采用平移出屏幕、修改Layer等方式来替换。)
- 不要把image button作为点击,很多人在创建button时用很多不可见的Image作为交互响应的控件,这些image虽然被alpha被设置为0不可见,但是DrawCall依然存在
4.UI优化:动态图集
- 对于系统UI开发来说,为了减少DC,通常希望将与该系统相关的所有图片资源合并到一个图集中,减少合并批次,但是实际开发过程中,UI资源通常会被分为commonatlas和系统atlas两类,一个界面Prefab至少会用到两张图集,或者在项目体量变大之后一张2048x2048放不下通用资源,需要用到两张,就会出现ABA图集穿插打断合批的情况,内存也随之上去了。
- 动态图集:为了解决游戏中动态图片太多的问题,运行时,将界面上零散的图片收集起来,绘制到一张空白大图片,只需要将这个大图传到GPU里面,就能达到合批的效果。
动态打图集的方式.
在Unity游戏开发中,优化DrawCall是提升性能的关键环节之一。Unity提供了多种优化技术,如静态合批(Static Batching)、动态合批(Dynamic Batching)和GPU实例化(GPU Instancing)
还可以通过剔除(Culling)技术,比如视锥体剔除(Frustum Culling)和遮挡剔除(Occlusion Culling),来减少不在摄像机视野范围内的物体的DrawCall,进一步提升性能
在UI方面,DrawCall的优化也非常重要。例如,通过使用图集(Sprite Atlas)可以将多个图片合并为一个DrawCall,从而减少渲染次数
Draw Call ,CPU准备好需要绘制的元素,对底层图形程序接口进行调用的过程。
元素之间的合批是有条件的:层级、纹理、渲染方式,当这三者都相同的时候,就会合批(Batch)。
· 纹理:其实就是贴图,如果使用的是同一张贴图,或者是同一张图集中的不同贴图,就可以认为它们的纹理是相同的;
· 渲染方式:渲染方式是由Shader计算的,在unity中,Shader是附着在材质球Material上,通过给元素添加Material来指定渲染方式。所以,可以简单的理解为,材质球Material相同,即可认为它们的渲染方式相同;这里需要注意的是,UI元素(Image、Raw Image、Text)的Material为None时,UGUI会给它指定默认的材质球UI-Default。
讲完了纹理和渲染方式,接下来说说层级。层级很好理解,但确是最难处理的,同时它也是合批的首要判定条件。
层级相同,则会判断纹理,纹理一致,再看渲染方式,都一致,则开始合批处理数据,处理完数据后将其添加进命令缓存区,GPU接收到命令就会开始渲染。
同一个Canvas内,UGUI的层级计算方式:
1、一个UI元素,在它所占的屏幕范围内(通常是矩形),如果没有任何UI在它的底下,那么它的层级号就是0(最底下);
2、如果一个UI在其底下且该UI可以和它合批,那它的层级号与底下的UI层级一样;
3、如果一个UI在其底下但是无法与它Batch,那它的层级号为底下的UI的层级+1;
4、如果有多个UI都在其下面,那么按前两种方式遍历计算所有的层级号,其中最大的那个作为自己的层级号。
尽量使得相同图集的元素处于一个连续的层级内,以减少Draw Call。
渲染优化,DrawCall,剔除,Shader,LOD,TextureStreaming
GPU实例化是一个有效绘制相同网格和材质对象的功能。当多次绘制相同的网格(如草或树)时,期望减少绘制调用。
要使用GPU实例化,请转到材质的检查器,并在材质的检查器中单击启用实例化。
Culling剔除
剔除图像中最终不会显示在屏幕上的部分。
视觉剔除
视觉剔除(Visual Culling)是一个从渲染中忽略相机渲染区域之外的物体的过程,即视锥。这可以防止相机范围外的物体被计算渲染。
默认情况下执行视觉锥体剔除,没有任何设置。对于顶点着色密集的对象,可以通过适当划分网格来应用剔除,以减少渲染成本
背面剔除
背面剔除是省略渲染(应该是)不可见的多边形背面的过程。大多数网格是封闭的(只有前面的多边形对相机可见),所以多边形的背面不需要绘制。
在Unity中,如果你没有在着色器中指定这个,多边形的背面就会被剔除,但是你可以通过在着色器中指定它来切换剔除设置。
Occlusion culling遮挡剔除
遮挡剔除是指从渲染中省略那些因为被物体遮挡而对相机不可见的物体的过程。这个函数使用预焙遮挡数据来确定一个对象是否在运行时被遮挡,并从渲染中移除遮挡的对象。
要使一个对象符合遮挡剔除的条件,将inspector的静态标志设置为Occluder Static 或者 Occludee Static。如果Occluder Static被禁用而Occludee Static 被启用,对象将不再被视为遮挡,而只是被遮挡的对象。在相反的情况下,对象是个遮挡物。
ShaderVariantCollection变体收集
ShaderVariantCollection可以在防止着色器被编译时出现性能尖峰。
shadervariantcollection允许你保存游戏中使用的着色器变量列表作为资产。它是通过选择“Create -> Shader -> Shader Variant”创建的集合”。
光照贴图Light Mapping
通过提前将光照效果和阴影烘焙到纹理中,可以以比实时生成低得多的负载实现高质量的光照表达式。
要烘培lightmap,首先将放置在场景中光组件设置Mixed或者Backed模式
批处理就是把渲染时使用相同材质(Shader)、相同贴图的3D模型的网格合并在一起,成为一个大网格,然后再调用一次Draw Call,直接渲染这一个大网格。
UGUI的合批就是把某个Canvas下满足合批规则的UI控件的网格合并为一个大的网格,然后将这些网格合并在一起,调用一次Draw Call,然后提交给GPU进行绘制。UGUI的合批规则除了上述规则,还需要在满足一些条件。