unity3D优化(全面版)供学习或面试总结使用

关于unity3D的优化问题 我之前我写过一些博文
今天我把所有的方面总结一下

unity优化要从哪方面开始着手

提到优化,很多人想到就是无脑降低DrawCall的指标 但是这个指标并非全部
我们优化的时候要注意三个方面
1.CPU方面 2.GPU方面 3.内存方面

CPU方面

DrawCall是知名度最高的优化点 但是除了DrawCall还有一些需要的优化的地方
1.DrawCalls 2.物理组件 3.GC 4.脚本代码的质量

对DrawCall优化

DrawCall是CPU调用底层图形的接口 比如有上千个物体,每一次渲染都要调用一次底层接口,每一次CPU就有很大的工作量。但是对于CPU来说图形处理的工作量是相同的,所以对于DrawCall的优化,只要是为了减少解放CPU在调用图形接口的开销。
所以我们的思路就是尽量减少每个物体的渲染次数,多个物体最好一次渲染
所以下面给出三种方案
(1)使用Draw Call Batching,也就是描绘调用批处理,unity运行时可以将一些物体进行合并,从而用一个描绘调用来渲染他们。
(2)通过把纹理打包成图集尽量减少材质的使用
(3)尽量减少使用反光,阴影之类的效果,因为那会使物体多次渲染

批处理

使用Draw Call Batching批处理

首先要知道为什么没有两个相同材质的物体即使使用批处理,也无法降低Draw Call数量的下降和性能的提升
因为批处理的两个物体的网络模型需要相同的材质,纹理也要相同,这样才能实现同时渲染的目的。
因此必须保证材质的相同,是为了保证被渲染的纹理相同
所以为了把两个纹理不同的材质合二为一,就需要将纹理打包成图集。
而Draw Call Batching本身还细分为Static Batching静态批处理和Dynamic Batching动态批处理两种

Static Batching静态批处理

所谓静态,也就是状态不会改变,所以我们定义:只要物体不移动,并且具有相同的材质,
静态批处理就允许引擎对任意大小的集合物体进行批处理实现降低描绘的调用

操作:只需要把检测器(Inspector)中勾选static复选框即可
在这里插入图片描述

下面我们测试一下
我们创建4个不同的物体 分别是Cube Sphere Capsule Cylinder 他们具有不同的网格模型,但是也具有相同的材质
首先不指定他们是Static类型的
在这里插入图片描述
然后是勾选static之后的

Dynamic Batching动态批处理
unity的DrawCall的动态批处理机制是引擎自动进行的 无需像静态批处理一样手动设置Static 举一个动态实例化预制体的例子
如果动态物体共享相同的材的话 引擎会自动对DrawCall进行优化,也就是动态批处理。
首先把一个Cube做成prefebs 然后实例化500次 看看效率

 public GameObject cube;
    private void Start()
    {
        for(int i = 0; i < 500; i++)
        {
            Instantiate(cube);
        }
    }

在这里插入图片描述

对物理组件的优化

假如我们在做一个策略类游戏的时候 需要在单元格上边排兵布阵,而要侦查到哪儿个兵站在哪个格子的时候 我们很可能使用射线
来检测,由于士兵的数量很多,而且为了精确的每一帧都会执行检测,那时候的CPU的负担会惨不忍睹。
下面介绍两个优化的方法
(1)设置一个合适的Fixed Timestep
首先要明白为什么要设置Fixed Timestep,因为Fixed Timestep和物理组件又很紧密的联系。物理组件在游戏中模拟各种物理效果的组件
最重要的是什么呢?当然是计算,通过计算才能将真实的效果展现在虚拟的游戏中,在unity中Fixed Timestep这个指标是和物理计算有关的。所以如果计算频率太高的话 自然会影响到CPU的开销。同时如果计算频率达不到游戏设计的要求的话,又打不到游戏的要求。
所以这就需要开发人员的具体分析
在这里插入图片描述
(2)尽量不要使用网络碰转器(mesh collider)由于mesh collider太过复杂。mesh collider利用一个网格资源并在其上构造碰撞器。对复杂的网状模型实现碰撞检测。比原有的碰撞模型是要准确的多。手机游戏明显无须这种性价比不高的东西
当然从性能优化的角度考虑,物理组件能少用,还是少用为好。

