unity性能优化Unity3d相关(M)Unity3D

Unity全面优化

2015-10-28  本文已影响5641人  欣羽馨予

前言

Unity的项目优化已经是老生常谈,很多人在项目完成之后,即便创意新颖,也会觉得差强人意,原因就在于没有做详细的项目优化。众所周知,Unity是一个综合性的3D开发引擎,其中包含图像渲染,逻辑处理,数据存储,发布测试等等各方面的内容。因此Unity各个方面都存在的待优化的内容,也可以说项目优化是项目开发中必不可少的一项工作。本篇文章会从项目的各个方面分析Unity待优化的内容,并给出优化方案,全面优化你的项目。优化项目无非是减轻系统的功耗负担,故下面从CPU、GPU、内存三方面的优化来讲解。

大纲

什么是DrawCall?
Unity每次在准备数据并通知GPU渲染的过程称为一次DrawCall。
为什么要优化DrawCall?
因为通知给GPU这个工作是由CPU来完成的,
完成每一次DrawCall都需要CPU完成很多的内容,
因此如果DrawCall很多的话CPU一定是不堪重负。
但对于GPU而言,很多的工作都是一样的,
也就是说很多的DrawCall是没有意义的,
可以理解为很多次的DrawCall都可以合并为一次,
这样减轻了CPU的负担,
同样也可以通知GPU完成相应的工作。
因此减少DrawCall成了优化CPU的第一要务。

    - 降低DrawCall第一步-->Draw Call Batching(批处理)

两个或多个纹理相同或材质相同的网格模型可以批量处理他们的材质,这样就可以将多个模型的材质DrawCall合并为一个,从而达到减少DrawCall的目的。批处理是系统工作范畴,我们只需要选择即可,批处理又分为静态批处理和动态批处理。

        1. Static Batching 静态批处理

场景中有很多游戏对象,其中静态对象(Inspector勾选Static的)可以通过静态批处理来优化DrawCall。


静态游戏对象

下面我们通过具体实例来验证:
在场景中创建四个游戏对象Cube,Sphere, Capsule, Cylinder,默认为非静态。此时运行游戏,DrawCall如下。


非静态四个对象的DrawCall为4 此时,DrawCall为4,Saved by batching为0。
然后,我们将四个对象都设置成静态Static,再看DrawCall。
设置成静态之后,通过静批DrawCall变为1

此时,DrawCall变为1,Saved by batching变为3。
当然,目前我们只有四个对象,如果在项目中超多的相同材质静态对象或相同纹理的静态对象,DrawCall减少量更大,优化做的也就会更好。
2. Dynamic Batching 动态批处理

上面的静态批处理需要给对象设置成静态,而动态批处理,则不需要,非静态的对象,系统自动做批处理。
同样,我们举例说明,通过预设体创建500个cube。
for(int i = 0; i < 500; i++)
{
GameObject cube = GameObject.Instantiate(cubePefab) as GameObject;
}
DrawCall的数量为:


动态批处理后的DrawCall数量

DrawCall为1,Saved by batching变为499,是不是很爽。
然而,事实上动态批处理有很多约束。我们同样是创建500个物体,不同的是其中的100个物体,每个物体的大小都不同,也就是Scale不同。
for(int i = 0; i < 500; i++)
{
GameObject cube = GameObject.Instantiate(prefab) as GameObject;
if(i / 100 == 0)
{
cube.transform.localScale = new Vector3(2 + i, 2 + i, 2 + i);
}
}
此时,DrawCall的数量:


变换缩放后的动批 DrawCall变为101,Saved by batching变为399,OhMyGod,小小的变换一下缩放,就不能批处理了,简直有点不智能。所以,在使用动批的时候,需要注意一下,如果你的动批不起作用可能是一下原因。

