当前位置:   article > 正文

unity官方文档翻译 + 笔记 |基于2019.3版本解决性能问题(Part 3 优化图形渲染)

unity官方文档

本文翻自Unity官网:https://learn.unity.com/tutorial/fixing-performance-problems-2019-3-1#5e85ad9dedbc2a0021cb81aa

简介

在本文中,我们将学习Unity在渲染一帧时的背后运作,以及渲染过程中可能出现的性能问题以及如何解决与渲染相关的性能问题。
在阅读本文之前,**理解一个事实非常重要,即没有一种适用于所有情况的方法来提高渲染性能。**渲染性能受到游戏内许多因素的影响,也高度依赖于游戏运行的硬件和操作系统。最重要的是,我们通过调查、实验和严格分析实验结果来解决性能问题。
本文包含了关于最常见的渲染性能问题的信息,并提供了如何解决这些问题的建议以及进一步阅读的链接。可能我们的游戏存在未涵盖的问题或问题组合。然而,本文仍然可以帮助我们理解问题,并为我们提供了有效搜索解决方案所需的知识和词汇。

渲染简介

开始之前,简要了解一下Unity在渲染一帧时会发生什么。了解事件的流程和正确的术语将有助于我们理解、研究并致力于解决我们的性能问题。

最基本的层面上,渲染可以被描述如下:
  1. 中央处理器(CPU)确定需要绘制的内容以及如何绘制。
  2. CPU向图形处理器(GPU)发送指令。
  3. GPU根据CPU的指令进行绘制。

现在让我们更详细地了解发生的过程。后文将更详细地介绍每个步骤,但现在让我们熟悉使用的术语,并理解CPU和GPU在渲染中的不同角色。
用来描述渲染的常用短语是渲染管线(rendering pipeline),这是一个有用的概念;高效的渲染关键在于保持信息的流动。

每帧渲染时,CPU执行以下工作:
  1. CPU检查场景中的每个对象,以确定是否应该进行渲染。只有符合一定条件的对象才会被渲染;例如,其边界框的某一部分必须位于摄像机的视锥体内。将不会被渲染的对象称为剔除。

  2. CPU收集将被渲染的每个对象的信息,并将这些数据分类为绘制调用(draw call)。绘制调用包含有关单个网格及其如何渲染的数据;例如,应使用哪些纹理。在某些情况下,共享设置的对象可以合并为同一个绘制调用。将不同对象的数据合并到同一个绘制调用中称为批处理(batching)。

  3. CPU为每个绘制调用创建一个数据包,称为批次(batch)。批次有时可能包含除绘制调用以外的其他数据,但这些情况不太可能导致常见的性能问题,因此本文不考虑这些情况。

对于每个包含绘制调用的批次,CPU现在必须执行以下操作:
  1. CPU可能会向GPU发送一个命令来改变一组被称为渲染状态的变量。这个命令称为SetPass调用。SetPass调用告诉GPU在下一个网格渲染时要使用哪些设置。只有在下一个要渲染的网格需要从上一个网格的渲染状态中进行更改时,才会发送SetPass调用。

  2. CPU将绘制调用发送给GPU。绘制调用指示GPU使用最近的SetPass调用中定义的设置来渲染指定的网格。

  3. 在某些情况下,批次可能需要多个通道。通道是一段着色器代码,而新的通道需要对渲染状态进行更改。对于批次中的每个通道,CPU必须发送一个新的SetPass调用,然后再次发送绘制调用。

GPU执行的工作如下:
  1. GPU按照它们被发送的顺序处理来自CPU的任务。
  2. 如果当前任务是SetPass调用,GPU会更新渲染状态。
  3. 如果当前任务是draw call,GPU会渲染网格。这个过程分为多个阶段,由不同的着色器代码部分定义。我们不会详细介绍这个部分的渲染过程,但了解到有一个称为顶点着色器的代码部分告诉GPU如何处理网格的顶点,然后一个称为片段着色器的代码部分告诉GPU如何绘制每个像素是很有用的。
  4. 这个过程会重复执行,直到所有来自CPU的任务都被GPU处理完毕。

现在我们了解了Unity渲染帧时发生的情况,让我们考虑一下在渲染过程中可能出现的问题。

渲染问题的类型:

