Unity性能优化汇总

一、脚本策略

(1)效率排序 GetComponent() > GetComponent(typeof(T)) > GetComponent(string)。

(2)移除空的MonoBehaviour回调定义,在场景第一次实例化时,Unity会将任何定义好的回调(Awake() Start() Update() 等)添加到一个函数指针列表中,它会在关键时刻调用这个列表。

(3)提前缓存组件的引用 T com = GetComponentT>() 。

(4)Update / Coroutines / InvokeRepeating

Update中内部的计算可以适当的减少调用次数。

Coroutines协程通常用于编写短事件序列的脚本,尽可能简单且独立于其他复杂系统的子系统。协程会产生额外的开销成本,还会分配一些内存。协程独立于Update()回调的触发,无论组件是否禁用,都将继续调用协程。协程会在包含它的GameObject变得不活动的那一刻停止,重新激活也不会自动重启。

InvokeRepeating()完全独立于MonoBehaviour和GameObject的状态。停止有两种方法:1 由给定的MonoBehaviour中调用CancelInvoke();2 销毁关联的MonoBehaviour或它的父GameObject。

禁用MonoBehaviour或GameObject都不会停止InvokeRepeating()。

(5)通过可见性禁用对象,使用的成对的方法OnBecameVisible()、OnBecameInvisible() 回调,注意GameObject必须附加一个可渲染的组件 MeshRender或SkinnedMeshRender。

(6)降低数学精度。判断距离使用平方而不使用开方, Vector3的sqrMagnitude。角度的计算。

(7).tag的调用。

tag是场景中GameObject的标签,而GameObject的成员tag是一个属性,在获取该属性时,实质上是调用get_tag()函数,从native层返回一个字符串。字符串属于引用类型,这个字符串的返回,会造成堆内存的分配。然而,Unity引擎也没有通过缓存的方式对get_tag进行优化,在每次调用get_tag时,都会重新分配堆内存。

二、内存优化

内存域:

第一个内存域 - 托管域,Mono平台工作的地方,C#代码在此运行,内存空间自动被垃圾回收管理。

第二个内存域 - 本地域(native),开发者间接与之交互,有一些底层的本地代码功能由C++编写。该域管理一些内部的空间分配,如各种子系统(渲染管线、物理系统、用户输入系统等)分配资源数据(纹理、音频和网格等)和内存空间。包括GameObject和Component等重要游戏对象的部分本地描述,大多数内建Unity类(如Transform、Rigidbody等组件)保存其数据的地方。

托管域也会包含存储在本地域中的对象描述包装器。因此,当和Transform等组件交互时,大多数指令会进入本地域,生成结果之后再复制回托管域。跨域会带来严重的性能问题。

第三个内存域 - 外部库,例如DirectX和OpenGL,还有项目中的自定义库和插件。

垃圾回收(Grabage Collector, GC):

简述:当请求使用新的内存空间,而托管的堆内存空间充足,GC就直接分配内存给调用者。当托管堆空间不足时,GC就进行扫描已存在,且不再使用的内存空间进行清理。最后如果空间仍然不足,就扩展当前的堆空间。

Unity中的GC是一种追踪式GC,使用标记和清除策略。托管堆为每个分配的对象提供一个额外的数据追踪位,该数据位标识对象是否被标记,默认值是false(表示未被标记)。该算法分为两个阶段。

(1)当收集开始时,查找堆内存上所有程序可访问的对象,如果该对象是被直接或间接的引用了,就将标识设置为true。而任何没有引用的对象本质上是不可见的,是可以被GC回收的。

(2)迭代所有查询到的对象(GC将在程序的整个生命周期跟踪这些对象),如果有标记GC将无视它,那些没有被标记的将作为回收的候选者。所有有标记的对象被跳过,在下次垃圾回收扫描之前将他们重新设置为false。

当第二阶段结束,所有没被标记的对象被回收以释放空间。

性能增强技术:

(1)尽量避免增加堆内存分配常规操作:配表解析、New Class、String操作、Insrantiate、AddComponent()、.GetComponent()、格式转换、 .transform 、foreach、装箱。

(2)闭包。匿名函数拉入它作用域之外的数据时,编译器会定义一个新的类用以提供所需的数据环境,导致了在堆上分配内存。C#7提供了本地函数来提高性能,其实是把类换成了结构体。

(3)对象池、预制池。

(4)在对Renderer进行Material/Materials的获取

