优化前提

Unity开发的游戏之所以比利用原生API开发的游戏包大,是因为Unity游戏包里有一个Mono虚拟机,游戏通过Mono虚拟机实现跨平台运行。在虚拟机上运行,游戏运行变卡变慢。因此,我们必须学会Unity性能优化,这样才能保证游戏的开发效率和游戏性能。

Unity性能优化最关键的指标是DrawCall的次数,DrawCall次数越多,性能越差。因此,优化时要降低游戏运行时的DrawCall次数。DrawCall就是CPU对绘制图形接口的调用,CPU通过调用图形库(DirectX或OpenGL)接口,命令GPU进行渲染操作。每次调用DrawCall前,CPU都会准备很多工作:检测渲染状态,提交渲染所需要的数据,提交渲染状态。这些准备工作会耗费大量的性能。

此外,渲染的游戏物体越多,DrawCall次数越多。Batches是Unity内置的性能优化方法,将具有相同材质的一类游戏物体合并成一批进行处理,减少DrawCall次数。因此,减少DrawCall次数方法之一就是合并DrawCall。Statistics面板中的verts和tris顶点数与三角面数作为渲染数据也和游戏模型的数量有关。SetPass Call是指Shader里的Pass方法调用次数,Pass调用次数就是模型渲染次数,次数越多性能越差。Batches、DrawCall和SetPass Call彼此之间有一定的关系,三者次数的减少都意味着性能消耗降低。

资源优化

资源优化主要是根据资源优化标准,对Mesh、模型、骨骼、音乐格式(长时间音乐比如背景音乐Compressed In Memory,采用mp3压缩格式,游戏运行时只需解压一次。而短时间音乐比如音效Decompress On Load,采用wav非压缩格式,由于压缩空间不大不需要压缩,此外,这样还可以减少音乐播放所需要的解压次数。)、贴图(宽度小于1024)以及Shader(尽量减少复杂运算,减少discard操作)等资源进行格式处理和内容调节。常用的方法包括:贴图合并,材质合并,模型优化,减少三角面和顶点数等。

除了资源自身,减少资源冗余也是必须的,要保证每一个文件只存在于唯一的目录位置,避免重复打包。Resources文件夹下不要放任何不相关的资源,因为Resources下的文件不管有没有使用都会被打包。StreamingAssets和Resources类似,只不过不会压缩该文件夹下的资源,而Resources会压缩。

Unity打包资源的流程是先确定需要打包场景,然后再将场景中的游戏物体以及与游戏物体相关的资源进行打包。除了Resources文件,其他没有使用到的文件一律不会打包。在开发期间使用Resources进行资源管理可以提高开发效率,但在实际商用时必须采用AssetBundle打包。使用AssetBundle时要注意包之间的依赖,可以共享资源单独打包,避免AssetBundle多次打包。

渲染(GPU)优化

在计算机中,GPU主要用于图形渲染,CPU主要用于数值计算。具体在游戏运行过程中,GPU主要用于场景渲染、特效处理和光照处理等,还用于大量并行的简单任务。CPU主要用于游戏中的伤害计算、随机数生成以及敌人AI的生成。此外,资源的实时加载也需要CPU。

在Unity编辑器中的Gizmos有一个Selection Wire(选择网格线)勾选框,勾选后就可以看到模型的网格线。网格线越多,模型越精细。使用LOD-层级细节技术可以根据视野远近来动态显示加载的模型,从而最大程度上减少GPU图形渲染的性能开销。LOD-层级细节技术就是指当视野比较远时,就用比较粗糙的模型;当视野比较近时,就用比较精细的模型。因为当视野比较远时,精细和粗糙模型差别不大,这时如果用精细模型就会造成性能浪费。Unity中的LODGroup组件分别对应三个不同游戏模型,还可以调节每个模型的可视距离。当摄像机距离太远时,模型就直接不显示。

LOD-层级细节技术只可以替换游戏模型,而遮挡剔除(Oculusion Culling)可以只渲染摄像机视野的游戏物体,视野外的物体直接不渲染。一般适用于游戏中需要渲染大量游戏物体时的场景。我们首先要将遮挡的游戏物体设置为Oculusider Static资源,然后Bake,再设置需要渲染场景的摄像机。当摄像机移动时,视野也会跟着变化,这样系统只会渲染摄像机看得见的游戏物体,摄像机看不见的游戏物体就不渲染。如果有多个不同的摄像机,不同的摄像机看到的视野不同,遮挡的范围也不同。通过LOD-层级细节技术,我们可以选择性加载我需要渲染的场景,从而节约性能开销。

图形渲染离不开光,当场景中有很多光源时,光照计算会消耗大量性能。光照贴图(LightMapping),实际上是预先将各种光源呈现的效果制作成贴图来替代运行时的光照计算,这种光又称为死光。游戏中的静态场景一般采用光照贴图的方式来有优化性能。烘焙的时间与游戏中的个数有关。烘焙流程如下:先在游戏物体的Mesh Renderer或Terrain组件里弃用lightmaps,然后将要用于烘焙的光源设置为Baked模式,然后就可以直接在LightMapping窗口下选择Generating Lighting,最后生成的光照贴图会存放在该Scene的同名目录下。烘焙结束后,移除光源,场景中的游戏物体仍呈现之前的光照效果。

除了优化资源的渲染方式,资源本身的数量也会影响GPU的渲染。在游戏场景效果没有明显差异的情况下,贴图、材质和Mesh越少,性能消耗越少。Mesh Render负责模型的外观渲染,Mesh Filter负责模型的形状。在Unity中,一般将使用相同材质Material的游戏物体Mesh进行合并。

代码(CPU)优化

CPU在给GPU发送DrawCall命令之前会准备大量的准备工作,准备工作做完后,就将这些数据传递给GPU进行渲染。因此游戏物体越多,CPU的准备工作就越多,就会增大CPU的性能开销。因此,我们可以通过代码来动态合并具有相同材质Material的游戏物体,然后再删除之前的物体,从而达到合并Mesh减少Material的目的。此外,开发过程中尽量使用较少的材质,一般一个物体对应一个材质,一个材质对应一个Shader。对于Shader里面的Reflection和Shadow阴影等高级特性也要尽量少用。

除了通过合并游戏物体来减少游戏物体的数量之外,资源池Object Pool也是减少游戏物体的最佳方法之一。常见使用场景为:射击游戏中的子弹和跑酷游戏中的金币或障碍物等。如果游戏场景中,有频繁创建和销毁的游戏对象,都可以用Object Pool对象池技术。对象池的回收和激活是通过设置游戏对象SetActive函数的参数值为true或false来实现。对象池初始化几个未激活(SetActive(false))的游戏对象,然后使用时就激活(SetActive(true)),使用完后恢复到初始化状态(SetActive(false))后就放回了对象池。通过回收游戏对象可以避免多次重复实例化相同的游戏对象,从而减少渲染游戏对象所消耗的性能。

总结

上面提到的优化技术只是最基本的Unity性能优化方法,这里推荐UWA出品的Unity性能优化技术专题,可以利用空闲时间研究下。Unity性能优化涉及到的技术非常多,初学者只需了解一些常用的优化方法,更重要的是在游戏开发实践中不断摸索和进步!应用层面的软件开发一定要多实践,用项目去驱动学习,先会用再深入原理,这才是编程的最佳学习方法!