Version: 2021.2
OpenGL Core
Optimizing draw calls

优化图形性能

良好的性能对很多游戏的成功至关重要。以下几条简单法则有助于将游戏的渲染速度最大化。

找出影响图形性能的主要因素

游戏的图形部分主要影响计算机的两个系统:CPU 和 GPU。找到性能问题所在是一切优化的首要法则,因为 GPU 与 CPU 的优化策略大不相同(甚至相反;例如,通常在优化 CPU 时让 GPU 做更多工作,反之亦然)。

常见瓶颈及检查方法:

  • GPU 通常受填充率或内存带宽制约。
    • 降低显示分辨率并运行游戏。如果显示分辨率降低后游戏运行更快,表明 GPU 填充率可能是限制因素。
  • CPU 通常受到需要渲染的批次数的限制。
    • 检查 Rendering Statistics 窗口中的“batches”。渲染的批次越多,CPU 成本越高。

不太常见的瓶颈:

  • GPU 有太多顶点需要处理。可接受的能确保良好性能的顶点数量取决于 GPU 和顶点着色器的复杂程度。一般来说,移动端应不超过 100,000 个顶点。另一方面,即使有数百万个顶点,PC 也能管理到位,不过最好还是通过优化尽可能减少此数量。
  • CPU 有太多顶点需要处理。这些顶点可能位于蒙皮网格、布料模拟、粒子或其他游戏对象和网格中。如上所述,通常较好的做法是在不影响游戏质量的情况下尽可能降低此数量。有关如何执行此类操作的指导,请参阅下面有关 CPU 优化的部分。
  • 如果渲染在 GPU 或 CPU 方面不是问题,则可能在其他地方存在问题,例如在脚本或物理系统中。请使用 Unity Profiler 找出问题。

CPU 优化

为了在屏幕上渲染对象,CPU 需要做很多处理工作:确定哪些光源影响该对象,设置着色器和着色器参数,向图形驱动程序发送绘制命令,而图形驱动程序随后将准备发送到显卡的命令。

所有这种基于“每个对象”的 CPU 使用率都是非常消耗资源的,所以如果有很多可见对象,影响就会累加起来。例如,如果有一千个三角形,如果它们都在一个网格中,而不是每个三角形在一个网格中(这种情况下加起来就有 1000 个网格),则 CPU 处理起来就比较容易。两种方案的 GPU 成本非常相似,但 CPU 完成渲染一千个对象(而不是一个)的工作要高得多。

减少可见对象数量。要减少 CPU 需要执行的工作量,请执行以下操作:

  • 通过手动方式或使用 Unity 的绘制调用批处理将近处对象组合在一起。
  • 通过将单独的纹理放入更大的纹理图集,在对象中使用更少的材质。
  • 减少可能导致对象多次渲染的因素(例如反射、阴影和每像素光照)。

将对象组合在一起,使每个网格至少有几百个三角形,并使整个网格只使用一种__材质__。请注意,组合两个不共享材质的对象根本不会提高性能。需要多种材质的最常见原因是两个网格不共享相同的纹理;为了优化 CPU 性能,请确保组合的所有对象共享相同的纹理。

前向渲染路径中使用大量像素光照时,有些情况下组合对象可能没有意义。请参阅下面的光照性能部分,了解如何管理此情况。

使用 OnDemandRendering 进行 CPU 优化

使用 OnDemandRendering 可通过控制应用程序的渲染速度来提升 CPU 性能。

在以下情况下,可能需要降低帧率:

  • 菜单,例如应用程序入口点或暂停菜单。菜单往往是相对简单的场景,不需要全速渲染。可以使用较低的帧率来渲染菜单,从而降低功耗并防止设备温度升高到可能调节 CPU 频率的程度。
  • 回合制游戏,例如国际象棋。玩家花时间等待其他用户行动或者玩家思考自己的行动。在活动较低的时间段中,可以降低帧率以防止不必要的功耗并延长电池续航时间。
  • 内容在大多数时间内是静态的应用程序(如汽车 UI)。

调整渲染速度有助于管理功耗和设备散热,从而最大限度延长电池续航时间并防止 CPU 调速。这一点特定适合于 Adaptive Performance 包。即使帧的渲染频率不高,应用程序仍会以正常速度将事件发送到脚本(例如,应用程序可能会在未渲染的帧期间接收输入)。为了防止输入滞后,可以在输入持续时间内调用 OnDemandRendering.renderFrameInterval = 1,以便移动、按钮等仍可迅速响应。