每次对新的GameObject的Renderer调用.material,都会生成一个新的Material实例,且GameObject销毁后,Material实例无法自动销毁,这会对资源管理造成一定的成本,想要处理的话就需要手动调用“UnloadUnusedAssets”来卸载,但这样就造成了性能开销;管理不好可能会造成材质球大量冗余甚至泄露,极端情况下甚至会导致过高的内存。

建议通过主动MaterialPropertyBlock(先new一个MaterialPropertyBlock,然后给它赋值,最后用Renderer.SetPropertyBlock方式给MeshRender设置属性)的方式修改材质属性,或者人为对有限个材质实例进行管理,效果相同的物体通过sharedMaterial来共用材质实例。Shader支持GPU Instancing。

各种内存资源的释放 :

Destroy(): 主要用于销毁克隆对象,也可以用于场景内的静态物体,不会自动释放该对象的所有引用。虽然也可以用于Asset,但是概念不一样要小心,如果用于销毁从文 件加载的Asset对象会销毁相应的资源文件!但是如果销毁的Asset是Copy的或者用脚本动态生成的,只会销毁内存对象。 

AssetBundle.Unload(false):释放AssetBundle文件内存镜像 。

AssetBundle.Unload(true):释放AssetBundle文件内存镜像,同时销毁所有已经Load的Assets内存对象 。

Reources.UnloadAsset(Object):显式的释放已加载的Asset对象,只能卸载磁盘文件加载的Asset对象 。

Resources.UnloadUnusedAssets():用于释放所有没有引用的Asset对象 。

GC.Collect()强制垃圾收集器立即释放内存 Unity的GC功能不算好,没把握的时候就强制调用一下。

tips:

1、Reources.UnloadAsset(Object) UnloadAsset may only be used on individual assets and can not be used on 

GameObject's/Components or AssetBundles(卸载资产只能用于单个资产,不能用于GameObject's/Components or AssetBundles),单一资产:贴图、材质、shader。

2、AssetBundle.Unload(false) 必须配合 Resources.UnloadUnusedAssets() 才能完成一次彻底的资源清除。

三、图形优化

(1)减少DrawCall

1、Dynamic Batching :只能处理小于900个顶点属性的物体,必须使用同一个材质球,参数必须相同。

2、Static Batching:内存占用比较大,物体不能移动等。

3、GPU Instancing 用于减少渲染大量相同物体时的DrawCall。这些物体可以有不同的参数,比如颜色与缩放。如果绘制1000个物体,它将一个模型的vbo(显存中顶点信息的缓存区)提交一次给显卡,至于1000个物体不同的位置,状态,颜色等等将他们整合成一个per instance attribute的buffer给gpu,在显卡上区别绘制,它大大减少提交次数。目前适合游戏中的草、石头等元素较多的地方。

(使用静态,动态批处理物体的材质的所有参数是相同的。使用GPU Instancing的同一类物体的材质对象相同,但可以在代码中通过接口设置不同的参数,但仍会被批渲染)

4、GPU Skinning,使用GPU来做动画蒙皮的实现,用于提高CPU性能,同时可以减少内存占用。将CPU中的蒙皮工作转移到 GPU 中进行,减轻CPU的负担。

原理实际上是把骨骼矩阵存在配置文件里面,然后通过特殊的shader,计算顶点的位置,直接在GPU端得到了网格模型的顶点在动画帧该在的位置。这一切由于是在GPU端直接得出结果,所以根本不会产生CPU的合并和DrawCall。

(2)模型:降低几何复杂度。移除一些顶点上多余的信息,降低定点数。模型的LOG(多层次细节)

(3)模型:贴图/材质/网格 合并 。网格合并后对模型的渲染可以降低GPU的负担。

(4)模型:光照贴图 模型烘焙。

(5)相机的遮挡剔除 遮挡物和被遮挡物都要设置static 开启烘焙生效。

遮挡剔除 (Occlusion Culling) 功能可在对象因被其他物体遮挡,当前在相机中无法看到时,禁用对象渲染。该功能不会在三维计算机图形中自动开启,因为在大部分情况下,离相机最远的对象最先渲染,离相机近的对象覆盖先前的物体(该步骤称之为“重复渲染 (overdraw)”)。遮挡剔除 (Occlusion Culling) 与视锥体剔除 (Frustum Culling) 不同。视锥体剔除 (Frustum Culling) 只禁用相机视野外的对象渲染,不禁用视野中被遮挡的任何物体的渲染。注意,使用遮挡剔除 (Occlusion Culling) 功能时,仍将受益于视锥体剔除 (Frustum Culling)。