渲染最重要的一点是:为了完成渲染帧,CPU和GPU必须完成它们的所有任务。如果其中任何一个任务花费的时间太长,将导致渲染帧的延迟。

渲染问题有两个基本原因。第一种问题是由于管线效率低下引起的。管线效率低下意味着渲染管线中的一个或多个步骤花费的时间过长,中断了数据的平滑流动。管线内的低效率被称为瓶颈。第二种问题是由于试图将过多数据推送到管线中造成的。即使是最高效的管线,在单个帧中处理的数据量也有限。

当游戏因为CPU在执行渲染任务时花费的时间过长而导致渲染帧时间过长时,我们称之为CPU受限。当游戏因为GPU在执行渲染任务时花费的时间过长而导致渲染帧时间过长时,我们称之为GPU受限。

理解渲染问题

在进行任何更改之前,使用性能分析工具来了解性能问题的原因非常重要。不同的问题需要不同的解决方案。同样重要的是,我们要测量每个更改的效果;解决性能问题是一个权衡的过程,改善某个方面的性能可能会对其他方面产生负面影响。
我们将使用两个工具来帮助我们了解和解决渲染性能问题:Profiler窗口和Frame Debugger。这两个工具都内置在Unity中。

Profiler窗口

Profiler窗口允许我们实时查看游戏的性能数据。我们可以使用Profiler窗口查看游戏的许多方面的数据,包括内存使用情况、渲染流程和用户脚本的性能。

帧调试器(Frame Debugger)

帧调试器(Frame Debugger)允许我们逐步查看帧的渲染过程。使用帧调试器,我们可以查看详细信息,例如每个绘制调用期间绘制了什么、每个绘制调用的着色器属性以及发送到GPU的事件顺序。这些信息有助于我们了解游戏的渲染方式并找到可以改善性能的地方。
如果您还不熟悉使用帧调试器,Unity手册的这个页面是对其功能的很好介绍,而这个教程视频展示了它的使用方式。

性能问题的原因

在尝试改善游戏的渲染性能之前,我们必须 确保游戏运行缓慢是由于渲染问题引起的。如果真正的问题是过于复杂的用户脚本,那么尝试优化渲染性能就没有意义!
一旦我们确定问题与渲染有关,我们还必须了解游戏是CPU限制还是GPU限制。不同的问题需要不同的解决方案,因此在尝试修复问题之前,了解问题的原因至关重要。如果您还不确定游戏是CPU限制还是GPU限制,可以参考这个教程进行判断。
如果我们确定问题与渲染有关,并且知道游戏是CPU限制还是GPU限制,那么我们就可以继续阅读。

如果游戏受限于CPU

大体上来说,为了渲染一帧画面,CPU必须执行的工作可以分为三个类别:

  • 确定需要绘制的内容
  • 准备GPU的命令
  • 将命令发送给GPU

这些广泛的类别包含许多个别任务,这些任务可以在多个线程上执行。线程使得不同的任务可以同时进行;当一个线程执行一个任务时,另一个线程可以执行完全不同的任务。这意味着工作可以更快地完成。当渲染任务在不同的线程上分开执行时,称为多线程渲染。

Unity的渲染过程涉及三种类型的线程:主线程渲染线程和工作线程。主线程是我们的游戏中大部分CPU任务发生的地方,包括一些渲染任务。渲染线程是一个专门发送命令给GPU的线程。工作线程每个执行一个单一任务,例如剔除或网格蒙皮。哪些任务由哪个线程执行取决于我们游戏的设置和游戏运行的硬件。例如,目标硬件拥有的CPU核心越多,可以生成的工作线程就越多。因此,在目标硬件上进行我们的游戏的性能分析非常重要;我们的游戏在不同设备上可能表现出完全不同的性能。

在我们尝试提高性能之前,必须了解是哪些任务导致我们的游戏受限于CPU。如果我们的游戏运行缓慢是因为剔除操作在一个线程上花费的时间太长,那么在另一个线程上减少发送命令给GPU所需的时间是没有帮助的。

注意:并非所有平台都支持多线程渲染;在撰写本文时,WebGL不支持此功能。在不支持多线程渲染的平台上,所有的CPU任务都在同一个线程上执行。如果我们在这样的平台上受限于CPU,优化任何CPU工作将改善CPU性能。如果我们的游戏是这种情况,我们应该阅读以下所有部分,并考虑哪些优化方法最适合我们的游戏。