如果脚本、物理、动画等方面工作量巨大,但渲染量并不大,此时使用这一 API 作用不大。应用程序的视觉效果可能会卡顿,对功耗的影响最小。

注意:VR 应用程序不支持按需渲染 (On Demand Rendering)。如果不渲染每一帧,则会导致视觉效果与头部移动不同步,并可能增加晕动症的风险。

GPU:优化模型几何体

优化模型几何体有两个基本规则:

  • 除非必要,否则不要使用三角形。
  • 尽可能降低 UV 贴图接缝和硬边(双倍顶点)的数量。

请注意,图形硬件必须处理的实际顶点数通常与 3D 应用程序报告的数量不同。建模应用程序通常显示的是构成模型的不同角点的数量(称为几何顶点数)。但是,对于显卡,为了进行渲染,需要将一些几何顶点拆分成两个甚至更多个逻辑顶点。如果顶点具有多个法线、UV 坐标或顶点颜色,则必须将其拆分。因此,Unity 中的顶点计数通常高于 3D 应用程序给出的计数。

虽然模型中的几何体数量主要与 GPU 相关,但 Unity 中的某些功能也要在 CPU 上处理模型(例如,网格蒙皮)。

有关在 Unity 之外的 3D 应用程序中创建资源时提高性能的更多技巧,请参阅角色建模的性能优化

光照性能

The fastest option is always to create lighting that doesn’t need to be computed at all. To do this, use Lightmapping to “bake” static lighting just once, instead of computing it each frame. The process of generating a lightmapped environment takes only a little longer than just placing a light in the scene in Unity, but:

  • 运行速度要快得多(每像素 2 个光源的情况下,速度快 2–3 倍)
  • 视觉效果要好得多,因为可以烘焙全局光照,使光照贴图显得更平滑

在许多情况下,可运用简单的技巧,无需添加多个额外的光照。例如,无需添加直接照入摄像机的光源来提供__边缘光照__效果,而是直接在着色器中添加专用的 Rim Lighting 计算(请参阅表面着色器示例以了解如何执行此操作)。

前向渲染中的光照

另请参阅:前向渲染

对于所有像素,动态光照会为每个受影响的像素增加渲染工作,可能导致对象在多个 pass 中被渲染。避免在性能较弱的设备(如移动端或低端 PC GPU)上使用多个__像素光照__来照射单个对象,应使用光照贴图实现静态对象的光照,而不是每帧计算其光照。每顶点动态光照可能会为顶点变换增加显著的工作量,因此尽量避免多个光源照射单个对象的情况。

避免组合距离足够远而需要受到不同像素光照影响的网格。使用像素光照时,每个网格必须渲染多次,因为只要发生像素光照就要进行渲染。如果组合两个相距很远的网格,则会增加组合对象的有效大小。照射该组合对象任何部分的所有像素光照在渲染期间都要考虑在内,因此需要创建的渲染 pass 的数量可能增加。通常情况下,为渲染组合对象而必须创建的 pass 数为每个单独对象的 pass 数之和,因此进行网格组合并不会获得任何好处。

在渲染过程中,Unity 会查找网格周围的所有光源,并计算出哪些光源对网格的影响最大。使用 Quality 窗口上的设置可修改多少个光源用于像素光照以及多少个用于顶点光照。每个光源根据它与网格的距离以及它的光照强度来计算其重要性;纯粹从游戏背景而言,有些光源比另一些光源更重要。鉴于此原因,每个光源都有 Render Mode 设置,可设置为 ImportantNot Important__;标记为 Not Important__ 的光源具有较低的渲染开销。

示例:假设有一个驾驶游戏,玩家的汽车在黑暗中行驶,前照灯已打开。前照灯可能是游戏中视觉上最重要的光源,因此它们的 Render Mode 应设置为 Important。游戏中可能还有其他不太重要的光源,比如其他汽车的尾灯或远处的灯柱,这些光源不能通过像素光照来大幅改善视觉效果。这种情况下,可放心地将这些光源的 Render Mode 设置为 __Not Important__,从而避免将渲染能力浪费在无用之处。

