下面以参与该款游戏《Finding Monsters》(寻找萌兽)的开发者Phil Lira的角度为大家分享开发中的经验和干货。

在讲具体的优化细节之前,先看看该游戏的整体状况。该作共有13个关卡和一个可交互的开始房间。游戏最多可达130个DrawCall和18万可见三角形。开启了抗锯齿效果(MSAA 2x)并且场景同时可能出现4个怪兽。怪兽骨骼数最多可达69根,每个顶点最多可被4根骨骼影响。所有的着色器都是自定义的。
手机第一次连接到GearVR时就会默认下载并安装游戏运行所需的所有动态库。GearVR Services就是其中之一,它包含开发者模式的选项用于直接对游戏进行分析和测试。开启开发者模式时,游戏会在VR模式下运行但不必连接到GearVR。

该选项默认是隐藏的,需要依次点击 Application Manager -> All -> GearVR Service -> Manage Storage进入管理界面,并连续单机VR Service Version7次(蛮牛酱没试过这个节奏),然后就可以看到开发者模式的切换按钮了。

测试后得知,GearVR的开发者模式本身不会带来额外的消耗。唯一的缺点是模拟头部追踪时陀螺仪默认是不起作用的。但只需简单的创建脚本来模拟即可解决。这里是用了一个简单的滑块来控制相机旋转。

分析是关键
在涉及到优化时不能轻易假设。举个例子,随着显示屏分辨率越来越高,对于移动游戏来说填充率也越来越重要,所以你可能会认为VR游戏也是如此。但VR的渲染纹理大小通常是1024x1024,所以驱动的开销才是更频繁的问题。时刻注意游戏的DrawCall才是黄金法则,Oculus建议将DrawCall保持在100以内。

我们的优化过程都遵循三个基本步骤:分析、优化、测试。
分析过程中我们主要是利用分析工具确定游戏瓶颈,一定要找准瓶颈进行优化,否则只是浪费时间。瓶颈确定以后,测量每帧完成所需的时间。用于与优化过之后进行对比。

优化过程通常就是重新完成某个过程,在消耗更少资源的情况下实现同样的效果。

最后就是对优化结果进行测试。我们要知道每帧所节省的时间。极度重要的一点是要尽量保证测试时的场景与游戏过程都与优化前保持完全一致。否则可能会产生误导性的结果。然后就是反复迭代三个步骤直至游戏达到理想的帧率。

在VR游戏中要想达到稳定的每秒60帧,就必须保证在14毫秒内完成每帧的绘制。因为在进行垂直同步之前还要预留足够的时间给镜头畸变和时间扭曲。VR里面时间扭曲是降低延迟的关键,并且能够完美弥补空缺的帧,在GearVR中表现也很好。

Unity 自带Profiler就够用了。它会暂停帧并给出关键部分的时间戳。还可使用Profiler.BeginSample /Profiler.EndSample来获取各时间戳所对应的脚本区域。

优化DrawCall
游戏中一些场景的驱动消耗约有7、8毫秒。尽管已经合理使用了静态或动态批处理进行婚礼混淆,但DrawCall依然居高不下。

还好unity5有自带的帧调试器,你可以独立查看每个DrawCall是由哪些部分产生的。Unity中依次点击Window -> Frame Debugger可以打开帧调试器。

调试后发现很多原本应该一起处理的对象结果没有。这是由光照贴图引起的。场景用到了一张1024x1024的光照贴图集其中约有4~7张光照贴图。由于光照贴图纹理不会显示在材质检视面板,所以大多开发者都不会注意到这点。这里我们将图集尺寸改为2048x2048并调整了光照贴图分辨率以放入一张图集。

依次点击Window -> Lighting,在General GI下点击Atlas Size下拉列表可更改图集尺寸。如果是Unity4.x的版本,就必须写脚本来改变LightmapEditorSettings的MaxWidth 和 MaxHeight。

另一个导致DrawCall比较高的元素就是UI。一开始为UI定义了很多层Unity都作为单独的DrawCall来处理。

之后将能合并的斤两合并到背景以保证所有的UI元素尽可能使用最少的层。

优化加载时间
如果加载时间过长,首先要检查的就是纹理压缩设置。尽管GearVR支持ASTC压缩,但Unity只在OpenGL 3支持该格式。如果构建的设备不支持所选的纹理压缩格式,Unity会在显示开始动画时解压缩所有纹理,从而导致加载时间过长。

另外就是分析加载时长要关闭Autoconnect Profiler选项。开启该选项会在连接Unity编辑器的Profiler时消耗几秒。

如果不是以上原因,就要使用ADB来查看所有Debug.Log调用的时间戳以确定问题所在了。可以在第一个脚本的Awake函数中使用Debug.Log打印当前时间。然后在控制台使用以下命令行

adb logcat -v time -s Unity > startupTime.txt

将每个Unity调用的时间输出到startupTime.txt文件。

我们的游戏加载时间过长的原因就是第一个场景的某个对象引用了太多不必要的声音资源。

优化过度绘制
过度绘制就是某个像素被渲染目标绘制多次。下面是两个卧室场景,一个是正常纹理,另一个带有过度绘制的调试着色器:

在过度绘制那张图中,白色像素就表示被绘制了多次。场景中物体的渲染顺序会影响过度绘制,因为GPU会提前检测面片是否会被覆盖。如果被覆盖则不绘制。

主要的改善办法就是按照从前往后的顺序排列透明物体。Unity会基于物体中心到相机的距离来进行排序,所以某些情况下会有问题。

例如,上图中所有物体都是在墙前面绘制的,尽管墙面离相机要更远。这是因为墙面是单独的网格,它的中心离相机更近,如下图:

解决办法是将墙面放到不同的层下。本游戏中我们创建了自定义的检视面板以便在编辑器中查看Material.renderQueue:

这样就可以像精灵那样配置各层,比使用Shader要更方便也更可控。

Mali Graphics Debuggger和Adreno Profiler工具可帮助查看并定位场景中过度绘制的图片。虽然管用,但一次只看一帧不太现实。如果想实时查看怎么办呢?

我们的办法是自定义着色器来展示过度绘制的部分。该着色器很简单,渲染场景并将输出颜色设为fixed4(0.1, 0.1, 0.1, 1.0) 。然后使用Camera Shader Replacement将它应用到所有对象。这样就能实时查看并切换正常纹理和过度绘制纹理视图。

对GPU和CPU动态节流
Oculus Mobile SDK支持对CPU和GPU降频以节省电量并降低发热,称为节流。将游戏调整到合适的节流层次可增加游戏可玩时长。也可动态进行调整。

我们的游戏选用了比较保守的层次。但也有层次可支持同时显示4个怪兽。使用目前的层次设置,可以节省5、6毫秒的时间用于更新怪兽(物理、AI、蒙皮),这也为节省每帧消耗掉时间作出了贡献。当然移除怪兽也不可取,所以我们对那些关切使用了比较高的层次以支撑多余的怪兽带来的开销。

结论
优化是开发伊始就要考虑的问题。要向整个团队制定统一的性能要求。这能极大避免出现游戏开发后期因性能问题导致返工,从而需要重新制作所有美术资源及关卡的问题。

VR开发对于引擎的选择也至关重要。Unity对于VR开发已有了相当大的进步及优化,并且还在持续改善中。我们也建议大家使用Unity进行VR游戏开发。