物理组件是我们在游戏开发中经常用到的组件,比起设计超级复杂的伤害计算算法,一个Trigger就能解决很多问题,还有那可以模拟一切物理效果的Rigidbody。但如果物理组件使用过多,计算量过大,也会造成CPU过载。对于物理组件的优化,有以下两点。
- 设置一个合适的Fixed Timestep。
我们都知道在计算物理逻辑的时候通常会将代码放到FixedUpdate里面,然而FixedUpdate的执行频率,就由Fixed Timestep决定,并不是所有的游戏中物理计算都需要0.02秒执行一次。因此这个值,可以针对项目慢慢调试,设置出一个比较合适的值,这样即完成了物理计算,也可以减轻CPU的负担。所以,想想自己上高中物理的时候做的物理大题,让CPU少做几道,确实可以轻松很多。换位思考也可以这样对吧,换到CPU的角度考虑。
- 不要使用MeshCollider
原因很简单,因为MeshCollider太复杂了,想起我当年做秘密行动的时候一次次的崩溃,也是醉了,就是因为加了一个超大的MeshCollider,不夸张的说真是要一分钟崩溃一次,更严重的是崩溃完之后,场景文件都没有了,可见其威力巨大。

秘密行动中的MeshCollider 当然,从性能优化的角度考虑,物理组件能少用还是少用为好。
- GC(Garbage Collection垃圾回收)

GC是用来处理内存的,为什么会影响到CPU的开销呢?因为GC是CPU调度的。大量的调用GC确实可以回收内存,但如果内存占用量不是很大的情况下,调用GC的性价比就很低,因为GC对CPU的开销所造成的代价更大。所以优化GC,就是减少对GC的调用。
首先我们要明确所谓的GC是Mono运行时的机制,
而非Unity3D游戏引擎的机制,
所以GC也主要是针对Mono的对象来说的,

        而它管理的也是Mono的托管堆。
        其次我们要搞清楚什么东西会被分配到托管堆上?
        不错咯,就是引用类型咯。
        比如类的实例,字符串,数组等等。
        而作为int,float,包括结构体struct其实都是值类型,
        它们会被分配在堆栈上而非堆上。
        所以我们关注的对象无外乎就是类实例,字符串,数组这些了。

那么GC什么时候会触发呢?两种情况:
首先当然是我们的堆的内存不足时,会自动调用GC。
其次呢,作为编程人员,我们自己也可以手动的调用GC。
所以为了达到优化CPU的目的,我们就不能频繁的触发GC。
而上文也说了GC处理的是托管堆,而不是Unity3D引擎的那些资源,
所以GC的优化说白了也就是代码的优化。
以下几点是需要注意的:

代码质量优化老生常谈,这里简单提几点:

GPU主要处理图像渲染,与CPU不同,侧重点自然也不同。GPU需要优化的点主要有以下几点:

优化方案很简单,减少绘制的数目,无非是减少顶点数量,简化复杂度,举措如下。

再有就是压缩图片,减小显存带宽的压力
- OpenGL ES 2.0使用ETC1格式压缩等等,在打包设置那里都有。
- 使用MipMap。


*上面是一个mipmap 如何储存的例子,左边的主图伴有一系列逐层缩小的备份小图*

Mipmap中每一个层级的小图都是主图的一个特定比例的缩小细节的复制品。因为存了主图和它的那些缩小的复制品,所以内存占用会比之前大。但是为何又优化了显存带宽呢?因为可以根据实际情况,选择适合的小图来渲染。所以,虽然会消耗一些内存,但是为了图片渲染的质量(比压缩要好),这种方式也是推荐的。

谈到内存优化,首先要看一下,Unity开发中需要用到哪些内存。

Unity3D的内部内存都会存放一些什么呢?

因为我们的游戏脚本是用C#写的,同时还要跨平台,所以带着一个Mono的托管环境显然必须的。那么Mono的托管内存自然就不得不放到内存的优化范畴中进行考虑。那么我们所说的Mono托管内存中存放的东西和Unity3D内部内存中存放的东西究竟有何不同呢?其实Mono的内存分配就是很传统的运行时内存的分配了:


结束语

项目优化至关重要,因为现在的产品大同小异的很多,游戏也是如此,无非就是那么几种类型,在保持创新精神的基础上,还要着重关注的就是用户体验。用户体验这个词不是新词了,所以怎样提高用户体验呢,流畅、明朗、便捷。当然,这些都取决与项目优化。所以,是你的产品就好好优化它,因为每个产品都可以变得更好。

【参考文献】诺克萨斯的小匹夫


上一篇下一篇

猜你喜欢

热点阅读