通过优化每像素光照可以节省 CPU 和 GPU 工作量:CPU 的绘制调用将减少,而 GPU 要处理的顶点将减少,同时为所有其他对象渲染栅格化的像素也将减少。

GPU:纹理压缩和 Mipmap

使用压缩纹理可减小纹理的大小。这种做法可加快加载时间、减小内存占用并显著提高渲染性能。与未压缩的 32 位 RGBA 纹理所需的内存带宽相比,压缩纹理使用的内存带宽要小得多。

纹理 Mipmap

对于 3D 场景中使用的纹理,应始终启用 Generate mipmaps 选项。Mipmap 纹理使 GPU 能够为较小的三角形使用较低分辨率的纹理。这一点类似于纹理压缩可以帮助限制 GPU 渲染时传输的纹理数据量。

此规则的唯一例外是当已知纹理像素将 1:1 映射到渲染的屏幕像素时(与 UI 元素或在 2D 游戏中一样)。

LOD(细节级别)和每层剔除距离

剔除对象涉及使对象不可见。这是减轻 CPU 和 GPU 负载的有效方法。

在许多游戏中,在不影响玩家体验的情况下快速有效地执行此操作的方法是,相对于大对象,更激进地剔除小对象。例如,可让远处的小岩石和碎片不可见,而大型建筑物仍然保持可见。

有多种方式实现此目标:

Real-time shadows

Real-time shadows are nice, but they can have a high impact on performance, both in terms of extra draw calls for the CPU and extra processing on the GPU. For further details, see the Shadow Performance page.

GPU:编写高性能着色器的技巧

不同的平台具有截然不同的性能;与低端移动端 GPU 相比,高端 PC GPU 在图形和着色器方面的处理能力要高得多。即使在单一平台上也是如此;快速的 GPU 比慢速的集成 GPU 快几十倍。

GPU performance on mobile platforms and low-end PCs is likely to be much lower than on your development machine. It’s recommended that you manually optimize your shaders to reduce calculations and texture reads, in order to get good performance across low-end GPU machines. For example, some built-in Shader objects have “mobile” equivalents that are much faster, but have some limitations or approximations.

以下是移动端和低端 PC 显卡的一些指导原则:

复杂的数学运算

超越数学函数(例如 powexplogcossintan)都很消耗资源,所以尽量避免使用它们。如果可能,请尽量考虑使用查找纹理作为复杂数学计算的替代方法。

避免编写自己的运算(如 normalizedotinversesqrt)。Unity 的内置选项确保驱动程序可以生成好得多的代码。请记住,Alpha 测试 (discard) 运算通常会使片元着色器变慢。

浮点精度

While the precision (float vs half vs fixed) of floating point variables is largely ignored on desktop GPUs, it is quite important to get a good performance on mobile GPUs. See the Shader Data Types and Precision page for details.

有关着色器性能的更多详细信息,请参阅着色器性能页面。

用于提高游戏运行速度的简单核对表

  • 在针对 PC 平台进行构建时,保持顶点数量低于 200K 和 3M/帧(具体值取决于目标 GPU)。
  • 如果要使用内置着色器,请从 MobileUnlit 类别中选取。这些类别也适用于非移动平台,但它们是更复杂着色器的简化和近似版本。
  • 保持每个场景使用较少的不同材质,并尽可能在不同对象之间共享材质。
  • 在非移动对象上设置 Static 属性以便允许内部优化,如静态批处理
  • 只有一个(最好是方向性的)pixel light 影响几何体(而不是有多个)。
  • 烘焙光照而不是使用动态光照。
  • 尽可能使用压缩纹理格式,并使用 16 位纹理而非 32 位纹理。
  • 尽可能避免使用雾效。
  • 如果复杂的静态场景具有大量遮挡,使用遮挡剔除减少可见几何体数量和绘制调用次数。设计关卡时注意遮挡剔除。
  • 使用天空盒“伪造”远处的几何体。
  • 使用像素着色器或纹理组合器来混合多个纹理而不是使用多 pass 方法。
  • 尽可能使用 half 精度变量。
  • 最大限度减少在像素着色器中使用复杂的数学运算,例如 powsincos
  • 每个片元使用更少的纹理。

另请参阅

OpenGL Core
Optimizing draw calls