处理内存,却让CPU受伤的GC

GC虽然是用来处理内存的,但是确实可以增加内存的开销。因此,的确可以实现释放内存的效果,但是代价也的确沉重
加重CPU的负担,因此对于GC 的优化目标就是尽量少触发GC。
首先明确所谓的GC是Mono运行的机制,并非是unity引擎的机制
所以GC也是针对于Mono的对象来说的。他是管理Mono的托管堆,清楚这一点,也就明白GC不是用来处理assets中的纹理 音效等
因为unity引擎也有自己的内存堆 而不是和Mono一起使用所谓的托管堆。

其次我们要清楚 什么东西会被分配到托管堆上,那就是引用类型比如类的实例,字符串,数组等,作为int类型,float类型
包括结构体struct其实都是值类型,他们会被分配到堆栈上,并非堆上。所以我们关心的无非是类实例,字符串,数组。
那么GC在什么情况会被触发呢?
堆的内存不足,自动调用GC
作为编程人员,可手动调用GC

所以为了达到优化CPU的目的,就不能频繁的触发GC,而之前介绍了GC处理的是托管堆,而不是unity的引擎的资源,所以GC 的优化说白了也就是代码的优化。
所以我提醒读者五点
(1)字符串的连接的处理,因为有字符串连接的过程,其实是一个生成新的字符串的过程,而之前的字符串就成了垃圾,而作为引用类型的字符串也就自然分配到了堆上,被弃之的旧字符串就会触发GC 当做垃圾的回收
所以我们在字符串的使用上,尽量使用StringBuilder来操作
之前我专门写过这个相关的内容的博文

(2)尽量不要使用foreach来循环,而是使用for循环。foreach实质上是实际到了迭代器的使用,每一次循环所产生的的迭代器的垃圾达到了24Bytes 那么循环10次也就是240Bytes

(3)不要直接访问gameobject中的tag比如if(go.tag==“me”)最好换成if(go.CompareTag(“me”))
因为访问物体的tag的时候 属性会在堆上额外分配空间,如果在循环中这样处理 后果不堪设想

(4)使用“池” 比如对象池来实现空间的反复利用
我之前写过一篇功能比较完整的对象池博文
给大家参考

(5)最好不要使用LINQ的命令,他会分配额外的空间,同样也是GC收集的目标。

代码的质量的优化

说道这个可能很多人感觉多此一举,其实对于代码的优化也是很重要的。
(1)以物体的Transfrom为例,我们应该只访问一次,之后就把他的引用保留,而非每次使用都去访问。
我查到一组数据
GetComponent=619ms
Monobehaviour=60ms
CachedMB=8ms
Manual Cache=3ms
(2)最好不要频繁使用GetComponent,尤其在循环中
(3)善于使用OnBecameVisible()和OnBecameInvisible ()来控制物体的update函数执行,以减少开销
(4)使用内建的数组,比如Vector3.zero而不是new Vector3(0,0,0)
(5)对于方法参数的优化,善于使用ref关键字,值类型的参数是通过将实参的值赋值到形参
来实现值传递的方法,也就是通常的值传递,
(6)减少粒子系统的play的调用次数
(7)transform的孩子节点不该过多(特别是包含粒子系统的时候)
(8)应减少不必要的Transform.position/rotation等访问
(9)如果可能,尽量用Queue/Stack来代替List

对于GPU的优化

CPU和GPU的不同,所以侧重点自然也不一样。
(1)填充率,可以简单理解为图形处理单元每秒渲染的像素数量
(2)像素的复杂度,比如动态阴影 ,光照,复杂的shader等
(3)几何体的复杂度
(4)GPU的显存带宽

针对上面的方面 我们优化的方法也就比较明显了
(1)减少定点数量,简化计算复杂度
(2)压缩图片,以适应显存带宽

对于内存的优化

首先我们要知道unity的运行的时候内存是怎么分配的
(1)unity的内存分布
(2)Mono的托管内存
(3)若干我们引入的DLL或者第三方DLL所需要的内存

unity内存中存放的东西
(1)资源:纹理,网格,音频等
(2) GameObject和各种组件
(3) 引擎内部逻辑需要的内存:渲染器,物理系统,粒子系统

