赞
踩
本文翻自Unity官网:https://learn.unity.com/tutorial/fixing-performance-problems-2019-3-1#5e85ad9dedbc2a0021cb81aa
在本文中,我们将学习Unity在渲染一帧时的背后运作,以及渲染过程中可能出现的性能问题以及如何解决与渲染相关的性能问题。
在阅读本文之前,**理解一个事实非常重要,即没有一种适用于所有情况的方法来提高渲染性能。**渲染性能受到游戏内许多因素的影响,也高度依赖于游戏运行的硬件和操作系统。最重要的是,我们通过调查、实验和严格分析实验结果来解决性能问题。
本文包含了关于最常见的渲染性能问题的信息,并提供了如何解决这些问题的建议以及进一步阅读的链接。可能我们的游戏存在未涵盖的问题或问题组合。然而,本文仍然可以帮助我们理解问题,并为我们提供了有效搜索解决方案所需的知识和词汇。
开始之前,简要了解一下Unity在渲染一帧时会发生什么。了解事件的流程和正确的术语将有助于我们理解、研究并致力于解决我们的性能问题。
现在让我们更详细地了解发生的过程。后文将更详细地介绍每个步骤,但现在让我们熟悉使用的术语,并理解CPU和GPU在渲染中的不同角色。
用来描述渲染的常用短语是渲染管线(rendering pipeline),这是一个有用的概念;高效的渲染关键在于保持信息的流动。
CPU检查场景中的每个对象,以确定是否应该进行渲染。只有符合一定条件的对象才会被渲染;例如,其边界框的某一部分必须位于摄像机的视锥体内。将不会被渲染的对象称为剔除。
CPU收集将被渲染的每个对象的信息,并将这些数据分类为绘制调用(draw call)。绘制调用包含有关单个网格及其如何渲染的数据;例如,应使用哪些纹理。在某些情况下,共享设置的对象可以合并为同一个绘制调用。将不同对象的数据合并到同一个绘制调用中称为批处理(batching)。
CPU为每个绘制调用创建一个数据包,称为批次(batch)。批次有时可能包含除绘制调用以外的其他数据,但这些情况不太可能导致常见的性能问题,因此本文不考虑这些情况。
CPU可能会向GPU发送一个命令来改变一组被称为渲染状态的变量。这个命令称为SetPass调用。SetPass调用告诉GPU在下一个网格渲染时要使用哪些设置。只有在下一个要渲染的网格需要从上一个网格的渲染状态中进行更改时,才会发送SetPass调用。
CPU将绘制调用发送给GPU。绘制调用指示GPU使用最近的SetPass调用中定义的设置来渲染指定的网格。
在某些情况下,批次可能需要多个通道。通道是一段着色器代码,而新的通道需要对渲染状态进行更改。对于批次中的每个通道,CPU必须发送一个新的SetPass调用,然后再次发送绘制调用。
现在我们了解了Unity渲染帧时发生的情况,让我们考虑一下在渲染过程中可能出现的问题。
渲染最重要的一点是:为了完成渲染帧,CPU和GPU必须完成它们的所有任务。如果其中任何一个任务花费的时间太长,将导致渲染帧的延迟。
渲染问题有两个基本原因。第一种问题是由于管线效率低下引起的。管线效率低下意味着渲染管线中的一个或多个步骤花费的时间过长,中断了数据的平滑流动。管线内的低效率被称为瓶颈。第二种问题是由于试图将过多数据推送到管线中造成的。即使是最高效的管线,在单个帧中处理的数据量也有限。
当游戏因为CPU在执行渲染任务时花费的时间过长而导致渲染帧时间过长时,我们称之为CPU受限。当游戏因为GPU在执行渲染任务时花费的时间过长而导致渲染帧时间过长时,我们称之为GPU受限。
在进行任何更改之前,使用性能分析工具来了解性能问题的原因非常重要。不同的问题需要不同的解决方案。同样重要的是,我们要测量每个更改的效果;解决性能问题是一个权衡的过程,改善某个方面的性能可能会对其他方面产生负面影响。
我们将使用两个工具来帮助我们了解和解决渲染性能问题:Profiler窗口和Frame Debugger。这两个工具都内置在Unity中。
Profiler窗口允许我们实时查看游戏的性能数据。我们可以使用Profiler窗口查看游戏的许多方面的数据,包括内存使用情况、渲染流程和用户脚本的性能。
帧调试器(Frame Debugger)允许我们逐步查看帧的渲染过程。使用帧调试器,我们可以查看详细信息,例如每个绘制调用期间绘制了什么、每个绘制调用的着色器属性以及发送到GPU的事件顺序。这些信息有助于我们了解游戏的渲染方式并找到可以改善性能的地方。
如果您还不熟悉使用帧调试器,Unity手册的这个页面是对其功能的很好介绍,而这个教程视频展示了它的使用方式。
在尝试改善游戏的渲染性能之前,我们必须 确保游戏运行缓慢是由于渲染问题引起的。如果真正的问题是过于复杂的用户脚本,那么尝试优化渲染性能就没有意义!
一旦我们确定问题与渲染有关,我们还必须了解游戏是CPU限制还是GPU限制。不同的问题需要不同的解决方案,因此在尝试修复问题之前,了解问题的原因至关重要。如果您还不确定游戏是CPU限制还是GPU限制,可以参考这个教程进行判断。
如果我们确定问题与渲染有关,并且知道游戏是CPU限制还是GPU限制,那么我们就可以继续阅读。
大体上来说,为了渲染一帧画面,CPU必须执行的工作可以分为三个类别:
这些广泛的类别包含许多个别任务,这些任务可以在多个线程上执行。线程使得不同的任务可以同时进行;当一个线程执行一个任务时,另一个线程可以执行完全不同的任务。这意味着工作可以更快地完成。当渲染任务在不同的线程上分开执行时,称为多线程渲染。
Unity的渲染过程涉及三种类型的线程:主线程、渲染线程和工作线程。主线程是我们的游戏中大部分CPU任务发生的地方,包括一些渲染任务。渲染线程是一个专门发送命令给GPU的线程。工作线程每个执行一个单一任务,例如剔除或网格蒙皮。哪些任务由哪个线程执行取决于我们游戏的设置和游戏运行的硬件。例如,目标硬件拥有的CPU核心越多,可以生成的工作线程就越多。因此,在目标硬件上进行我们的游戏的性能分析非常重要;我们的游戏在不同设备上可能表现出完全不同的性能。
在我们尝试提高性能之前,必须了解是哪些任务导致我们的游戏受限于CPU。如果我们的游戏运行缓慢是因为剔除操作在一个线程上花费的时间太长,那么在另一个线程上减少发送命令给GPU所需的时间是没有帮助的。
注意:并非所有平台都支持多线程渲染;在撰写本文时,WebGL不支持此功能。在不支持多线程渲染的平台上,所有的CPU任务都在同一个线程上执行。如果我们在这样的平台上受限于CPU,优化任何CPU工作将改善CPU性能。如果我们的游戏是这种情况,我们应该阅读以下所有部分,并考虑哪些优化方法最适合我们的游戏。
Player Settings中的图形任务选项确定Unity是否使用工作线程执行本来应该在主线程和某些情况下在渲染线程上完成的渲染任务。在支持此功能的平台上,它可以显著提升性能。如果我们希望使用此功能,应该在启用和禁用图形任务的情况下对我们的游戏进行性能分析,观察它对性能的影响。
我们可以通过使用Profiler窗口来确定哪些任务导致我们的游戏受限于CPU。本教程将展示如何确定问题所在。
现在我们知道哪些任务导致我们的游戏受限于CPU,让我们来看一下一些常见问题及其解决方案。
发送命令给GPU是导致游戏受限于CPU的最常见原因。在大多数平台上,这个任务是在渲染线程上执行的,尽管在某些平台上(例如PlayStation 4),这可能由工作线程执行。
在发送命令给GPU时,最耗时的操作是SetPass调用。如果我们的游戏因为发送命令给GPU而受限于CPU,减少SetPass调用的次数可能是提高性能的最佳方法。
我们可以在Unity Profiler窗口的Rendering profiler中查看发送的SetPass调用和批次的数量。在性能受影响之前可以发送的SetPass调用数量在很大程度上取决于目标硬件;高端PC可以发送更多的SetPass调用而不会影响性能,而移动设备则较少。
SetPass调用的数量及其与批次数量的关系取决于几个因素,我们将在本文后面更详细地讨论这些主题。然而,通常情况下:
如果减少批次数量不能减少SetPass调用的次数,它仍然可能导致性能的改善。这是因为相比于包含相同数量网格数据的多个批次,CPU可以更有效地处理单个批次。
总的来说,有三种减少批次和SetPass调用次数的方法。我们将更深入地研究每一种方法:
减少需要渲染的对象数量通常会减少批次和SetPass调用次数。
减少需要渲染的对象数量是减少批次和SetPass调用次数的最简单方法。我们可以使用多种技术来减少需要渲染的对象数量。
实时照明、阴影和反射为游戏增加了很多逼真感,但代价也很高。使用这些功能可能会导致多次渲染对象,这可能会极大地影响性能。
这些功能的确切影响取决于我们为游戏选择的渲染路径。渲染路径是绘制场景时进行计算的顺序的术语,渲染路径的主要区别在于它们如何处理实时光线、阴影和反射。一般而言,如果我们的游戏在高端硬件上运行并使用大量实时光线、阴影和反射,则延迟渲染(Deferred Rendering)可能是更好的选择。如果我们的游戏在低端硬件上运行并且不使用这些功能,则前向渲染( Forward Rendering)可能更合适。然而,这是一个非常复杂的问题,如果我们希望利用实时光线、阴影和反射,最好是研究这个主题并进行实验。Unity手册的这一页提供了有关Unity中可用的不同渲染路径的更多信息,是一个有用的起点。这个教程包含有关Unity中灯光的有用信息。
无论选择哪种渲染路径,使用实时光线、阴影和反射都会影响我们游戏的性能,因此了解如何优化它们非常重要。
当满足一定条件时,一个批次可以包含多个对象的数据。为符合批处理的条件,对象必须:
对符合批处理条件的对象进行批处理可以提高性能,尽管像所有优化技术一样,我们必须仔细地进行分析,以确保批处理的成本不超过性能的收益。
有几种不同的技术可以用于对符合批处理条件的对象进行批处理:
剔除、收集将要绘制的对象的数据、将这些数据排序成批次并生成GPU命令,所有这些都可能导致CPU瓶颈。这些任务将在主线程或单独的工作线程上执行,具体取决于我们游戏的设置和目标硬件。
当我们使用一种称为骨骼动画的技术通过变形来动画网格时,会使用SkinnedMeshRenderer。它最常用于动画角色。与渲染蒙皮网格相关的任务通常在主线程或单独的工作线程上执行,具体取决于我们游戏的设置和目标硬件。
渲染蒙皮网格可能是一项昂贵的操作。如果我们在Profiler窗口中看到渲染蒙皮网格导致游戏变慢,有几个方法可以尝试提高性能:
重要的是要理解,许多与渲染无关的CPU任务会在主线程上执行。这意味着如果我们在主线程上受到CPU限制,我们可能可以通过减少与渲染无关的CPU任务所花费的时间来改善性能。
例如,在我们的游戏中的某个点,我们的游戏可能在主线程上执行昂贵的渲染操作和昂贵的用户脚本操作,使我们受到CPU限制。如果我们已经尽可能地优化了渲染操作而不失去视觉保真度,那么我们有可能通过减少我们自己脚本的CPU成本来提高性能。
如果我们的游戏受到GPU限制,首先要做的是找出是什么导致了GPU瓶颈。GPU性能最常受到填充率的限制,特别是在移动设备上,但内存带宽和顶点处理也可能是问题。让我们分别检查这些问题,学习其原因、如何诊断以及如何修复。
填充率是指GPU每秒可以渲染到屏幕的像素数量。如果我们的游戏受到填充率的限制,这意味着我们的游戏正在尝试每帧绘制比GPU处理能力更多的像素。
简单来说,我们可以检查填充率是否导致了游戏受到GPU限制:
如果填充率是问题的原因,有几种方法可以帮助我们解决问题。
内存带宽是指GPU从其专用内存中读取和写入数据的速率。如果我们的游戏受限于内存带宽,通常意味着我们使用的纹理对于GPU来说太大了,无法快速处理。
为了检查内存带宽是否是一个问题,我们可以执行以下操作:
如果内存带宽是我们的问题,我们需要减少游戏中的纹理内存使用。同样,适用于每个游戏的最佳技术可能各不相同,但我们可以通过一些方法优化纹理。
顶点处理是指GPU在渲染网格中的每个顶点时所需执行的工作。顶点处理的成本受两个因素影响:必须渲染的顶点数量和必须对每个顶点执行的操作数量。
如果我们的游戏受限于GPU,并且已经确定不受填充率或内存带宽的限制,那么很可能顶点处理是问题的原因。如果是这种情况,尝试减少GPU必须执行的顶点处理量可能会带来性能提升。
我们可以考虑几种方法来帮助我们减少顶点数量或每个顶点上执行的操作数量。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。