一.Drawcall:影响 CPU 效能,一个 Draw Call等于CPU呼叫一次GPU的 DrawIndexedPrimitive (DX) or glDrawElements (OGL),等于一个 Batch合批。Unity提供了Dynamic Batching动态合批和Static Batching动态合批两种方式的Draw Call Batching合批技术去优化Drawcall(Draw Call Batching劣势:它需要把一个Batch中的所有物体组合到一起,相当于创建了一个与这些物体加起来一样大的物体,与此同时就需要分配相应大小的内存,这不仅会消耗更多内存,还需要消耗CPU时间。特别是对于移动的物体,每一帧都得重新进行组合。对于静止不动的物体来说,只需要进行一次组合就可以一直使用,效率高一些。因此需要进行一些权衡)。(iOS来说Draw Call应尽量控制在20次以内,可以在编辑器的Statistic窗口看到 DrawCall 与 Batched 的数字)
UNITY 在 Player Setting 里的两个功能选项 Static Batching 与 Dynamic Batching设置位置:
1.Dynamic Batching动态合批:完全自动进行的,不需要也无法进行任何干预,对于顶点数在300以内的可移动物体(不论物件是否不静态或动态),只要使用相同的材质,就会组成Batch
动态合批推荐处理:
1)Hierarchy顺序_图集:同一图集的Image元素应尽量保证在Hierarchy中连续,避免中间插入其他图集导致断批。还要注意Image与Text组件的层级,Text组件会排在Image组件之前渲染。(层级合批并不是完全按照Hierarchy顺序,Unity自身还提供了一个算法去Batch合并层级:如果有一个UI元素,它所占的屏幕范围内如果没有任何UI在它的底下,那么它的层级号就是0;如果有一个UI在其底下且该UI可以和它Batch,那它的层级号与底下的UI层级一样;如果有一个UI在其底下但是无法与它Batch,那它的层级号为底下的UI的层级+1;如果有多个UI都在其下面,那么按前两种方式遍历计算所有的层级号,其中最大的那个作为自己的层级号。
2)遮罩裁切Mask与RectMask2D:裁切多于2个时推荐用可内部合批的Mask,2个裁切时两种裁切的性能差不多,1个裁切时和矩形裁切时推荐不需要创建新材质的RectMask2D。Mask会导致Drawcall数量成倍增长,例如头像边框叠在头像上面,会比用Mask省很多。Mask需要依赖一个Image组件可以自定形状,每帧都使用新材质再次渲染,实现原理是一个DrawCall来创建Stencil mask(来做像素剔除),然后画所有子UI,再在最后一个DrawCall移掉Stencil mask;这头尾两个DC无法跟其他UI操作进行Batch合批,多个Mask内部可以合批。RectMask2D不需要依赖Image组件但裁切形状只能为长方形,通过消耗性能去做裁切,能优化大部分Mask造成的断批,多个RectMask2D内部不能合批,有一些注意点详情请看【Unity源码学习】遮罩:Mask与Mask2D第三部分性能分析
3)文本:文本通常是引擎自动生成的另外一张或若干张图集,如果没注意绘制顺序,很容易造成断批
4)特效:支持配置特效分级、特效持续时间、特效隐藏处理。在特效播放结束后,对特效进行移除(隐藏、缩放、移除屏幕)
5)图集:共享资源放在1~3张大图集中,称为重用图集;其它非重用UI按照功能模块进行划分,每个模块使用1~2张图集为功能图集,功能界面尽量只引用自身和重用图集两个图集;对于一些UI,如果同时用到功能图集与重用图集,但是其功能图集剩下的“空位”较多,则可以考虑将用到的重用图集中的元素单独拎出来,合入功能图集中,从而做到让UI只依赖于功能图集,也就是通过一定的冗余来达到性能的提升。尽量紧凑,没有太多空白。同图集里图片的Packing Tag图集名称和Format资源的压缩格式需要设成相同(新版没看见哪里有Packing Tag,Packing Tag如果“太大”或者“Format压缩设置的不一致”或者“Alpha通道部分有本部分没有”就会生成多个组,每个组会增加一个DrawCall)。图片不能放在Resources下因为Resources文件夹下的资源将不会被打入图集。(项目图集设置Edit>Project Setting>Editor>Sprite Packers)详情请看【Unity游戏开发】图集整理策略
6)图片:纹理内存大小(字节) = 纹理宽度 x 纹理高度 x 通道数RGBA x 通道大小(一/半字节)。纹理宽高像素,最好是2的幂数其次是4的倍数,尤其是大图优化会很明显;纹理通道,根据需求去除Alpha通道,或者应用单通道(比如灰度图/地形高度图/掩码图/Shader掩码图)。可以九宫格或者Mirror镜像处理的大图,缩小切图。通道大小,可以用16位代替32位图,色彩过渡会降低。
7)隐藏被完全遮挡的界面的办法:在父节点上或者添加一层父节点,加上Canvas和Graphic Raycaster和CanvasGroup组件;需要隐藏时,设置CanvasGroup的Alpha=0(就不会传给GPU渲染了,从而降低OverDraw),并设置界面的CanvasRenderer.enable=false。(此时可能还是能被GraphicRaycast检测到会对逻辑行为造成影响,所以还需要自己实现一个manager去管理CanvasRenderer.enabled下的脚本行为。)界面setactive=false会导致所在Canvas的VBO数据失效,所以再次把界面setactive=true的时候会导致整个Canvas去rebuild重建和rebatch重新合批,从而对CPU造成负担。
8)
9)材质与纹理:材质减少场景中使用的材质数量,尽量共享材质。纹理不同的材质可以把纹理组合到一张更大的纹理中(称为Texture Atlasing)
10)Shader 或贴图的更改:每次对Shader 的更改或者贴图的更改,基本上就是对 Rendering Pipeline 的設定做修改,所以需要不同的 Draw Call 來完成物件的绘制
2.Static Batching静态合批(更高效,但收费):需要把静止的物体标记为Static,然后如果在使用相同材质球的条件下,无论大小都会组成Batch
静态合批推荐处理:
1)把不会移动的物体标记为Static。此外还可以通过CombineChildren脚本(Standard Assets/Scripts/Unity Scripts/CombineChildren)手动把物体组合在一起,但这个脚本会影响可见性测试,因为组合在一起的物体始终会被看作一个物体,从而会增加GPU要处理的几何体数量,因此要小心使用
2)对于复杂的静态场景,还可以考虑自行设计遮挡剔除算法,减少可见的物体数量同时也可以减少Draw Call
3.Batch合批:Batch算是对 Draw Call 的另一种称呼,每一次的 Draw Call 会产生一个 Batch, Batch 里装的是物件顶点资料,Batch 由 CPU 通过 “驱动程式” 将顶点资料送往 GPU,GPU接手后将物件画在画面上。
1)劣势及应对方法:合批会在静态/动态/GPU Instance等所有类型的批处理中都会发生,意味着Unity将不再能够对其按原有的顺序进行排序,可能会导致严重的OverDraw。常见的解决方案是禁用麻烦对象的批处理(或者按区域手动来进行批合);另一种解决方案是通过将渲染队列更改为Geometry-1,Geometry + 1等来手动设置Unity渲染顺序,这几乎可以完全控制渲染顺序,但这带来了责任/风险(常见的是把地表、天空盒进行延后渲染),操作方式:使用Standard材质要求检查器处于Debug模式(Inspector窗口右上角三个>Debug),可以在CustomRenderQueue看到或修改具体的渲染队列数
2)预估游戏中大概可以跑多少个 Batch的公式:NVIDIA 在 GDC 曾提出,25K batchs/sec 会吃满 1GHz 的 CPU 100% 的使用率。如果你的目标是游戏跑30FPS、使用2GHz的CPU、20%的工作量发給Draw Call來使用,那你每秒可以有多少Draw Call呢:333 Batchs/Frame = 25K * 2 * (0.2/30)
二.Overdraw过度绘制:影响 GPU 效能,指屏幕上的某个像素在同一帧的时间内被绘制了多次,GPU过载会影响动画的播放和界面响应速度,即帧率。(可以在编辑器的Scene窗口选择Overdraw,看到Overdraw的绘制;可以通过Windows > Analysis > Frame Debugger查看渲染信息)
1.降低Overdraw推荐处理:
(其中透明像素是最常见导致大量OverDraw的元凶,透明度渲染通常伴随Alpha Blend,这需要额外的读取和算术运算,透明材质通常不会写入Z-buffer,因此最终会写入更多无法丢弃的像素。应付方法是减少绘制的透明像素的数量及其成本:减少渲染的透明层的数量;减小透明几何图形所需的屏幕尺寸,您可以按比例缩小透明对象的(屏幕)大小;尽最大可能从精灵中删除100%透明纹理像素,制作紧密的网格以减少完全透明的区域)
1)九宫格Fill center 会影响OverDraw
2)RectMask2D比Mask组件要优化一些:Mask组件自带两层OverDraw,下面是被遮的图即使完全不显示但依然会被OverDraw;Rect mask 2d 就是一层OverDraw,下面是被遮的图如果完全不显示则不会被OverDraw,所以要比Mask组件优化一些
3)文本的阴影Shadow:会增加1层OverDraw
4)文本描边OutLine:复制了四份Shadow,会增加4层OverDraw
5)隐藏被完全遮挡的界面的办法:加CanvasGroup组件,设置CanvasGroup的Alpha=0(这样就不会传给GPU渲染了,从而降低OverDraw。CanvasGroup组件可以统一改子节点的一些行为)
6)特效:粒子效果请尽量简单,如果可以推荐用序列帧动画实现。粒子通常也是半透明图片
7)透明图片_图片设置:透明像素区域会增加OverDraw。可以把TextureType选择Sprite(2D and UI),然后SpirteMode可以选择Polygon,再去Sprite Editor里选择CustomOutline再点击Generate自动生成多边形(半透像素会导致生成的多边形不贴合,该方法也可以用来去掉MeshType=Tight时出现的一些邻图的多余像素);然后在Image使用图片时勾选UseSpriteMesh就会按照生成的多边形网格去渲染。UseSpriteMesh会增加渲染的顶点数量和计算量(增加渲染压力),也会降低Overdraw(减少内存压力),需要去权衡
8)透明图片_交互分离:交互最好是用只在逻辑上响应Raycast射线检测,但不参与绘制的组件。如果用大量不可见的Image作为交互响应的控件,会导致FillRate爆满,然后导致复杂界面在低端机上掉帧卡顿现象。(FillRate填充率是指显卡每帧每秒能够渲染的像素数,被反复绘制的次数越多那么它占用的资源也必然更多,压力主要来自半透明物体:半透明物体需要开启AlphaBlend透明度混合且关闭 ZTest深度测试和 ZWrite深度写入,绘制alpha=0的半透明物体也做AlphaBlend透明度混合,比较浪费)
9)SpriteRenderer与Image:大量静态图片推荐使用Image;如果需要移动变化,要么把这些需要移动、变化的Image单独放在一个Canvas上,要么把它们做成SpriteRenderer。SpriteRenderer用的是SpriteMesh,绘制成本高,移动变化不消耗低;Image可以使用RectMesh也可以使用SpriteMesh,Canvas会重用所以静态绘制成本低,移动变化会导致Canvas重绘产生大量开销。
10)后处理效果:会增加Overdraw,因为需要对受影响的像素至少计算一次
11)渲染策略:1:如将“天空球”或者“相交的几何”对象拆分为较小的子对象,以便Unity可以获取更准确的原点来更有效地对绘图调用进行排序;其中“相交的几何”可以以相交线分成4个平面,这样基于距离的排序就可以正确地完成其工作,但这个操作将支付更多的Draw Call以减少OverDraw,因此需要做权衡。2:Unity会尽最大努力从近到远渲染不透明元素。这就是说,我们首先渲染距离摄像机更近的元素,以便使用Z-Test剔除被遮挡的物体的像素(减少渲染以降低Overdraw),该排序基于相机与每个对象的边界框中心之间的距离。这种排序非常有利于GPU性能,但是在某些情况下,会导致渲染队列排序混乱,使OverDraw增加。比如倒球面网格,球体的中心恰好是摄影机所在的位置,前面会有一些对象在圆心与球面中间,这种情况将会先渲染该球体产生大量OverDraw,这就是为什么在Unity中应该把渲染天空盒放在渲染不透明几何图形之后的原因。
12)shader透明混合方式:如果无法进一步减少OverDraw,可以尝试用shader的Additive blending透明混合方式,比移动设备上的Alpha Blend透明混合方式要廉价得多。(shader里可以设置Blend One One或者Blend SrcAlpha One,因为背景都是One,亮度会比其他混合方式增强一些,所以叫additive)
13)Shader的性能优化:使用简单的数学运算,例如使用乘法代替除法,或者使用位运算代替逻辑运算等等;尽量少使用分支语句,如果必须使用分支语句,尽量使用if语句代替switch语句,因为if语句的性能通常比switch语句要好;尽量少使用纹理采样,纹理采样是非常耗时的操作,如果必须使用纹理采样,尽量将多个纹理采样合并成一个,以减少采样次数;尽量少使用动态分配内存,动态分配内存是非常耗时的操作,如果必须使用动态分配内存,尽量使用静态分配内存代替。
2.OverDraw量化工具
1)Unity自带工具1:Scene窗口>选择Overdraw,叠层越多的区域越亮。(劣势:Unity使用不带Z-Write深度写入的附加着色器来显示它,因此你将看到的大多数不透明对象上的OverDraw都是假的,Unity没有考虑到渲染管线所做的Z-Test深度测试,本来正常情况下很多不透明元素的渲染会被Z-Test深度测试剔除掉。)
2)Unity自带工具2:HDRP 7.1+高清渲染管线支持该工具,按层数显示成不同颜色且带Z-Write深度写入,半透明对象的OverDraw比上面的工具1要更准确。Window>Analysis>RenderingDebugger>Fullscreen Debug Mode设置为TransparencyOverdraw
3)RenderDoc:免费的GPU图形调试器,直接安装RenderDoc即可,其内置Unity集成。3D场景的OverDraw主要取决于所涉及的渲染顺序和着色器,先渲染近处不透明的物体然后Z-Test就会跳过远处被遮挡的地面等像素,使绘制会更高效。在Unity中,UI最通常在渲染管道的末端绘制以确保它显示在所有事物之上,而全屏界面就会导致整个界面进行100%重绘。
4)Compute Shader(作者Nordeus):可以用大量数字分析量化场景的OverDraw,提供场景每时每刻都有的OverDraw数值。适用于DirectX,目前不适用于OpenGL。最优只有1倍OverDraw,尽可能避免2倍OverDraw,不要超过3倍,堆各种后处理效果避免不了OverDraw会上去。
5)Snapdragon Profiler:晓龙cpu安卓手机(除华为),可能经常不稳定但很有用
6)Xcode:当世代最强的渲染分析工具,推荐用ios设备+Xcode分析渲染性能
1.性能优化必要性
(1)面临的问题:包体大,启动速度慢,崩溃闪退,手机发热严重,图片模糊,功能模块内资源规划混乱,逻辑复杂或者资源加载或者服务器导致卡顿,实例化过多,过多重复资源,发布版未屏蔽调试版的Debug信息,占用内存持续上升
(2)与所关注的用户体验强相关
2.启动优化-Resource文件
(1)Resource System:Unity里内置的,只要在UnityAsset文件夹下的任意地方创建叫Resources的文件夹,就可以在Unity运行时通过Unity提供的Api对Objects进行加载和卸载,可以创建多个Resources的文件夹。
(2)Resources中的结构复杂内容多,会导致:
1)内存和包体增加:Resources文件夹下的内容不管是否引用都会被打到包体里
2)管理复杂增加:协同开发维护成本,遍历查找时长增加
3)构建打包的时间增加:多个Resources文件夹下所有的Assets和Objects内容在打包时会合并到一个序列化文件中,所以打包时处理索引和构建序列化文件的时间也会增加。打包后的序列号文件包含metadata和indexing information索引信息(根据对象名称解析对象文件的GUID和Local ID,或者根据字节偏移量在序列文件正文中定位到该对象)
4)影响游戏启动时间:在启动时Resources文件夹下的所有对象都需要实例化(同时Resources外的文件只有需要立刻用到的对象需要进行Instance ID实例化置scene中引用),加载时间随资源数量以nlog(n)的线性增长,低端机受到的影响比较明显
(3)推荐放在Resources文件下的资源:
1)在整个游戏生命周期都存在,从启动到关闭都在运行。
a.一个MonoBehaviour Singletons单例
b.一些序列化的配置数据,比如微信appID或者一些不会变的Key
c.LoadingPrefab预制体,游戏启动或者场景加载的Loading
d...
2)资源所占内存小且不会变化
a.名字随机配置
b.默认头像icon
c.道具品质框
d...
3)不会热更新的资源:因为Resources里热更的资源会常驻在手机内存里。并且这里的预制不能挂热更脚本,用这里的资源是可能热更资源还没加载完。
4)不会因为平台或者设备变化的资源:比如在Android和Ios不同平台,或者IphoneX和Ipad不同设备上的资源不同,就不能放在这
3.启动优化-MononBehaviour:直接现象长时间显示黑屏
(1)第一个场景的MonoBehavirou对象不应该太多:Awake或Start逻辑太多会延迟游戏初始化的时间,导致游戏启动缓慢,对低端机影响更大
(2)Awake或Start逻辑方法逻辑简单:逻辑太复杂或者太耗时,启动就会缓慢卡顿
(3)推荐做法:第一个场景作为游戏闪屏,只显示对应Logo或者具体的闪屏效果;或者第一个场景不加载资源和不处理复杂逻辑,只是一个过度场景
4.重复资源优化:
(1)资源重复的危害:(推荐从大局出发给出统一标准)
1)包体增加,构建成本增加:多份资源,编译解析实例化成本增加
2)内存增加:比如两个人在同一个场景加载了的内容相同的不同资源
3)重复性工作增加:资源几何变换,比如头像做了两份工作
4)出错率增加:几个变换,渲染层级调整等,有几份资源就需要成倍调整几次
5)I/O增加:读写增加
6)增加项目重构复杂度:比如网络弹窗 奖励弹窗被做成了多份时,不过是所有都重构,还是重新写一个代替所有来重构,都会使重构工作耗费更长的时间。(重构为了改善软件质量和性能,优化程序设计模式和架构,提高软件的扩展性和维护性)
7)项目管理成本增加:整理在用资源,应删除资源时,需要耗费更长的时间
8)降低团队协作能力:过多重复,自己做自己的
9)沦为酱油程序员:只关注自己的部分,不关心项目的整理管理和整体性能
(2)查找重复资源工具所需功能:可以选择根据名字/大小/全部(全部就是名字相同或大小相同)的条件来查找,需要可查被的引用路径(或者可以直接定位到资源),最好可以预览两个资源对比预览
1)重复的png jpg...等纹理资源
2)重复文本json txt xml
3)重复perfab:创建新的源预制的话,里面个GUID和引用的LocalID都不同,所以不是重复的
4)重复Shader
5)持续更新
5.纹理设置优化:Read/Write Enabled设置不需要时不要开启。因为启用后会访问原始像素数据,在游戏内存中会保留2次:GPU和CPU各存储一份(因为大多数平台从GPU内存回读慢,供CPU代码使用效率极低,所以CPU自己也会存一份)。
6.纹理MipMap选择
7.纹理压缩优化
8.非角色模型优化
9.动画模型优化
10.网格优化
11.字符串优化1
12.字符串优化2
13.集合List优化
14.闭包优化
15.字典和枚举优化
16.foreach优化
17.数组优化
18.正则表达式优化
19.矢量运算优化
20.颜色转换优化
21.Object查找优化
22.摄像机访问优化
23.Log输出优化
24.方法调用优化
参考的文章列表博客:
【Unity】UGUI性能之DrawCall与OverDraw