赞
踩
前言:记录了总6w字的面经知识点,文章中的知识点若想深入了解,可以点击链接学习。由于文本太多,按类型分开。这一篇是 性能优化 常问问题总结,有帮助的可以收藏。
性能优化,主要聚焦在 内存、 CPU、GPU 三大方向上。
Unity内存占用组成
1.1.1 Mono方向
(1)编程语言:字符串拼接,减少频繁扩容,合理用new,减少内存碎片的产生(将大额内存进行预处理)等一系列降低GC的操作。尽量用结构体取代类(值类型不占用堆,不需要GC)。
(2)配置表优化:延迟加载,具体表现为:
1.protobuf主要用于网络传输,或者将一些数据(比如聊天数据)序列化在存本地,下次启动文件再反序列化过来就可以了。
2.一个字典存储了所有的配置表信息,key是配置表的文件id,value是一个字典存储的配置表中的配置信息,其中key是配置ID,value是行数据偏移信息(配置行的起止位置)。
3.序列化的时候先只需要储存索引,要用到的时候按照索引再去现场序列化即可,这样可以不在游戏开始就序列化所有数据,大大的降低内存占用。
C#侧优化方案-用MessagePack压缩
MessagePack for C#(MessagePack-CSharp)是用于C#的极速MessagePack序列化程序,比MsgPack-Cli快10倍,与其他所有C#序列化程序相比,具有最好的性能。 MessagePack for C#具有内置的LZ4压缩功能,可以实现超快速序列化和二进制占用空间小。
1.1.2 Native方向
美术资源,模型面数,密度精度,贴图格式,限制等。
1.1.3 对象池
管理unity对象
1.1.4 AssetBuildle
1. 资源冗余(重复资源)
2.避免运行时资源泄露。
为什么会资源泄露
UI优化核心就是一点:减低DrawCall
2.1 DrawCall介绍
CPU准备好需要绘制的元素,对底层图形程序接口进行调用的过程。
也可以理解为:CPU向GPU发布一条渲染指令,就是一次DrawCall的过程。简称DC。
2.2 降低DrawCall
2.3 合批
一次Draw Call中批量处理多个物体。只要物体的变换和材质引用相同,GPU就可以按完全相同的方式进行处理,即可以把它们放在一个Draw Call中。
注意:简单来说在一个Canvas下,需要相同的材质,相同的纹理以及相同的Z值。例如Ul上的字体Texture使用的是字体的图集,往往和我们自己的UI图集不一样,因此无法合批。还有UI的动态更新会影响网格的重绘,因此需要动静分离。
2.3.1 静态合批
将static的静态物体(永远不会移动、旋转和缩放) ,如果相同材质球,面数在一定范围之内。unity会自动合并成一个batch送往GPU处理。
1.需要做的事情
把要进行静态批处理的GameObject在Inspector面板右上角的Static勾选(实际上只需要勾选Batching Static即可)
2.优点
因为只需要进行一次,所以性能会比动态批处理要好。
3.缺点
使用静态合批需要额外的内存开销来存储合并后的几何数据。
因为需要额外维护多一份数据,所以包体会变大,占用的内存也会变多(不能有超级大量的相同模型(如:森林里的树))
无法移动
进行了静态批处理之后的GameObject不能在游戏运行时改变位置或者是跟渲染有关的属性。并且因为把所有要静态批处理的GameObject都合并成一个大网格保存起来,所以这实际上相当于即使是同一个GameObject,也需要复制一份网格数据一起保存在这个大网格的顶点数据里面去,这样就导致了占用的内存变多了。
静态合批就是多渲染一套合并后的网格 ,提前存在内存里,内存当然就大了。
4.原理
在开始阶段把需要静态批处理的GameObject进行一次网格合并操作,然后把这个合并之后的大网格保存起来,后续都是用这个网格而不需要再进行合并。
在预处理阶段,把一些材质相同的模型的顶点统一变换到世界空间坐标下,并且新构建一个大的VB把数据保存下来,在绘制时,就会把这个大的VB提交上去,只需要设置一次渲染状态,再进行多次drawcall绘画出每个子模型。 所以Static Batching是不会减少drawcall的,但由于只修改了一次渲染状态依然可以减少CPU的消耗。而且在渲染前,也可以进行视锥体剔除,减少顶点着色器对不可见的顶点的处理次数,提交GPU的效率。
2.3.2 动态合批(OpenGL介绍)
如果动态物体共用着相同的材质,那么Unity会自动对这些物体进行批处理。动态批处理操作是自动完成的,并不需要你进行额外的操作。
1. 优点
不用自己做任何事情,Unity会在游戏中自动进行动态批处理,只要满足下述条件。
在Unity中,要进行动态批处理需要满足以下条件:
- 顶点属性要小于900。例如,如果shader中需要使用顶点位置、法线和纹理坐标这三个顶点属性,那么要想让模型能够被动态批处理,它的顶点数目不能超过300。因此,优化策略就是shader的优化,少使用顶点属性,或者模型顶点数要尽可能少。(这个是《UnityShader入门精要》这本书上说到的,同时书上也说了不一定是900,可能不同版本的Unity会有所区别,这个可以自己在Unity中去手动验证得出)
- 多Pass的shader会中断批处理。
- 使用LightingMap的物体需要小心处理。为了让这些物体可以被动态批处理,需要保证它们指向LightingMap中的同一位置。
2. 原理
Unity会检测哪些GameObject使用了同一个共享材质,然后去合并这些使用了同一个共享材质的网格顶点数据,形成一个新的大网格,然后传给显存,直接渲染这个大网格就相当于渲染了所有的被合并的小网格,而这只需要一次DrawCall。
在每一帧运行时,计算相同材质的模型,把他合并批次进行渲染。动态合批只需要设置一次渲染状态,且能减少drawcall次数。
为什么用了不同的材质,不同的贴图,不同的材质属性等会导致不能动态批处理呢?(OpenGL)
在Unity渲染其实也是调用的OpenGL或者是DirectX,因为我只了解过OpenGL,所以以OpenGL为例(DirectX也是同理)。OpenGL中要渲染一个东西出来的,需要顶点着色器和片元着色器,这个对应的是ShaderLab中的顶点着色器和片元着色器,然后还需要把要渲染的网格的顶点属性作为一个数组绑定到VBO(顶点缓存对象)中去,然后绑定VAO(顶点数组对象)并设置顶点数组的属性,然后一些需要外部设置的着色器属性也是在这个阶段进行设置,当做完这些之后,再调用glDrawElements(也就是一次DrawCall)去渲染这个物体。这就是OpenGL渲染一个东西的最简单的流程。所以回到问题,为什么贴图,材质属性等必须一样呢?因为如果不一样的话,他们就不能通过一次DrawCall去设置这些属性和贴图了,想想看,如果A物体使用了贴图A,B物体使用了贴图B,如果把他们合并成一个大网格,要直接在一个DrawCall里渲染出来的话,这个合并好的大网格到底该用贴图A还是贴图B呢?无论使用哪个,都是不对的。所以A物体和B物体不能进行批处理。
2.3.3 动静合批的区别
动态批处理一切都是自动的,不需要做任何操作,而且物体是可以移动的,但是限制很多。
静态批处理:自由度很高,限制很少,缺点可能会占用更多的内存,而且经过静态批处理后的所有物体都不可以再移动了。
静态合批发生在加载场景的时候。
动态合批发生在游戏运行的时候。
2.3.4 合批了就一定就性能更加好嘛?
不一定,场景中比如有很多颗静态的树,你如果设置静态合批,内存直接炸裂。
2.3.5 静态合批是在什么时候完成的?
加载场景的时候
在unity上 启动项目就会加载场景
在手机上 要先 加载资源 解析资源 释放资源
所有在层级窗口中的物体,实际上已经记录数据了,加载场景是直接通过反射来创建,这个时候(加载场景之初)就会进行和批处理,所以运行中动态创建的物体 即使勾选了static 也不会进行和批处理(因为时间已经过了)。
2.3.6 静态合批有数量限制/动态合批的限制多少顶点
32k顶点 / 900
每个Batch最大300Mesh。
详细请看
浅谈Batch(批次合并)_0zien0的博客-CSDN博客浅谈Batch(批次合并)https://blog.csdn.net/a42626423/article/details/126765697
2.4 DrawCall影响
DrawCall越高对显卡的消耗就越大。(影响CPU,导致游戏性能下降)
影响CPU性能的原因:
当CPU发布一条指令后,传到GPU处理,GPU处理渲染速度很快,对于30条还是300条时间影响不是很大。基本都早早的完成“工作”而处于闲置状态。
而对于CPU而言,每一次 DrawCall 前,CPU 都需要做一系列准备工作,才能让 GPU 正确渲染出图像。而 CPU 的每一次内存显存读写、数据处理和渲染状态切换都会带来一定的性能和时间消耗。这些相对于 GPU 渲染来说非常慢。大量的 DrawCall 会让 CPU 忙到不可开交,而 GPU 大部分时间都在摸鱼,是导致游戏性能下降的主要原因。
3.1 Overdraw介绍
Overdraw是指屏幕上的某个像素在同一帧的时间内被绘制了多次。
3.2 Overdraw影响什么
过多Overdraw可能会引起GPU过载,影响动画的播放和界面响应速度。
3.3 降低Overdraw
1.将Mask(自带两层Overdraw)替换为RectMask2D(自带一层Overdraw)
2.全屏遮挡的情况,则为被遮挡的canvas添加CanvasGroup 组件,然后在被挡住时将其alpha值设为0,不参与绘制,没有 DrawCall也没有Overdraw,同时顶点也不会参与重绘。
3.unity的文字显示中自带阴影组件shadow/Outline减少使用,自带overcall。
4.UI摆放,减少UI的叠加效果。
5.将不想显示的物体直接禁用,而不是将其透明度改为0,仍会产生overcall。
详细请看:
详细请看:
4.1.1 介绍
当场景中包含大量模型时,DrawCall就会非常大,造成的性能消耗就会比较大,从而会造成渲染效率降低,使用遮挡剔除技术,可以使那些被阻挡的物体不被渲染,达到提高渲染效率的目的。
4.1.2原理
在场景空间中创建一个区域,该遮挡区域有单元格组成,每个单元格构成整个场景的一部分,这些单元格会把整个场景拆分成多个部分,当摄像机能够看到该单元格时,表示该单元格中的物体会被渲染出来。
如果场景中存在大量小"物件”,则可以使用"层消隐距离"来优化场景;"层消隐距离"就是在比较远的距离将小物体剔除以减少绘图调用的数量(比如:可以一个大型场景中,高大型的物体任然可见,但是一些小装饰内容(小狗、车子之类的则可以隐藏))
LOD(Level of detail)多层次细节,是最常用的游戏优化技术。
它按照模型的位置和重要程度决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算。
这就是说,根据摄像机与模型的距离,来决定显示哪一个模型,一般距离近的时候显示高精度多细节模型,距离远的时候显示低精度低细节模型,来加快整体场景的渲染速度。
作用 : 优化GPU
缺点 : 同一模型要准备多个模型,消耗内存。
特点 : 以内存做消耗来优化GPU
为了加快渲染速度和减少图像锯齿,贴图被处理成由一系列被预先计算和优化过的图片组成的文件,这样的贴图被称为 MIP map 或者 mipmap。
5.2.1 优点
优化显存带宽,用来减少渲染。因为可以根据距离摄像机远近,选择适合的贴图来渲染。所以UI不适用MipMap。
5.2.2缺点
运行时占用更多内存,且增加包的容量。
【使用 Mip maps 需要使用 33%以上的内存,但不使用它会导致巨大的性能损失】
Mipmap纹理技术是目前解决纹理分辨率与视点距离关系的最有效途径,它会先将图片压缩成很多逐渐缩小的图片,例如一张64*64的图片,会产生 64*64 , 32*32 , 16*16 , 8*8 , 4*4 , 2*2 , 1*1的7张图片,(从2的0次幂 到2的n次幂,n+1即为图片大小)当屏幕上需要绘制像素点为20*20 时,程序只是利用 32*32 和 16*16 这两张图片来计算出即将显示为 20*20 大小的一个图片,这比单独利用 32*32 的那张原始片计算出来的图片效果要好得多,速度也更快。
Mask实现的具体原理是一个DC来创建Stencil mask(来做像素剔除),然后画所有子UI,再在最后一个DC移掉Stencil mask。
这头尾两个DC无法跟其他UI操作进行Batch,所以表面上看加个Mask就会多2个DC,但是,因为Mask这种类似“汉堡包式”的渲染顺序,所有Mask的子节点与其他UI其实已经处在两个世界了,上面提到的层级合并规则只能分别作用于这两个世界了,所以很多原本可以合并的UI就无法合并了。
原因是不同材质不能合批。具体来讲就是 Mask用的Stencil模板材质;而非Mask用的default Ul mat材质。
因此mask会比rectMask2D额外生成两个DC。
1.RectMask2D本身不产生drawcall.
2.不同RectMask2D的子对象不能合批.
RectMask2D控件的局限性包括:
仅在2D空间中有效
不能正确掩盖不共面的元素
RectMask2D的优势包括:
不使用模板缓冲区
无需额外的绘制调用
无需更改材质
高速性能
详细请看:
图集就是碎图合成大图 降低内存,减少dc。
UI图集有合批没有的优点,就是热更新的时候因为小文件变少了,所以会快一些。
UI图集就是UI的动态合批。
UI图集完成合批的条件是什么?
深度 贴图 材质 => 排序好的列表当前这个依次和前面对比是否贴图和材质ID相同决定是否合批。
扩展:
LightMap:就是指在三维软件里实现打好光,然后渲染把场景各表面的光照输出到贴图上,最后又通过引擎贴到场景上,这样就使物体有了光照的感觉。
Mask使用模板缓冲来实现区域切除逻辑(Stencil),会占用两个DC。它实现最初设置模板缓存会给Mask添加一个特殊的材质,并且以像素为单位存储是否需要显示最后还原模板缓存,这两次操作各增加一次DC。它可以和其他Mask子物体进行合批,如果两个mask重叠了,那就不能进行合批,会产生额外的dc。
rectmask2d继承自IClipper接口,内部主要实现的就是一个方法来实现了区域的切除逻辑,本身是不占用DC的,完全遮住的情况下不会绘制顶点和面,不参与深度运算不占用DC(和mask的最大区别)。 缺点:它无法和RectMask的子物体进行合批,只能和自身的子物体进行合批(注:如果本身带了Image组件的话是可以进行合批的)
mask2d只能矩形,要不同形状的遮罩还是得mask,所以RectMask2D并不一定完全好,他在特定情况下无法合批。
你的背包设计了1w个道具,如何优化?
操作
1. 每个滚动条目都是同一个预设体的实例
2.做缓存,只要实例化视野范围内滚动条目,往上超出视野部分,自动填补到视野的下面
可以缩小我们为性能瓶颈的搜索范围提供很大的帮助。
SetPassCall: 通俗的讲解就是更换重新装在渲染管线里面的Shader代码和配置,就像换画笔一样的。SetPassCall开销非常的大,所以尽可能的要少用一些不同的Shader,再一个场景里面。尽可能的让同一个Shader 绘制最多的物体后再切换下一个Shader。尽量避免绘制物体的时候平凡交叉的来回切换Shader来节约SetPassCall的次数。
可以把Shader 设置为常驻内存缓存,这样节约SetPassCall所带来的开销。
例如当mmo类型游戏中 存在多个玩家时,一个玩家移动,将通知所有玩家随之跟新,发送与接收数据太多造成卡顿,利用AOI机制(兴趣范围)
以状态修改的角色为原点,设置半径,获取该角色的兴趣范围,当其他角色存在该兴趣范围的时候,进行接收通知并更新状态,减少cpu压力。
可以利用该思想去解决不同的方案
思想(这是圆形区域,或者可以格子布局(九宫格优化)或者两者组合,扩大地图等):
优点:易于实现,且不需要特殊的数据结构。
缺点:计算成本较高:需要该角色与所有角色的距离判断。(1+n)n/2。
优化:
优化设计方案:
减小 Max Size
使用能生成视觉上可接受的结果的最低设置。这种非破坏性方式,可以快速降低纹理内存。
使用 2 的幂 (POT)
Unity 要求移动端纹理压缩格式(PVRCT 或 ETC)采用 POT 纹理尺寸。
说明:当游戏中的图标满足边长为2的n次方的时候,满足POT格式,比非POT格式的图标占用更少的内存。可以被GPU直接解析。
例如 119*119的图标(非POT格式)大小为55.3k,可以利用图集解决。
128*128的图标(POT格式)大小为16k。
所以并非图标越小占用内存越少。
原因:
内存对齐:许多图形硬件和软件要求纹理的尺寸必须是2的幂次方。使用POT尺寸的图像可以满足这种要求,因此在加载和渲染时更高效。
压缩算法:许多POT格式采用了压缩算法,如PNG的无损压缩和JPEG的有损压缩。这些算法可以有效地减少图像文件大小,从而减少内存占用。
质量损失:某些POT格式可能会对图像进行压缩,减少像素信息的精度或丢弃一些细节。虽然这样可以减小文件大小和内存占用,但也可能导致图像质量的损失。
将工作推迟到必要时进行以避免不必要的工作。就是用一个标志位来标记内容是否发生变化,如果没有发生变化就直接使用缓存数据,不需要重新计算。
要点:
脏标识模式:当前有一组原始数据随着时间变化而改变。由这些原始数据计算出目标数据需要耗费一定的计算量。这个时候,可以用一个脏标识,来追踪目前的原始数据是否与之前的原始数据保持一致,而此脏标识会在被标记的原始数据改变时改变。那么,若这个标记没被改变,就可以使用之前缓存的目标数据,不用再重复计算。反之,若此标记已经改变,则需用新的原始数据计算目标数据。
使用场合:
1,原始数据转换到目标数据会消耗很多时间,都可以考虑使用脏标记模式来节省开销。
2,游戏中物体局部变换到世界变换的计算,当没有变化时不需要每帧重复计算。(从根节点沿着它的父链将变换组合起来,矩阵相乘=世界变换)。还有游戏场景图中每帧渲染的对象,对于没有发生变化的对象可以不必重新渲染。再有我们的文档存档也可以用到,内存中就是我们的原始数据,存盘到磁盘就是我们的目标数据,当然不需要实时存盘。
3,若原始数据的变化速度远高于目标数据的使用速度,此时数据会因为随后的修改而失效,此时就不适合使用脏标记模式。
详细请看:
希望此篇文章可以帮助到更多的同学,此外对现在面临校招的大三大四的同学,以及热爱游戏或者即将面临找工作的朋友,可以点击下方链接,来解决游戏职业道路的种种困惑,并且还可以学习理论知识的同时,拓宽游戏制作的实践技能~
游戏行业大揭秘https://scrm.vipskill.com/CMS/prod/5726/54/home.html?mantisSiteId=175&track_id=__TRACKID__
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。