图形任务(Graphics jobs)

Player Settings中的图形任务选项确定Unity是否使用工作线程执行本来应该在主线程和某些情况下在渲染线程上完成的渲染任务。在支持此功能的平台上,它可以显著提升性能。如果我们希望使用此功能,应该在启用和禁用图形任务的情况下对我们的游戏进行性能分析,观察它对性能的影响。

定位哪些任务导致问题

我们可以通过使用Profiler窗口来确定哪些任务导致我们的游戏受限于CPU。本教程将展示如何确定问题所在。
现在我们知道哪些任务导致我们的游戏受限于CPU,让我们来看一下一些常见问题及其解决方案。

发送命令给GPU

发送命令给GPU是导致游戏受限于CPU的最常见原因。在大多数平台上,这个任务是在渲染线程上执行的,尽管在某些平台上(例如PlayStation 4),这可能由工作线程执行。
在发送命令给GPU时,最耗时的操作是SetPass调用。如果我们的游戏因为发送命令给GPU而受限于CPU,减少SetPass调用的次数可能是提高性能的最佳方法。
我们可以在Unity Profiler窗口的Rendering profiler中查看发送的SetPass调用和批次的数量。在性能受影响之前可以发送的SetPass调用数量在很大程度上取决于目标硬件;高端PC可以发送更多的SetPass调用而不会影响性能,而移动设备则较少。

SetPass调用的数量及其与批次数量的关系取决于几个因素,我们将在本文后面更详细地讨论这些主题。然而,通常情况下:

  1. 减少批次数量和/或使更多对象共享相同的渲染状态,通常会减少SetPass调用的数量。
  2. 减少SetPass调用的数量通常会提高CPU性能。

如果减少批次数量不能减少SetPass调用的次数,它仍然可能导致性能的改善。这是因为相比于包含相同数量网格数据的多个批次,CPU可以更有效地处理单个批次。
总的来说,有三种减少批次和SetPass调用次数的方法。我们将更深入地研究每一种方法:
减少需要渲染的对象数量通常会减少批次和SetPass调用次数。

  1. 减少每个对象需要渲染的次数通常会减少SetPass调用次数。
  2. 将需要渲染的对象的数据合并到较少的批次中将减少批次数量。
  3. 不同的技术适用于不同的游戏,因此我们应该考虑所有这些选项,确定哪些选项适用于我们的游戏,并进行实验。
减少正在渲染的对象数量

减少需要渲染的对象数量是减少批次和SetPass调用次数的最简单方法。我们可以使用多种技术来减少需要渲染的对象数量。

  1. 简单地减少我们场景中可见物体的数量可以是一种有效的解决方案。例如,如果我们在场景中渲染了大量不同的角色形成一群人,我们可以尝试减少场景中的这些角色数量。如果场景仍然看起来好而性能有所改善,这很可能是比更复杂的技术更快速的解决方案。
  2. 我们可以通过调整相机的远裁剪平面属性来减小相机的绘制距离。该属性表示相机不再渲染物体的距离。如果我们希望掩盖远处物体不再可见的事实,可以尝试使用雾来隐藏远处物体的缺失。
  3. 为了更细致地根据距离隐藏物体,我们可以利用相机的层剔除距离属性,为处于不同层的物体提供自定义的剔除距离。如果我们有许多小的前景装饰细节,这种方法会很有用;我们可以在比较短的距离内隐藏这些细节,而对于大型地形特征则使用较长的距离进行隐藏。
  4. 我们可以使用一种称为遮挡剔除(occlusion culling)的技术来禁用被其他物体遮挡的物体的渲染。例如,如果场景中有一座大建筑物,我们可以使用遮挡剔除来禁用其后面物体的渲染。Unity的遮挡剔除并不适用于所有场景,可能会增加额外的CPU开销,并且设置起来可能比较复杂,但它可以在某些场景中显著提高性能。Unity关于遮挡剔除最佳实践的博文是这个主题的很好指南。除了使用Unity的遮挡剔除之外,我们还可以通过手动取消激活我们知道玩家看不到的物体来实现自己的遮挡剔除形式。例如,如果我们的场景中包含用于过场动画但在之前或之后不可见的物体,我们应该将它们取消激活。利用我们对自己游戏的了解,总是比让Unity动态处理更高效。

