原文地址:https://blog.uwa4d.com/archives/optimzation_cpu.html
http://www.cnblogs.com/chwen/p/4396515.html
CPU方面的性能开销主要两大类:引擎模块和自身代码。其中,引擎模块中又可细致划分为渲染模块、动画模块、物理模块、UI模块、粒子系统、加载模块和GC调用等等。
主要就是drawcall、物理组件、GC(垃圾回收)、脚本等几个方面开展
渲染模块
- 降低DrawCall
- LOD
- Occlusion Culling
1.DrawCall
Drawcall是CPU向GPU发送绘制命令的接口调用。理论上每一个不同材质的物件需要渲染在屏幕上时,CPU都会调用图形API ( openGL or Diract3D ) 的Draw接口触发显卡进行绘制。
如果每次drawcall只提交少量的数据将导致CPU瓶颈,CPU无法将GPU填满。Drawcall对GPU的耗费在于硬件一直等待CPU提交数据,而无法得到有效利用。GPU大量的时间耗费在不断切换状态和正确性检测上。 实际上unity官方指出,Drawcall数量的降低并非重点,重点是减少批次的数量,Drawcall优化实际上是对批次数量的优化
优化方案:Drawcall batching,合并打包图集,减少光照和阴影等
-
Drawcall Batching
Unity中对Drawcall的批次有两种:静态批次(static batching)和动态批次(dynamic batching)。但不论静态批次还是动态批次都要求对象的材质是共享的,即不同材质的对象是无法进行批次的。而且要注意的一点:如果在脚本中调用材质时,使用Renderer.material会造成材质的拷贝,而使用Renderer.sharedMaterial来调用则不会拷贝材质。- 静态批次 Drawcall static batching
场景中的多个物件如果是不移动的(包括位置、缩放、旋转等),并且共享同一材质,比如地形、建筑、花盆等,那么可以选择采用静态批次。静态批次只需要在Inspector勾选static选项即可。静态批次需要注意的是,unity会将进行批次的多个对象合并成一个大的对象,也会导致内存损耗,有时候要避免太多对象静态批次造成的内存过高。这也表明,优化并非绝对做好某一方面,而是平衡各个硬件的瓶颈和效率,选择相对适中的方案
2.动态批次 Drawcall dynamic batching
动态批次是运动的物件在unity中也可以进行批次渲染,动态批次不需要手动设置,是unity自动进行的,但是这里有诸多陷阱和约束,开发者需要遵守一定的限制条件才能享受动态批次的好处。 - 静态批次 Drawcall static batching
-
合并图集
其实合并图集也是利用了Unity的Drawcall batching。将多个纹理进行打包成图集是为了减少材质,这样多个对象共享一个材质,并进而使用同一个纹理和shader,触发unity的动态批次。图集打包工具有很多,Asset store中也可以搜到不少,比如Texture Packer Free 、 DrawCall Optimizer(收费) Mesh Baker Free 等等都可以将贴图打包合并 -
光照和阴影
实时光照和阴影可能增加Drawcall,带有光源计算的shader材质会因为光照产生多个Drawcall。使用灯光会打断Drawcall batching,尽量使用烘焙灯光贴图等技巧来实现灯光效果。
2.LOD(https://blog.csdn.net/u014306293/article/details/72522834)
LOD技术指根据物体模型的节点在显示环境中所处的位置和重要度,决定物体渲染的资源分配,降低非重要物体的面数和细节度,从而获得高效率的渲染运算,就是根据与摄像机的距离来选择不同精密物体渲染
3.Occlusion Culling(https://blog.csdn.net/qq_33747722/article/details/70549006)
遮挡剔除、视锥剔除,这两个Unity提供的剔除方案,出视野之后应剔除对象渲染。
加载模块
仅次于渲染的第二大模块,主要包括资源加载,卸载,实例化,代码序列化
cpu开销在引擎中主要体现在Loading.UpdatePreloading和Loading.ReadObject两项中(Profiler)
- Loading.UpdatePreloading:这一项仅在调用类似LoadLevel(Async)的接口处出现,主要负责卸载当前场景的资源,并且加载下一场景中的相关资源和序列化信息等。下一场景中,自身所拥有的GameObject和资源越多,其加载开销越大
- Loading.ReadObject,记录的是资源加载时的真正资源读取性能开销,基本上引擎的主流资源(纹理资源、网格资源、动画片段等等)读取均是通过该项来进行体现,很大程度上决定了项目场景的切换效率。
1.纹理加载
纹理资源是项目加载过程中开销占用最大的资源之一,其加载效率由其自身大小决定。目前,决定纹理资源大小的因素主要有三种:分辨率、格式和Mipmap是否开启
2.网格加载
加载效率由自身大小决定
- 资源的数据量对加载性能影响较大,面片数越多,其加载越为耗时。设备性能越差,其耗时差别越为明显
- 关闭Read/Write功能会降低AssetBundle的物理大小,其降低量与资源本身数据量相关,略微提升加载效率,大幅度降低内存
3.Shader加载
一个Shader资源的物理Size仅几KB,在内存中也不过几十KB。所以,Shader资源的效率加载瓶颈并不在其自身大小的加载上,而是在Shader内容的解析上。几个小小的Shader,其加载耗时居然要高于几张Atlas纹理或者拥有上万片面的Mesh网格。
- 通过依赖关系打包,将项目中的所有Shader抽离并打成一个独立的AssetBundle文件,其他AssetBundle与其建立依赖;
- Shader的AssetBundle文件在游戏启动后即进行加载并常驻内存,因为一款项目的Shader种类数量一般在50~100不等,且每个均很小,即便全部常驻内存,其内存总占用量也不会超过2MB;
- 后续Prefab加载和实例化后,Unity引擎会通过AssetBundle之间的依赖关系直接找到对应的Shader资源进行使用,而不会再进行加载和解析操作
4.场景加载
-
场景加载
- 资源加载:资源加载几乎占据了整个加载过程的90%时间以上,其加载效率主要取决于资源的加载方式(Resource.Load或AssetBundle加载)、加载量(纹理、网格、材质等资源数据的大小)和资源格式(纹理格式、音频格式等)等等。不同的加载方式、不同的资源格式,其加载效率可谓千差万别
- Instantiate实例化:在场景加载过程中,往往伴随着大量的Instantiate实例化操作,比如UI界面实例化、角色/怪物实例化、场景建筑实例化等等。在Instantiate实例化时,引擎底层会查看其相关的资源是否已经被加载,如果没有,则会先加载其相关资源,再进行实例化,这其实是大家遇到的大多数“Instantiate耗时问题”的根本原因,这也是为什么我们在之前的AssetBundle文章中所提倡的资源依赖关系打包并进行预加载,从而来缓解Instantiate实例化时的压力
-
场景卸载
对于Unity引擎而言,场景卸载一般是由引擎自动完成的,即当我们调用类似Application.LoadLevel的API时,引擎即会开始对上一场景进行处理- Destory:引擎在切换场景时会收集未标识成“DontDestoryOnLoad”的GameObject及其Component,然后进行Destroy.同时,代码中的OnDestory被触发执行,这里的性能开销主要取决于OnDestroy回调函数中的代码逻辑
- Resources.UnloadUnusedAssets:一般情况下,场景切换过程中,该API会被调用两次,一次为引擎在切换场景时自动调用,另一次则为用户手动调用(在场景加载后,用户调用它来确保上一场景的资源被卸载干净).该API的CPU开销主要集中在500ms~3000ms之间.其耗时开销主要取决于场景中Asset和Object的数量,数量越多,则耗时越慢
UI模块
- NGUI
- UIPanel.LateUpdate是NGUI开销最大的函数
- UI重建以UIPanel为单位,尽量动态元素和静态元素分离到不同的UIPanel中,将因为动态UI元素引起的重建控制在较小范围内
- 动态UI元素还可以再根据更新频率再次细分到不同的UIPanel中
- 同一个UIPanel下的动态元素尽可能少,越多创建的Mesh越大,重建开销增加.比如,战斗中的血条可以做成多个UIPanel,每组下有5-10个动态血条为宜
物理模块
GC调用
GC是unity自动回收内存垃圾的回收器,这虽没有内存泄漏的风险,但是过多的垃圾回收会让CPU高负荷。这里就要避免不必要的内存申请和释放。可以在某个脚本定时清理垃圾,如void update(){if(Time.framecount %5 ==0)System.GC.collect();}
有以下几点需要注意:
- 字符串的拼接会产生临时字符串内存,移除代码中的字符串拼接,改用string.format,或stringbuilder,这没测。
- 用for代替foreach,foreach每次迭代产生24字节垃圾内存。100次循环就是2.4kB.
- 对象标签tag比较采用comparetag,不要用tag=="mytag"这样。
- 使用对象池。对象克隆也是调用new,因此对于可以循环利用的对象要采用对象池,比如子弹、特效、宝石等等。
- 尽量使用struct而非class,因为struct是栈区,class是堆区。
Unity官方给出的一些优化建议:
- PC平台的话保持场景中显示的顶点数少于200K~3M,移动设备的话少于10W,一切取决于你的目标GPU与CPU。
- 如果你用U3D自带的SHADER,在表现不差的情况下选择Mobile或Unlit目录下的。它们更高效。
- 尽可能共用材质。
- 将不需要移动的物体设为Static,让引擎可以进行其批处理。
- 尽可能不用灯光。
- 动态灯光更加不要了。
- 尝试用压缩贴图格式,或用16位代替32位。
- 如果不需要别用雾效(fog)
- 尝试用OcclusionCulling,在房间过道多遮挡物体多的场景非常有用。若不当反而会增加负担。
- 用天空盒去“褪去”远处的物体。
- shader中用贴图混合的方式去代替多重通道计算。
- shader中注意float/half/fixed的使用。
- shader中不要用复杂的计算pow,sin,cos,tan,log等。
- shader中越少Fragment越好。
- 注意是否有多余的动画脚本,模型自动导入到U3D会有动画脚本,大量的话会严重影响消耗CPU计算。
- 注意碰撞体的碰撞层,不必要的碰撞检测请舍去。
网友评论