引言
对问题认识不清以及过度优化往往会让事情变得更加复杂,产生更多的程序错误。然而,如果我们在游戏开发过程中从来都没有考虑优化,那么结果往往是惨不忍睹的。一个正确的做法是,从一开始就把优化当成是游戏设计中的一部分。 和PC相比,移动设备上的GPU有着完全不同的架构设计,它能使用的带宽、功能和其他资源都非常有限。这要求我们需要时刻把优化谨记在心,才可以避免等到项目完成时才发现游戏根本无法在移动设备上流畅运行的结果。
游戏优化不仅是程序员的工作,更需要美工人员在游戏的美术上进行一定的权衡,例如,避免使用全屏的屏幕特效,避免使用计算复杂的shader,减少透明混合造成的overdraw等。也就是说,这是由程序员和美工人员等各个部分人员共同参与的工作。
移动平台的特点
和PC平台相比,移动平台上的GPU架构有很大的不同。由于处理资源等条件的限制,移动设备上的GPU架构专注于尽可能使用更小的带宽和功能,也由此带来了许多和PC平台完全不同的现象。例如,为了尽可能移除那些隐藏的表面,减少overdraw(即一个像素被绘制多次), PowerVR芯片(通常用于iOS设备和某些Android设备)使用了基于瓦片的延迟渲染(Tiled-based Deferred Rendering, TBDR)架构,把所有的渲染图像装入一个个瓦片(tile)中,再由硬件找到可见的片元,而只有这些可见片元才会执行片元着色器。另一些基于瓦片的GPU架构,如Adreno(高通的芯片)和Mali(ARM的芯片)则会使用Early-Z或相似的技术进行一个低精度的的深度检测,来剔除那些不需要渲染的片元。还有一些GPU,如Tegra(英伟达的芯片),则使用了传统的架构设计,因此在这些设备上,overdraw更可能造成性能的瓶颈。
由于这些芯片架构造成的不同,一些游戏往往需要针对不同的芯片发布不同的版本,以便对每个芯片进行更有针对性的优化。尤其是在Android平台上,不同设备使用的硬件,如图形芯片、屏幕分辨率等,大相径庭,这对图形优化提出了更高的挑战。相比与Android平台,iOS平台的硬件条件则相对统一。读者可以在Unity手册的iOS硬件指南中找到相关的资料。
影响性能的因素
对于一个游戏来说,它主要需要使用两种计算资源:CPU和GPU。它们会互相合作,来让我们的游戏可以在预期的帧率和分辨率下工作。其中,CPU主要负责保证帧率,GPU主要负责分辨率相关的一些处理。
对于CPU来说,限制它的主要是每一帧中draw call的数目。(简单来说,就是CPU在每次通知GPU进行渲染之前,都需要提前准备好顶点数据,然后调用一系列API把它们放到GPU可以访问到的指定位置,最后,调用一个绘制命令”。调用绘制命令的时候,就会产生一个draw call。)过多的draw call会造成CPU的性能瓶颈,这是因为每次调用draw call时,CPU往往都需要改变很多渲染状态的设置,而这些操作是非常耗时的。如果一帧中需要的draw call数目过多的话,就会导致CPU把大部分时间都花费在提交draw call的工作上面了。当然,其他原因也可能造成CPU瓶颈,例如物理、布料模拟、蒙皮、粒子模拟等,这些都是计算量很大的操作。
而对于GPU来说,它负责整个渲染流水线。它从处理CPU传递过来的模型数据开始,进行顶点着色器、片元着色器等一系列工作,最后输出屏幕上的每个像素。因此,GPU的性能瓶颈和需要处理的顶点数目、屏幕分辨率、显存等因素有关。而相关的优化策略可以从减少处理的数据规模(包括顶点数目和片元数目)、减少运算复杂度等方面入手。
据此,我们可以把造成游戏性能瓶颈的主要原因及相应应对措施分成以下几个方面:
(1)CPU
- 过多的draw call。 —> 使用批处理技术减少draw call数目。
- 复杂的脚本或者物理模拟。
(2)GPU
- 顶点处理:
- 过多的顶点。 —> 减少需要处理的顶点数目(优化几何体、模型LOD、 遮挡剔除)。
- 过多的逐顶点计算。—> 使用Shader的LOD技术。
- 片元处理: —> 减少需要处理的片元数目(控制绘制顺序、 警惕透明物体、减少实时光照)。
- 过多的片元(既可能是由于分辨率造成的,也可能是由于overdraw造成的)。
- 过多的逐片元计算。 —> 代码方面的优化。
(3)带宽 :
- 使用了尺寸很大且未压缩的纹理。 —> 减少纹理大小。
- 分辨率过高的帧缓存。—> 利用分辨率缩放。
Unity中的渲染分析工具
Unity内置了一些工具,来帮助我们方便地查看和渲染相关的各个统计数据。这些数据可以帮助我们分析游戏渲染性能,从而更有针对性地进行优化。在Unity 5中,这些工具包括了渲染统计窗口(Rendering StatisticsWindow)、性能分析器(Profiler),以及帧调试器(FrameDebugger)。需要注意的是,在不同的目标平台上,这些工具中显示的数据也会发生变化。
(1)渲染统计窗口(Rendering StatisticsWindow)
Unity 5提供了渲染统计窗口(RenderingStatistics Window)来显示当前游戏的各个渲染统计变量,我们可以通过在Game视图右上方的菜单中单击Stats按钮来打开它。从图中可以看出,渲染统计窗口主要包含了2个方面的信息:音频(Audio)、图像(Graphics)。我们这里只关注第二个方面,即图像相关的渲染统计结果。
渲染统计窗口中显示了很多重要的渲染数据,例如FPS、批处理数目、顶点和三角网格的数目等。下表列出了渲染统计窗口中显示的各个信息。
(2)性能分析器的渲染区域
我们可以通过单击Window -> Profiler来打开Unity的性能分析器(Profiler)。性能分析器中的渲染区域Rendering Area)提供了更多关于渲染的统计信息,下图是某个场景的渲染分析结果。
性能分析器显示了绝大部分在渲染统计窗口中提供的信息,例如,绿线显示了批处理数目、蓝线显示了Pass数目等。最下方的窗口还给出了许多其他非常有用的信息,例如,draw call数目、动态批处理/静态批处理的数目、渲染纹理的数目和内存占用等。结合渲染统计窗口和性能分析器,我们可以查看与渲染相关的绝大多数重要的数据。一个值得注意的现象是,性能分析器给出的draw call数目和批处理数目、Pass数目并不相等,并且看起来好像要大于我们估算的数目,这是因为Unity在背后需要进行很多工作,例如,初始化各个缓存、为阴影更新深度纹理和阴影映射纹理等,因此需要花费比“预期”更多的drawcall。
(3)帧调试器
我们可以通过Window -> Frame Debugger来打开它。在这个窗口中,我们可以清楚地看到每一个draw call的工作和结果。帧调试器的调试面板上显示了渲染这一帧所需要的所有的渲染事件,在本例中,事件数目为30,。通过单击面板上的每个事件,我们可以在Game视图查看该事件的绘制结果,同时渲染统计面板上的数据也会显示成截止到当前事件为止的各个渲染统计数据。
Unity的渲染统计窗口、分析器和帧调试器这3个利器的帮助下,我们可以获得很多有用的优化信息。但是,很多诸如渲染时间这样的数据是基于当前的开发平台得到的,而非真机上的结果。我们仍然需要一些外部的性能分析工具的帮助。
其他性能分析工具
对于移动平台上的游戏来说,我们更希望得到在真机上运行游戏时的性能数据。这时,Unity目前提供的各个工具可能就不再能满足我们的需求了。
对于Android平台来说,高通的Adreno分析工具可以对不同的测试机进行详细的性能分析。英伟达提供了NVPerfHUD工具来帮助我们得到几乎所有需要的性能分析数据,例如,每个draw call的GPU时间,每个shader花费的cycle数目等。
对于iOS平台来说,Unity内置的分析器可以得到整个场景花费的GPU时间。PowerVRAM的PVRUniSCo shader分析器也可以给出一个大致的性能评估。Xcode中的OpenGL ES Driver Instruments可以给出一些宏观上的性能信息,例如,设备利用率、渲染器利用率等。但相对于Android平台,对iOS的性能分析更加困难(工具较少)。而且PowerVR芯片采用了基于瓦片的延迟渲染器,因此,想要得到每个draw call花费的GPU时间是几乎不可能的。这时,一些宏观上的统计数据可能更有参考价值。
一些其他的性能分析工具可以在Unity的官方手册(http://docs.unity3d.com/Manual/MobileProfiling.html)中找到。当找到了性能瓶颈后,我们就可以针对这些方面进行特定的优化。
Asset Store中也有一些不错的性能分析工具(非渲染方面),如Build Report Tool、PoolManager、Cruncher等。