减少每个对象必须渲染的次数

实时照明、阴影和反射为游戏增加了很多逼真感,但代价也很高。使用这些功能可能会导致多次渲染对象,这可能会极大地影响性能。

这些功能的确切影响取决于我们为游戏选择的渲染路径。渲染路径是绘制场景时进行计算的顺序的术语,渲染路径的主要区别在于它们如何处理实时光线、阴影和反射。一般而言,如果我们的游戏在高端硬件上运行并使用大量实时光线、阴影和反射,则延迟渲染(Deferred Rendering)可能是更好的选择。如果我们的游戏在低端硬件上运行并且不使用这些功能,则前向渲染( Forward Rendering)可能更合适。然而,这是一个非常复杂的问题,如果我们希望利用实时光线、阴影和反射,最好是研究这个主题并进行实验。Unity手册的这一页提供了有关Unity中可用的不同渲染路径的更多信息,是一个有用的起点。这个教程包含有关Unity中灯光的有用信息。

无论选择哪种渲染路径,使用实时光线、阴影和反射都会影响我们游戏的性能,因此了解如何优化它们非常重要。

  1. Unity中的动态照明是一个非常复杂的主题,深入讨论超出了本文的范围,但是Unity手册的这一页提供了常见照明优化的详细信息,可以帮助你理解它。
  2. 动态照明(Dynamic lighting)很昂贵。当我们的场景包含不移动的对象,例如风景,我们可以使用一种称为烘焙的技术来预先计算场景的照明,从而不需要运行时计算照明。这个教程介绍了这种技术,Unity手册的这个部分详细介绍了烘焙照明。
  3. 如果我们希望在游戏中使用实时阴影,这可能是提高性能的一个方向。Unity手册的这一页是一个很好的指南,介绍了可以在质量设置中调整的阴影属性以及这些属性如何影响外观和性能。例如,我们可以使用阴影距离属性来确保只有附近的对象投射阴影。
  4. 反射探头(Reflection Probes)可以创建逼真的反射,但在批次方面的代价很高。在关注性能时,最好尽可能减少我们对反射的使用,并在使用时尽可能优化它们。Unity手册的这一页是一个有用的指南,介绍了优化反射探头的方法。

将对象合并为更少的批次

当满足一定条件时,一个批次可以包含多个对象的数据。为符合批处理的条件,对象必须:

  1. 共享同一个材质实例
  2. 具有相同的材质设置(例如纹理、着色器和着色器参数)

对符合批处理条件的对象进行批处理可以提高性能,尽管像所有优化技术一样,我们必须仔细地进行分析,以确保批处理的成本不超过性能的收益。

有几种不同的技术可以用于对符合批处理条件的对象进行批处理:

  1. 静态批处理是一种技术,它允许Unity对不移动的附近适用对象进行批处理。一个可以从静态批处理中受益的很好例子是一堆相似的物体,比如大石头。Unity手册的这个页面包含了在我们的游戏中设置静态批处理的指南。静态批处理可能会导致更高的内存使用量,所以在对游戏进行性能分析时,我们应该牢记这个代价。
  2. 动态批处理是另一种技术,它允许Unity对适用的对象进行批处理,无论它们是否移动。使用这种技术进行批处理有一些限制。这些限制以及相关指导在Unity手册的这个页面上列出。动态批处理对CPU使用有影响,可能会导致在CPU时间上的成本超过节省的效果。在尝试这种技术时,我们应该牢记这个代价,并谨慎使用它。
  3. 对Unity的UI元素进行批处理会稍微复杂一些,因为它可能会受到UI布局的影响。来自2015年曼谷Unite大会的这个视频很好地概述了这个主题,而这个优化Unity UI的指南则提供了深入的信息,告诉我们如何确保UI批处理按照我们的意图进行。
  4. GPU实例化是一种技术,可以非常高效地对大量相同的对象进行批处理。它的使用存在一些限制,并且并非所有硬件都支持,但如果我们的游戏在屏幕上同时有许多相同的对象,我们可能能够从这种技术中受益。Unity手册的这个页面介绍了Unity中的GPU实例化,包括如何使用它、哪些平台支持它以及在什么情况下它可能对我们的游戏有益。
  5. 纹理图集是一种技术,将多个纹理合并成一个更大的纹理。它通常用于2D游戏和UI系统,但也可以用于3D游戏。如果我们在为游戏创建艺术作品时使用这种技术,我们可以确保对象共享纹理,从而有资格进行批处理。Unity内置了一个用于2D游戏的纹理图集工具,称为Sprite Packer。
  6. 可以手动在Unity编辑器或运行时通过代码将共享相同材质和纹理的网格进行组合。在这种方式下,我们必须注意阴影、照明和剔除仍将在单个对象级别上操作。这意味着通过合并网格获得的性能提升可能会因为无法剔除那些本来不需要渲染的对象而被抵消。如果我们想要尝试这种方法,可以查看Mesh.CombineMeshes函数。Unity标准资产包中的CombineChildren脚本是此技术的一个示例。
  7. 在脚本中访问Renderer.material时,我们必须非常小心。这会复制材质并返回对新副本的引用。这样做会破坏批处理,如果渲染器是批处理的一部分,因为渲染器不再引用相同的材质实例。如果我们想在脚本中访问批处理对象的材质,应该使用Renderer.sharedMaterial。

