本文是Unity官方教程,性能优化系列的第四篇《Optimizing graphics rendering in Unity games》的翻译。
相关文章:
Unity性能优化(1)-官方教程The Profiler window翻译
Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译
Unity性能优化(3)-官方教程Optimizing garbage collection in Unity games翻译
Unity性能优化(4)-官方教程Optimizing graphics rendering in Unity games翻译
简介
本文我们将会学习在Unity渲染一帧的幕后会发生什么,渲染时会发生哪种类型的性能问题,以及怎样去解决和渲染相关的性能问题。
阅读本文之前,理解没有一刀切的方法去改进渲染性能问题是至关重要的。渲染性能被我们游戏中的很多因素影响并且高度依赖我们游戏运行的硬件和操作系统。最重要的是我们通过调查,实验,以及精确分析性能检测的结果来解决性能问题。
本文包括了一些最常见的渲染性能问题的信息,以及解决方法和一些扩展阅读资源的链接。很可能我们的游戏有一些问题本文没有覆盖。尽管如此,本文仍然会帮助我们理解我们的问题,给我们一些基础知识使得我们更有效的去查找解决方案。
渲染的简要介绍
开始之前,我们快速简要的看看Unity渲染一帧时发生了什么。理解事件流和事情发生的正确时期将会帮助我们理解,研究,并且努力解决我们的性能问题。
备注:在本文中,我们将使用“object”,指代我们游戏中被渲染的对象。任何带着渲染组件的GameObject都讲称作object。
基本上,渲染流程如下:
-CPU计算出什么东西需要被绘制,并且怎么被绘制。
-CPU给GPU发送指令。
-GPU根据CPU的指令进行绘制。
现在让我们进一步看看发生了什么。在本文的后面,我们将覆盖上面每个步骤的细节。但是现在,我们先熟悉一些将会使用的名词,以及理解在渲染中CPU和GPU所扮演的不同角色。
渲染的过程通常被称为渲染管线,这十分有助于记忆。高效的渲染在于保持信息流动。
在渲染每一帧的时候,CPU都会做如下工作:
-CPU检查屏幕上的每个object,决定他们是否应该被渲染。一个object只有满足了一定的条件才会被渲染。例如,它的一部分碰撞盒必须要在摄像机的视锥体内。不被渲染的object称作被剔除了。想了解更多关于视锥体和视锥体剔除的信息,请阅读this page。
-CPU收集将要被渲染的每个object的信息,并把这些数据分类为指令被称作绘制指令draw calls。一个draw call包含网格数据以及网格应该怎样被渲染。例如,应该使用哪个纹理。在一定的情况下,共享设置的一些objects可能会被合并为一个draw call。合并不同objects的数据到同一个draw call被称作batching。
-CPU给每个draw call创建一个数据包,称为batch。Batch有时会包含一些draw call以外的数据,但是这些情况通常不会对性能有什么影响,因此在本文中我们将不会考虑这些数据。
每一个batch必须包含一个draw call,CPU现在会做如下工作:
-CPU可能会发出指令,使得GPU改变一些渲染状态的变量。这个指令被称为SetPass call。SetPass通知GPU,使用哪些设置去渲染下一个网格。只有在渲染下一个网格时,渲染状态和渲染上一个网格时发生了改变的情况下,才会调用SetPass call。
-CPU把draw call发送给GPU。draw call指示GPU使用最近的SetPass call定义的设置去渲染指定的网格。
-一定的情况下,batch可能需要不止一个的pass。pass是一段shader代码,并且新的pass需要改变渲染状态。对于batch中的每个pass,CPU必须发送一个新的SetPass call然后必须要再次发送draw call。
同时,GPU做如下工作:
-GPU按照CPU发送的指令顺序处理这些指令。
-如果当前任务是SetPass call,那么GPU更新渲染状态。
-如果当前任务是draw call,那么GPU渲染网格。渲染网格发生在很多阶段,不同阶段的shader代码可以定义渲染。这部分的渲染过程十分复杂,我们不会详细讲解,但是理解下面的知识是很有用的:顶点着色器vertex shader告诉GPU怎么处理网格的顶点。片元着色器fragment shader告诉GPU怎么绘制单独的像素。
-这个过程会重复执行,知道所有CPU发送的任务都被GPU完成。
现在我们了解了Unity渲染帧时发生了什么,让我们考虑渲染时可能发生的一些问题。
渲染问题的类型
理解渲染最重要的是:为了渲染一帧,CPU和GPU必须都完成他们的任务。他们中的任何一个花费了太长的时间去完成任务,都会造成渲染延迟。渲染问题有两个基本的原因。第一类问题是由低效的渲染管线引起的。当渲染管线中一步或者多步花费了太长时间,打断了平滑的数据流时,渲染管线会很低效。渲染管线的低效被称为瓶颈。第二类问题是由于,渲染管线被塞入了太多的数据。即使是最高效的渲染管线,对于一帧中可以处理的数据量也是有限制的。
当我们的游戏花费太长的时间去渲染一帧,是由于CPU花费了过长的时间去完成渲染任务时,我们的游戏被称为CPU约束。当我们的游戏花费太长的时间去渲染一帧,是由于GPU花费了过长的时间去完成渲染任务时,我们的游戏被称为GPU约束。
理解渲染问题
在我们做任何修改前,先使用Profiler理解引起问题的原因是至关重要的。不同的问题需要不同的解决方案。测量我们做出的改变的效果也是很重要的。修复性能问题是一项平衡性的工作,改进一方面的性能问题,很可能会对其他方面有负面影响。
我们将使用两个工具来帮助我们理解和解决渲染性能问题。Profiler和frame debugger。他们都是Unity内置的工具。
Profiler
Profiler窗口允许我们实时的看到游戏的性能,我们可以看到游戏的很多方面的数据,包括内存使用,渲染管线,脚本性能等等。
如果你还不熟悉使用Profiler,请参看Unity用户手册this page of the Unity Manual,以及这篇教程Unity性能优化(1)-官方教程The Profiler window翻译 。
Frame Debugger
frame debugger允许我们一步一步的查看一帧是怎样被渲染的。我们可以使用它查看详细的渲染信息,如在每个draw call中都绘制了什么,每个draw call的shader属性以及发送到GPU的事件的顺序。这些信息帮助我们理解我们的游戏是怎样渲染的以及我们可以在哪里改进性能。
如果你还不熟悉frame debugger的使用,请参考this page of the Unity Manual以及这个视频教程this tutorial video。
找到性能问题的原因
在改进我们游戏的渲染性能前,我们必须先确认我们游戏运行慢是由渲染问题引起的。如果真正的问题是由于过于复杂的用户脚本引起的,那么我们去优化渲染性能是毫无意义的。如果你不太确认,性能问题是否有渲染引起的,请参考Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译 。
一旦我们确认是渲染问题,我们必须同时确认是CPU限制还是GPU限制。不同问题有不同的解决方案,所以在试图修复问题前,理解引起问题的原因是至关重要的。如果你不确定你的游戏是CPU限制还是GPU限制,请参考Unity性能优化(2)-官方教程Diagnosing performance problems using the Profiler window翻译 。
如果是CPU受限
基本上,在渲染一帧中,CPU的工作分为三类:
-决定什么必须被绘制
-为GPU准备好命令
-发送命令给GPU
这三类工作包含很多独立的任务,这些任务可能是通过多线程工作的。多线程允许不同的任务同时执行;当一个线程执行一个任务时,另一个线程可以同时执行完全不同的任务。这意味着工作能够更快的完成。当渲染任务被分发到不同的线程时,称为多线程渲染。
Unity渲染过程中和三类线程相关:主线程,渲染线程和工人线程(the main thread, the render thread and worker threads)。主线程用于我们游戏的主要CPU任务,包括一些渲染任务。渲染进程是专门用于发送命令给GPU的。每个工人线程执行一个单独的任务,例如剔除和网格蒙皮。哪些任务执行在哪个线程,取决于我们游戏运行的硬件和游戏的设置。例如,CPU的核心数量越多,就会生成越多的工人线程数。由于这个原因,在我们的目标硬件上进行性能分析是十分重要的。在不同的设备上,我们游戏的表现可能相差很多。
由于多线程渲染非常复杂并且依赖硬件,在我们尝试改进性能时,必须首先理解是哪些任务导致了CPU限制。如果我们的游戏运行缓慢是因为在一个线程上剔除操作花费了太长的时间,那么我们在另一个线程上降低发送给GPU命令的时间是不会有什么帮助的。
注意:不是所有的平台都支持多线程渲染,在此时,WebGL不支持这个功能。在不支持多线程渲染的平台上,所有的任务都在相同的线程中执行。如果在这样的平台上,CPU限制时,任何优化CPU的工作都会改进CPU性能。如果我们的游戏是这种情况,我们应该阅读下面的全部章节,并且考虑哪些优化最适合我们的游戏。
图形工作Graphics jobs
在Player settings中的Graphics jobs选项决定了Unity是否使用工人线程去执行一些原本需要在主线程或者渲染线程中执行的任务,在支持这个功能的平台上,它能够提供可观的性能提升。如果我们希望使用这个功能,我们应该分别对开启或者关闭此功能进行性能分析,以观察这个功能对性能的提升效果。
找出哪些任务对性能问题有影响
我们可以使用Profiler来检查哪些任务引起了CPU限制。请参考。。。
现在我们理解了哪些任务引起我们游戏CPU限制,让我们看看常见的问题和解决方案。
发送命令到GPU
发送命令到GPU花费时间过长是引起CPU限制的最常见的原因。在大多数平台上,这个任务是由渲染线程执行的,个别平台在工人线程上执行(如ps4)。
发送命令到GPU中,其中最耗时的操作是SetPass call。如果CPU限制是由发送命令到GPU引起的,那么降低SetPass的数量通常是最好的改善性能的办法。
在Unity的渲染Profiler中,我们可以看到有多少SetPass call和batches被发送。有多少SetPass call被发送会造成性能问题,和游戏的目标硬件关系很大。在高端pc上可以发送的SetPass call数量远远大于移动平台。
SetPass call以及相关的batches数量取决于几个因素。本文稍后会详细阐述。简单来说,通常如下:
-降低batches数量或者使更多的对象共享相同的渲染状态,通常会减低SetPass call数量。
-降低SetPass call数量,通常会改进CPU性能。
如果降低batches数量没有降低SetPass call数量,他本身也会导致性能改进。这是因为CPU能够更有效率的处理单个batch,即使它和几个batches包含了数量相同的网格数据。
基本上,有三种方法降低SetPass call和batches的数量,我们将仔细论述下面的每一种方法:
-降低要渲染的对象数量,通常可以同时降低SetPass call和batches的数量。
-降低每个要渲染的对象的渲染次数,通常可以降低SetPass call
-合并要渲染的对象的数据到更少的batches,可以降低batches数量
不同的技术适用于不同的游戏,我们将考虑以上所有的选择,决定哪些适合我们的游戏并且做出实验。
降低要渲染的对象数量
降低要渲染的对象数量是最简单的降低batches 和 SetPass calls的方法。有以下几个技术,可以降低要渲染的对象数量:
-简单的降低我们场景的可见对象数量,是一个有效的解决方案。例如,我们要渲染有很多人物的人群,我们可以尝试减少人物的数量,如果看起来人群的效果仍然不错,那么这就是一个比其他复杂方法快捷的多的优化方法。
-我们可以通过设置摄像机的剪裁平面的远端来降低摄像机的绘制范围。这个属性表示距离摄像机多远的物体将不再被渲染。如果我们想隐藏远处的物体不被渲染的事实,我们可以尝试使用雾来掩盖远处。
-如果需要基于距离的更细粒度的隐藏物体,我们可以使用摄像机的Layer Cull Distances属性,它可以给不同的Layer设置单独的剪裁距离。如果我们有很多前景装饰细节时,这个方法很有用。我们可以使用很小的距离隐藏细节。
-我们可以使用遮挡剔除功能去关闭被其他物体遮挡的物体的渲染。例如,我们场景中有一个很大的建筑,我们可以使用遮挡剔除功能,关闭它后面的物体的渲染。Unity的遮挡剔除功能不适用于所有的场景,它会导致额外的cpu消耗,并且相关设置很复杂,但是在一些场景中,它可能会极大的改善性能。使用遮挡剔除的最佳实践,This Unity blog post on occlusion culling best practices。
另外,我们可以通过手工的关闭物体渲染来实现我们自己的遮挡剔除,我们可以手工的关闭我们知道玩家无法看到的物体的渲染。例如,如果我们的场景包含一些过场的物体,那么在他们出现之前或者移出以后,我们应该手工的关闭他们的渲染。对于我们游戏来说,使用我们的知识,手工的剔除,往往比Unity动态的遮挡剔除有效的多。
降低每个要渲染的对象的渲染次数
实时的光照,阴影和反射可以极大的提高我们游戏的真实感,但是这些操作可能非常昂贵。使用这些功能可能导致物体被渲染多次,这对性能有很大的影响。
这些功能的精确的影响依赖于我们游戏选择的渲染路径。渲染路径,也就是表明当我们绘制场景的时候,渲染计算的执行顺序。不同渲染路径最主要的不同,是它们怎么处理实时光照,阴影和反射。通常来说,如果我们游戏运行在比较高端的设备上,并且应用了很多实时光照,阴影和反射时,延迟渲染是比较好的选择。前向渲染适用于低端设备,并且不使用以上功能。尽管如此,如果我们需要更好的使用实时光照,阴影和反射等功能,情况是十分复杂的,最好研究相关主题以及实验。请参考This page of the Unity Manua这是一个十分有用的起点。 请参考Thistutorial这里包含了Unity中光照的相关主题内容。
不论选择哪种渲染路径,使用实时光照,阴影和反射都会影响我们游戏的性能,所以,理解怎样去优化他们是十分重要的。
-Unity中动态光照是很复杂的主题,讨论他超过了本文范围,请参考this tutorial和 this page of the Unity Manual。
-动态光照很昂贵。当我们的场景包含静态物体时,例如风景,我们可以使用烘焙技术去预计算场景的光照,这样就不需要在运行时计算光照了。具体请参考This tutorial和 this section of the Unity Manual。
-如果我们希望在游戏中使用实时阴影,我们可能可以改进这方面的性能。This page of the Unity Manual 这篇文章介绍的阴影设置,以及这些设置怎么影响性能的。例如,我们可以设置阴影距离,确保只有近处的物体投射阴影。
-反射探头创建真实感的反射,但是会很大的影响batches数量。最好我们在性能敏感的场合最小化的使用它,并且无论在哪使用了他们都要尽可能的优化。反射探头的优化请参考This page of the Unity Manual。
合并物体为更少的batches
在一定情况下,一个batch可以包含多个物体的数据。为了可以适合合并,物体必须满足以下条件:
-共享相同材质的相同实例
-有一样的设置(例如,纹理,shader,shader参数等)
合并合适的物体可以改进性能,尽管如此,所有的优化技术我们都必须小心的分析,合并的消耗没有超过获得的性能改进。
合并合适的物体有几种不同的技术:
-静态batching技术,允许Unity合并相邻的不移动的合适的物体。一个好的例子是,一堆相似的物体,例如巨石,可以从静态batch中受益良多。在游戏中设置静态batch,请参考This page of the Unity Manual。
静态batch会导致更高的内存占用,所以我们在优化时要衡量这个代价。
-动态batching技术,是另外一种Unity合并合适的物体的技术,不论它是运动还是静止。对能够使用这种技术合并的物体,有一些限制。这些限制请参考this page of the Unity Manual 动态batching会影响CPU使用,可能会引起CPU消耗的时间大于节省的时间。我们应该通过实践衡量它的代价,并且在使用的时候注意这些。
-合并Unity的UI元素要更复杂一些,因为他会受我们界面布局的影响。具体请参考This video from Unite Bangkok 2015和this guide to optimizing Unity UI。
-GPU instancing技术允许大量一样的物体十分高效的合并处理。它的使用有一定限制,并且不是所有硬件都支持,但是如果我们的游戏在场景中同时存在大量相同的物体,我们可以在这个技术下收益。请参考This page of the Unity Manual 这里介绍了Unity中GPU instancing的技术细节以及怎样使用它,支持哪些平台,以及在哪种环境下,我们的游戏会受益于此。
-纹理图集,是把大量的小纹理合并为一张大的纹理图的技术。它通常在2d游戏和UI系统中使用,但是也可以在3d游戏中使用。当我们使用这个技术为游戏创建美术资源时,我们可以确保物体共享同一图集,因此适合合并。Unity内置了图集工具Sprite Packer。
-我们可以手工的合并共享相同材质和纹理的网格,不论是在Unity编辑器中还是在运行时使用代码。当我们手工合并网格时,我们必须意识到,阴影,光照和剔除仍然在每个单独的物体层级上操作。这意味着,合并网格所产生的性能提升,很可能被本来可以被剔除的物体,不再被剔除了,带来的影响抵消。如果我们想深入研究这项技术,我们应该查看Mesh.CombineMeshes函数 ,Unity’s Standard Assets package中的CombineChildren脚本是一个例子。
-在脚本中,我们必须小心使用Renderer.material,这会复制材质,并且返回一个新副本的引用。这样做会破坏batching,如果这是合并中的一部分。因为renderer不再持有相同的材质引用了。如果我们需要访问一个在合并中的物体的材质,我们应该使用Renderer.sharedMaterial。
剔除,排序以及合并
剔除,收集将要被绘制的物体的数据,排序这些数据,生成GPU命令,这些都对CPU限制有贡献。这些任务会在主线程或者独立的工人线程中运行,取决于我们游戏的设置以及目标硬件。
-剔除本身消耗并不大,但是减少不必要的剔除操作可能会对性能有帮助。剔除是对场景中每个激活的物体,每个摄像机每个物体的计算,甚至是哪些不被渲染的层级的物体。为了降低这些,我们应该关闭摄像机,并且对于当前不使用的物体反激活或者禁用renderer。
-Batching可以极大的增加向GPU发送命令的速度,但是他有时可能会在其他地方带来消耗。如果batching操作造成了CPU限制,我们可以会要限制手工或者自动batching操作的数量。
蒙皮
SkinnedMeshRenderers在当我们使用一个网格动画变形时(技术上称为骨骼动画)使用。它最多的用在动画人物身上。渲染蒙皮的任务通常在主线程或者单独的工人线程,依赖于游戏的设置以及目标硬件。
渲染蒙皮可能会是昂贵的操作。如果我们在渲染Profiler中,看到渲染蒙皮对CPU限制影响很大,这里有几个方法我们可以尝试去改进它的性能。
-我们应该考虑当前正在使用SkinnedMeshRenderers组件的物体是否有必要使用。可能是这种情况,我们导入的模型包含了SkinnedMeshRenderers组件,但是我们其实并不需要它运动。这种情况下,我们使用MeshRenderer组件替换它,会有助于性能提升。当我们在Unity中导入模型时,如果我们在模型的导入设置中选择不导出动画,这个模型就会包含一个MeshRenderer组件替换SkinnedMeshRenderers组件。
-如果我们只在一些时刻运动物体(例如,只在游戏开始时,或者只有距离摄像机一定距离内时),我们应该交换为一个细节较少的网格,或者使用MeshRenderer替换SkinnedMeshRenderers。SkinnedMeshRenderers组件有一个函数BakeMesh,可以用匹配的动作创建一个网格,这个十分有用,在不同的网格或渲染器中切换时物体不会有可见的变化。
-这些文章有关于使用蒙皮的优化建议,请参考This page of the Unity Manual 和the Unity Manual page on the SkinnedMeshRenderer component,还应该记住,蒙皮的消耗是在每个顶点上,因此,使用顶点较少的模型可以有效降低工作量。
-在一些平台上,和CPU相比,蒙皮可以被GPU更高效的处理。如果我们的GPU比较强,这个值得尝试。我们可以为当前平台开启GPU蒙皮,在Player Settings中。
主线程操作和渲染无关
理解许多主线程的任务是和渲染无关的很重要。这意味着,如果是CPU限制发生在主线程,我们应该把优化CPU时间改进性能的努力放在和渲染无关的任务上。
例如,在某个时间点上,我们的游戏需要做十分昂贵的渲染操作并且在主线程上我们的脚本操作也非常昂贵,使得CPU限制。如果我们已经在不损失视觉真实度的前提下尽可能的优化渲染了,那么我们减少用户脚本的CPU消耗可能会改进性能。
如果游戏是GPU限制
如果游戏是GPU限制,那么第一件事就是找到GPU瓶颈的原因。GPU性能最常被填充率限制,尤其在移动平台,但是显存带宽和顶点处理也可能影响。让我们检查这些问题,并且学习引起问题的原因,怎么诊断和怎么修复问题。
填充率
填充率是指GPU在屏幕上每秒可以渲染的像素数。如果我们的游戏收到填充率的限制,意味着我们的游戏每帧尝试绘制的像素数量超过了GPU的处理能力。
检查是否填充率引起了游戏GPU限制很简单:
-使用Profiler分析,注意GPU时间
-在Player settings中降低显示分辨率
-再次分析游戏,如果性能改善了,很可能是填充率的问题
如果确认了填充率引起了问题,有几个方法可以解决问题:
-片元shader是告诉GPU怎么样去绘制一个像素的一段shader代码。这段代码GPU需要为每一个需要绘制的像素执行,所以如果这段代码效率低,那么很容易发生性能问题。复杂的片元shader是很常见的引起填充率问题的原因。
-如果我们的游戏使用Unity内置shader,我们应该使用最简单和最优化的shader,为了达到我们想要的视觉效率。例如,the mobile shaders是Unity针对移动平台高度优化的shader,我们应该实验使用它们是否可以在不影响视觉效果的前提下改善性能。这些shader是为了移动平台设计的,但是它们也适用于任何项目。如果使用它们可以达到项目视觉效果的要求,那么在非移动平台上使用它们也是能够很好的改善性能的。
-如果游戏中的物体使用的是Unity的Standard Shader,那么理解Unity编译这些shader是基于当前的材质设置的是很重要的。只有那些当前使用的功能会被编译。这意味着,移除例如detail maps可以减少片元shader的复杂度,这对性能提升有很大益处。如果我们游戏中是这种情况,我们应该实践,是否能够在不影响视觉质量的前提是提升性能。
-如果我们游戏使用的是定制的shader,我们应该尽可能的优化它。优化shader是一个很复杂的主题,请参考this page of the Unity Manual和 this page of the Unity Manual。
-Overdraw是指相同的像素绘制了多次。这是在物体绘制在其他物体之上的时候发生的,也在很大程度上引起了填充率问题。为了理解Overdraw,我们必须先理解Unity在场景中绘制物体的顺序。物体的shader决定了物体的绘制顺序,通常由render queue属性决定。Unity使用这些信息按照严格的顺序绘制物体,具体细节请参考page of the Unity Manual 另外在不同render queue的物体在被绘制之前会按不同的顺序排序。例如,Unity在Geometry queue中为最小化Overdraw会从前到后排序物体,但是在Transparent queue中,为了达到视觉效果的要求,则是从后到前排序物体。在Transparent queue中,从后向前排序物体其实最大化了Overdraw。Overdraw是一个很复杂的主题,并且没有一刀切的解决方案,但是降低重叠物体的数量使得Unity不能自动排序是关键。调查Overdraw问题最好的起点是Unity的场景视图中,DrawMode允许我们看到场景中的Overdraw,我们可以从这开始降低Overdraw的工作。最常见的引起Overdraw的因素是透明材质,未优化的粒子,和重叠的UI元素。所以我们应该尝试优化这些。请参考This article on the Unity Learn site 这篇文章聚焦于UI,但是也包含了Overdraw的很好的指导。
-使用屏幕后处理技术也会极大的影响填充率,尤其是我们使用了不止一种的屏幕后处理的时候。如果我们在使用屏幕后处理是遇到了填充率问题,我们应该尝试不同的设置或者使用更加优化的屏幕后处理版本。例如使用Bloom (Optimized)替换Bloom。如果我们在同一个摄像机下使用了多个屏幕后处理,这将造成成倍的shader pass。这种情况下,我们应该合并shader到一个单独的pass,例如Unity’s PostProcessing Stack。如果我们优化屏幕后处理效果后,仍然有填充率问题,那么我们也许要考虑关闭屏幕后处理,尤其是在低端的设备上。
显存带宽
显存带宽是指GPU读写它的专用内存的速度。如果我们的游戏受限于显存带宽,通常意味着我们使用的纹理太大了,以至于GPU无法快速处理。
我们可以按如下方法检查是否显存带宽的问题:
-用Profiler分析游戏,并关注GPU时间
-在质量设置中降低当前平台的纹理质量
-继续分析游戏,如果性能改善了,那么通常是显存带宽的问题。
如果是显存带宽的问题,我们需要降低纹理的内存占用。针对不同的游戏通常有不同的最佳解决方案,这里我们提供几个优化纹理的方法:
-纹理压缩技术可以同时极大的降低纹理在磁盘和内存中的大小。如果是显存带宽的问题,那么使用纹理压缩减小纹理在内存的大小可以帮助改善性能。Unity中有很多可用的纹理压缩的格式和设置。通常来说,一些纹理压缩格式只要可用就应该尽可能的使用,尽管如此,通过实践找到针对每个纹理最合适的设置是最好的。请参考This page in the Unity Manual 讲述了纹理压缩的格式和各种设置的详细信息。
-多级渐远纹理,是Unity对远处的物体使用的低分辨率版本的纹理。如果我们的场景包含距离摄像机很远的物体,我们可以通过使用多级渐远纹理来缓解显存带宽的问题。Unity场景视图中的The Mipmaps Draw Mode允许我们查看哪些物体受益于多级渐远纹理,请参考this page of the Unity Manual 包含了使用多级渐远纹理的详细信息。
顶点处理
顶点处理是指GPU必须渲染网格中的每一个顶点的工作。顶点处理的消耗受两件事情影响:必须渲染的顶点数量,以及在每个顶点上要进行的操作数量。
如果我们的游戏是GPU限制,并且已经确认了不是填充率和显存带宽引起的问题,那么就很可能是顶点处理引起的。如果是这种情况,那么尝试减少GPU顶点处理的数量很可能会获得性能提升。
有一些方法可以减少顶点数量或者在每个顶点上执行的操作数量:
-首先,我们应该降低不必要的网格复杂的。如果我们使用的网格包含在游戏中无法被看见的LOD,或者低效的网格在错误的创建时包含了太多的顶点,这些都会浪费GPU的工作量。最直接的降低顶点处理的消耗的方法,就是在3d建模软件中创建模型时使用更少数量的顶点。
-我们可以尝试使用法线贴图技术,我们使用它来模拟更高几何复杂度的网格。尽管使用这种技术有一些GPU负载,但是在多数情况下,它会获得性能提升。请参考This page of the Unity Manual 介绍了使用法线贴图技术去模拟更复杂的网格。
-如果我们的游戏没有使用法线贴图技术,在网格的导入设置中,我们可以关闭顶点的切线。这会降低GPU处理顶点的数据量。
-LOD(Level of detail),这是当物体远离摄像机时,降低物体网格的复杂度的技术。这可以有效的降低GPU需要渲染的顶点数量,并且不影响视觉表现。具体使用细节请参考. The LOD Group page of the Unity Manual。
-顶点shader,是一段shader代码,告诉GPU怎么绘制每个顶点。如果我们的游戏受限制于顶点处理的影响,那么降低顶点shader的复杂度可能会有助于性能提升。
-如果我们的游戏使用Unity内置shader,我们应该使用最简单和最优化的shader,为了达到我们想要的视觉效率。。例如,the mobile shaders是Unity针对移动平台高度优化的shader,我们应该实验使用它们是否可以在不影响视觉效果的前提下改善性能。
--如果我们游戏使用的是定制的shader,我们应该尽可能的优化它。优化shader是一个很复杂的主题,请参考this page of the Unity Manual 和 this page of the Unity Manual。
结论
我们已经学习了Unity中,是怎样渲染的,当渲染时可能会发生什么问题,以及怎样在我们的游戏中提高渲染的性能。使用这些知识以及性能分析工具Profiler,我们可以修复渲染相关的性能问题,并且构建我们的游戏使他们具有流畅有效率的渲染流水线。
下面列出本文主题的相关资源
扩展阅读
Unity Learn: A guide to optimizing Unity UI
Unity Knowledge Base: Why is my static batching breaking or otherwise not working as expected?
Fabian Giesen: A trip through the graphics pipeline
Gamasutra: How to choose between Forward or Deferred rendering paths in Unity
Gamasutra: Batching independently moving GameObjects into a single mesh to reduce draw calls
FlameBait Games: Optimizing SkinnedMeshRenderers for Unity 5
Pencil Square Games: Reducing draw calls (also named SetPass calls) in Unity 5