(6)Shader 的LOD技术。只有Shader的LOD值小于某个设定的值,该Shader才会被使用。

(7)Shader代码优化,尽量把计算放在顶点着色器上;使用低精度浮点值进行计算;尽量不适用分支语句、循环语句。

(8)粒子系统渲染因素:粒子系统个数、粒子个数。策略:减粒子系统个数、减粒子个数。CPU端耗时影响程度:粒子系统个数 > 粒子数。

UGUI的优化

1、在进行UI设计额时候尽量少使用来自不同Atlas的材质,也尽量避免倾轧的情况(Scene视图调节为Writeframe模式可以更容易查看),尽可能把所有的文字放到图片之上,使用同种字体,更容易进行合批。

2、 首先一个Mask组件就会产生一个Draw Call,而且在Mask中的图片无法与外界的图片进行合批

3、调节到OverDraw模式,颜色越鲜亮的地方造成的OverDraw越大,随之带来的GPU压力也是越大的

1)在ImageType选项为Sliced的情况下,不需要Fill Center 的时候去掉勾选。 2)Empty4Raycast。 3)Code PolygonImage

4、减少Raycast Target

5、避免网格重建(Canvas.BuildBatch)

采取的对应策略就是【动静分离】,在经常变动的UI元素(位置、颜色、图片等)上添加Canvas组件,就可以避免因为UI的改变造成整个Mesh全部重建。

特例:优化Image颜色动画造成的网格重建(脚本:MatColorAni)

四、物理优化

(1)OnCollisionEnter 和 OnTriggerEnter

1、当A,B都添加刚体(Rigidbody)时

OnCollisionEnter方法

A和B相互碰撞时,无论是谁碰撞的谁,两者都能触发OnCollisionEnter方法,前提是两者都没有勾选isTrigger。

OnTriggerEnter方法

A或者B中有一个勾选isTrigger或者两者都勾选isTrigger后,A和B都可以触发OnTriggerEnter方法,但是不可进入OnCollisionEnter方法。

注意:

OnCollisionEnter方法必须是在两个碰撞物体都不勾选isTrigger的前提下才能进入,反之只要勾选一个isTrigger那么就能进入OnTriggerEnter方法。

OnCollisionEnter和OnTriggerEnter是冲突的不能同时存在的。

2、当A,B有一个添加了刚体(Rigidbody)时

OnCollisionEnter方法

若A添加了刚体,B没有添加刚体,A去碰撞B,则A会被弹开,B不会运动,此时A、B都会触发OnCollisionEnter方法。

若A添加了刚体,B没有添加刚体,B去碰撞A,不会发生碰撞效果,此时A和B都不会触发OnCollisionEnter方法。

OnTriggerEnter方法

只要A和B中有一个添加了刚体,无论谁碰撞谁,两者都会触发 OnTriggerEnter方法

总结:

OnCollisionEnter方法要求碰撞的发起方必须拥有刚体,而被碰撞方有没有刚体并不重要。OnTriggerEnter方法则对此没有要求,只需要碰撞双方有一个具有刚体即可触发。即刚体是一个判断是否实现碰撞的是与否的标志。刚体对于系统的开销是很大的,所以在使用刚体时,根据可能发生的碰撞触发事件,适当的减少刚体,是一个减少资源消耗的好办法。 比如地面就可以不设置刚体,因为地面是永远不动的,把人物设置刚体就可以实现真实的物理碰撞效果了。

(2)最小化射线发射和边界体积检测。

(3)避免复杂的网格碰撞器。

(4)适当使用静态碰撞器。

运行时引入静态碰撞器,会重新生成它,产生CPU峰值。可以添加一个Rigidbody组件且开启Kinematic变成动态碰撞器,改变transform或施加力来移动。

(5)首选离散碰撞检测。 编辑器设置 Rigidbody组件的CollisionDetection属性:Discrete(离散)、Continuous(连续)、ContinuousDynamic(连续动态)

(6)调整允许的最大时间步长。编辑器设置Editor/ProjectSetting/TimeManager -> Maximum Allowed Timestep

(7)优化碰撞矩阵。编辑器设置Editor/ProjectSetting/PhysicsManger -> Layer Collision Matrix

(8)使物理对象休眠。 编辑器设置Editor/ProjectSetting/PhysicsManger -> Sleep Threshold

(9)修改处理器迭代次数。 编辑器设置Editor/ProjectSetting/PhysicsManger -> Default Solver Iterations

  • 7
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值