剔除(Culling)、排序(sorting)和批处理(batching)

剔除、收集将要绘制的对象的数据、将这些数据排序成批次并生成GPU命令,所有这些都可能导致CPU瓶颈。这些任务将在主线程或单独的工作线程上执行,具体取决于我们游戏的设置和目标硬件。

  1. 剔除本身不太可能非常昂贵,但减少不必要的剔除可能会提高性能。对于所有活动场景对象,即使它们在未被渲染的层上,每个对象每个相机都有一个开销。为了减少这个开销,我们应该禁用不使用的相机并停用或禁用当前未使用的渲染器。
  2. 批处理可以极大地提高向GPU发送命令的速度,但有时可能会在其他地方添加不必要的开销。如果批处理操作导致游戏变慢,我们可能希望限制游戏中手动或自动批处理操作的数量。

蒙皮网格(Skinned meshes)

当我们使用一种称为骨骼动画的技术通过变形来动画网格时,会使用SkinnedMeshRenderer。它最常用于动画角色。与渲染蒙皮网格相关的任务通常在主线程或单独的工作线程上执行,具体取决于我们游戏的设置和目标硬件。

渲染蒙皮网格可能是一项昂贵的操作。如果我们在Profiler窗口中看到渲染蒙皮网格导致游戏变慢,有几个方法可以尝试提高性能:

  1. 我们应该考虑是否需要为当前使用SkinnedMeshRenderer的每个对象使用此组件。例如,我们可能导入了一个使用SkinnedMeshRenderer组件的模型,但实际上并没有进行动画。在这种情况下,将SkinnedMeshRenderer组件替换为MeshRenderer组件将有助于提高性能。在将模型导入Unity时,如果我们在模型的导入设置中选择不导入动画,则该模型将具有MeshRenderer而不是SkinnedMeshRenderer。
  2. 如果我们只有在某些时间对对象进行动画(例如,仅在启动时或仅在它距相机一定距离时),我们可以将其网格切换为较少详细的版本,或将其SkinnedMeshRenderer组件切换为MeshRenderer组件。 SkinnedMeshRenderer组件具有BakeMesh函数,可以创建一个匹配姿势的网格,这对于在不对对象进行任何可见更改的情况下在不同的网格或渲染器之间进行切换非常有用。
  3. Unity手册的这个页面包含了有关优化使用蒙皮网格的动画角色的建议,而SkinnedMeshRenderer组件的Unity手册页面包括可以提高性能的微调。除了这些页面上的建议外,值得记住的是,网格蒙皮的成本随着每个顶点的增加而增加;因此在我们的模型中使用较少的顶点可以减少必须完成的工作量。
  4. 在某些平台上,皮肤绑定可以由GPU而不是CPU处理。如果我们的GPU有很多容量,这个选项可能值得尝试。我们可以在播放器设置中为当前平台和质量目标启用GPU皮肤绑定。

与渲染无关的主线程操作

重要的是要理解,许多与渲染无关的CPU任务会在主线程上执行。这意味着如果我们在主线程上受到CPU限制,我们可能可以通过减少与渲染无关的CPU任务所花费的时间来改善性能。