Mono托管内存

我们的游戏脚本使用c#写的,同时还要跨平台,所以带着一个Mono的托管环境显然是必须的
(1)值类型:int,float,结构体,bool 之类的,他们存放在堆栈上(不是GC)
(2) 引用类型:其实可以理解为各种类的实例,比如游戏脚本中对游戏引擎中各种控件的封装
其实很好理解,c#中坑定要有对应的类去对应引擎中的各种控件,这部分就是c#的封装,由于在堆中分配,会用到GC

对UI的优化

优先级高

1.同一图集的Image元素尽量保证在Hirrachy中连续,避免中间插入其他图集,或者插入文本
2.避免图片叠加在一起(遮挡,旋转)
3.透明的Image 用来所响应事件也会存在性能开销
4.不要勾选Rarcasts 不需要的情况下
5.避免或者减少Mask的使用,一个Mask至少增加2个DrawCall
6.避免频繁动态更新UI元素的vertex Rext Color Material
7.避免UI元素数目过多和层次结果过于复杂影响Batch更新速度
8.固定的Text考虑与背景图层合在一张图上

优先级低

1.降低界面的更新频率
2.避免图集分离,使用相同的图集
3.如果sprite是中间镂空且切图为九宫格的时候,可以去除fill center 减少overdraw
4.避免频繁删除/增加UI对象,UI层次结构变化会引起Canvas的更新Texture等,
5.尽可能少的使用UI Material和贴图(使用图集)
6.同一父节点下所有子节点,保证相同的层次结构,便于底层相同depth选UI元素的Batch

优化的一些方法

遮挡剔除

这个功能可以用在对象因为其他物体遮挡,当前相机无法看到的时候,禁用物体的渲染
这个功能需要手动开启,大部分情况下:离相机最远的对象最先渲染,离相机近的物体覆盖之前的物体
遮挡剔除和视椎体剔除不同视椎体渲染知识剔除摄像机视野之外的渲染,不禁用视野中被遮挡的任何物体的渲染
使用遮挡剔除的时候,仍然受益于视椎体剔除

如果场景中包含大量模型,一定会造成渲染效率的降低,如果使用遮挡剔除的技术,
可以使那些被阻挡的物体不被渲染达到高效的效果

遮挡剔除的基本原理:
场景中创建一个遮挡区域,遮挡区域由单元格组成,每个单元格构成整个场景遮挡区域的一部分
这些单元格会将场景拆开为多个部分,当摄像机可以看到单元格的时候,单元格中的物体会被渲染出来
其他被挡住的单元格不会被渲染

层级消隐

如果场景中存在大量小物体,我们可以考虑使用层级消隐
**层级消隐:**将距离比较远的物小物体剔除,减少绘制调用的次数
例如:在一个场景中 比较远的地方有大型建筑物 还有一些细节(石块等)我们就可以将这些小物件隐藏掉
将小物件单独放在一个层中,然后通过操作层来操作这些小物块

LOD技术

根据物体在游戏画面中所占据的百分比来调用不同复杂度的模型
当一个物体距离比较远的地方,使用复杂度低的模型,距离比较近的时候,使用复杂度高的模型

批处理(上文还有我之前有专门的文章讲过)大致说下使用

将一些游戏物体进行合并,同时渲染(相同材质)
动态批处理: 自动进行,不用干预,顶点数900之内可移动物体 相同材质
**静态批处理: ** 手动设置静态,永远不能由移动,缩放,旋转,(同材质)

尽量多的使用预制体(会被批处理)

减少游戏对象的镜像缩放
分别拥有大小(-1,1,1)和(1,1,1)的两个对象将不会进行批处理。

异步加载

游戏开发中的资源加载比较耗时,如果用同步加载的方式很可能产生卡顿的现象
所以我们采用异步的机制使用协程来实现异步加载
异步加载的好处就在于不会产生阻塞的现象

举个例子 假如资源比较庞大 使用同步加载 之后的部分(UI的显示等)都在等着加载的完成
这就可能造成视觉上的卡顿的现象(造成不理想的游戏效果)
使用异步加载 就是调用异步方法之后 并不会阻塞其他行为的执行
加载工作由后台开辟的另一个异步线程执行

以上就是这篇博文的所有内容

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值