例如,在我们的游戏中的某个点,我们的游戏可能在主线程上执行昂贵的渲染操作和昂贵的用户脚本操作,使我们受到CPU限制。如果我们已经尽可能地优化了渲染操作而不失去视觉保真度,那么我们有可能通过减少我们自己脚本的CPU成本来提高性能。

如果游戏受到GPU限制

如果我们的游戏受到GPU限制,首先要做的是找出是什么导致了GPU瓶颈。GPU性能最常受到填充率的限制,特别是在移动设备上,但内存带宽和顶点处理也可能是问题。让我们分别检查这些问题,学习其原因、如何诊断以及如何修复。

填充率(Fill rate)

填充率是指GPU每秒可以渲染到屏幕的像素数量。如果我们的游戏受到填充率的限制,这意味着我们的游戏正在尝试每帧绘制比GPU处理能力更多的像素。

简单来说,我们可以检查填充率是否导致了游戏受到GPU限制:

  1. 对游戏进行分析并记录GPU时间。
  2. 在玩家设置中降低显示分辨率。
  3. 再次对游戏进行分析。如果性能有所改善,那么很可能是填充率的问题。

如果填充率是问题的原因,有几种方法可以帮助我们解决问题。

  1. 片段着色器是着色器(shaders)代码的部分,它告诉GPU如何绘制单个像素。这段代码被GPU执行,用于绘制每个像素,因此,如果代码效率低下,性能问题很容易堆积。复杂的片段着色器是填充率问题的一个非常普遍的原因。
  2. 如果我们的游戏使用内置着色器,我们应该尽可能使用最简单和最优化的着色器来实现我们想要的视觉效果。例如,Unity随附的移动设备着色器已经高度优化,我们应该尝试使用它们并查看是否可以在不影响游戏外观的情况下提高性能。这些着色器是为移动平台设计的,但它们适用于任何项目。如果它们为项目提供所需的视觉保真度,那么在非移动平台上使用“移动”着色器以提高性能是完全可以的。
  3. 如果我们的游戏中的对象使用Unity的标准着色器,那么重要的是要了解Unity根据当前的材质设置编译着色器。仅编译当前正在使用的功能。这意味着删除诸如细节贴图之类的功能可能会导致更少复杂的片段着色器代码,这可以极大地改善性能。同样,如果这是我们的游戏中的情况,我们应该尝试更改设置,并查看是否能够在不影响视觉质量的情况下提高性能。
  4. 如果项目使用自定义着色器,应尽可能优化它们。优化着色器是一个复杂的主题,但可从Unity手册的这一页找到一些参考。
  5. Overdraw是指同一像素被绘制多次的情况。当对象在其他对象的上方绘制时,就会发生这种情况,这会导致填充率问题。要了解过度绘制,我们必须了解Unity绘制场景中对象的顺序。一个对象的着色器决定了它的绘制顺序,通常是通过指定对象所在的渲染队列来实现的。Unity使用这些信息以严格的顺序绘制对象,详细信息请参见Unity手册的这一页。此外,不同渲染队列中的对象在绘制之前以不同的方式进行排序。例如,Unity在几何队列中从前往后对物品进行排序以最小化过度绘制,但在Transparent队列中从后往前对对象进行排序以实现所需的视觉效果。这种从后往前的排序实际上会使Transparent队列中的对象过度绘制。过度绘制是一个复杂的问题,没有一种通用的方法可以解决过度绘制问题,但是减少Unity无法自动排序的重叠对象数量是关键。最好的起点是在Unity的场景视图中进行调查;有一个绘制模式可以让我们看到场景中的过度绘制,从而确定我们可以在哪里工作以减少过度绘制。过度绘制的最常见的罪魁祸首是透明材料、未经优化的粒子和重叠的UI元素,因此我们应该尝试优化或减少这些元素。Unity Learn网站上的这篇文章主要关注Unity UI,但也包含有关过度绘制的良好的一般指导。
  6. 使用图像效果可能会极大地影响填充率问题,特别是如果我们使用多个图像效果时。如果我们的游戏使用图像效果并且遇到填充率问题,我们可以尝试使用不同的设置或更优化的图像效果版本(例如,使用“优化版”的Bloom代替Bloom)。如果我们的游戏在同一摄像机上使用多个图像效果,则会产生多个着色器通道。在这种情况下,将我们的图像效果的着色器代码合并到单个通道中,例如Unity的后处理堆栈中,可能会有益。如果我们已经优化了我们的图像效果,仍然有填充率问题,我们可能需要考虑禁用图像效果,特别是在低端设备上。

内存带宽

内存带宽是指GPU从其专用内存中读取和写入数据的速率。如果我们的游戏受限于内存带宽,通常意味着我们使用的纹理对于GPU来说太大了,无法快速处理。

为了检查内存带宽是否是一个问题,我们可以执行以下操作:

  1. 对游戏进行性能分析,并记录GPU时间。
  2. 在质量设置中,降低当前平台和质量目标的纹理质量。
  3. 再次对游戏进行性能分析,并记录GPU时间。如果性能有所改善,很可能是内存带宽引起的问题。

如果内存带宽是我们的问题,我们需要减少游戏中的纹理内存使用。同样,适用于每个游戏的最佳技术可能各不相同,但我们可以通过一些方法优化纹理。

  1. 纹理压缩是一种技术,可以大幅减小纹理在磁盘和内存中的大小。如果内存带宽在我们的游戏中是一个问题,使用纹理压缩来减小内存中纹理的大小可以提高性能。在Unity中有许多不同的纹理压缩格式和设置可供选择,每个纹理都可以有单独的设置。通常情况下,应该尽可能使用某种形式的纹理压缩;然而,通过试错方法找到每个纹理的最佳设置效果最好。Unity手册中的这个页面包含了关于不同压缩格式和设置的有用信息。
  2. Mipmaps是纹理的低分辨率版本,Unity可以在远离相机的对象上使用。如果我们的场景中包含与相机距离较远的对象,我们可以使用Mipmaps来缓解内存带宽的问题。在场景视图中的Mipmaps绘制模式可以让我们看到哪些场景中的对象可以受益于Mipmaps,并且Unity手册的这个页面提供了有关为纹理启用Mipmaps的更多信息。

顶点处理

顶点处理是指GPU在渲染网格中的每个顶点时所需执行的工作。顶点处理的成本受两个因素影响:必须渲染的顶点数量和必须对每个顶点执行的操作数量。

如果我们的游戏受限于GPU,并且已经确定不受填充率或内存带宽的限制,那么很可能顶点处理是问题的原因。如果是这种情况,尝试减少GPU必须执行的顶点处理量可能会带来性能提升。

我们可以考虑几种方法来帮助我们减少顶点数量或每个顶点上执行的操作数量。

  1. 首先,我们应该致力于减少不必要的网格复杂性。如果我们使用的网格在游戏中无法看到其细节级别,或者由于创建错误导致的效率低下的网格具有过多的顶点,那么这对GPU来说是一种浪费。减少顶点处理成本的最简单方法是在我们的3D艺术程序中创建具有较低顶点数的网格。
  2. 我们可以尝试一种称为法线贴图的技术,它使用纹理来在网格上创建更大几何复杂性的视觉效果。尽管这种技术会在GPU上产生一些开销,但在许多情况下会带来性能提升。Unity手册的这个页面提供了有关如何使用法线贴图来模拟网格中复杂几何形状的有用指南。
  3. 如果我们的游戏中的某个网格没有使用法线贴图,我们通常可以在该网格的导入设置中禁用顶点切线的使用。这将减少每个顶点发送到GPU的数据量。
  4. 细节级别(Level of Detail,简称LOD)是一种优化技术,通过减少远离相机的网格的复杂性来降低顶点数量,而不影响游戏的视觉质量。Unity手册的LOD Group页面提供了有关如何在游戏中设置LOD的更多信息。
  5. 顶点着色器是一段着色器代码,告诉GPU如何绘制每个顶点。如果我们的游戏受限于顶点处理,那么减少顶点着色器的复杂性可能会有所帮助。
  6. 如果我们的游戏使用内置着色器,我们应该尽可能使用最简单和最优化的着色器来实现我们想要的视觉效果。举个例子,Unity附带的移动设备着色器经过高度优化;我们可以尝试使用它们,并查看是否可以在不影响游戏外观的情况下提高性能。
  7. 如果我们的项目使用自定义着色器,我们应该尽可能优化它们。优化着色器是一个复杂的主题,但是Unity手册的这个页面提供了有关优化我们的着色器代码的有用起点。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/不正经/article/detail/105627
推荐阅读
相关标签
  

闽ICP备14008679号