这是系列文章的第二篇,详细介绍了 Unity 项目的优化技巧。使用它们作为以更少资源以更高帧速率运行的指南。尝试这些最佳实践后,请务必查看该系列的其他页面:
Unity 的图形工具使您能够在一系列平台上(从移动设备到高端控制台和桌面)创建任何风格的优化图形。这个过程通常取决于您的艺术指导和渲染管道,因此在开始之前,我们建议您查看 可用的渲染管道。
选择渲染管道时,请考虑 这些因素 。除了选择管道,您还需要选择渲染路径。
渲染路径表示与光照和阴影相关的一系列特定操作。决定渲染路径取决于应用程序的需求和目标硬件。
前向渲染路径
通用渲染管线 (URP)和 内置渲染管线均使用前向渲染。在前向渲染中,显卡投影几何体并将其分割为顶点。这些顶点被进一步分解成片段或像素,然后渲染到屏幕上,形成最终的图像。
管道每次将一个对象传递给图形 API。前向渲染会为每个光源带来成本,因此场景中的光源越多,渲染所需的时间就越长。
内置管道的 前向渲染器 在每个对象的单独通道中绘制每个灯光。如果多个灯光照射到同一个游戏对象上,则可能会造成严重的过度绘制,重叠区域需要多次绘制相同的像素。为了减少过度绘制,请尽量减少实时光源的数量。
URP 不会为每个光源渲染一次,而是根据每个对象剔除光源。这样就可以在一次传递中计算照明,与内置渲染管道的前向渲染器相比,产生更少的绘制调用。
内置渲染管线、URP 和 高清渲染管线 (HDRP) 也使用延迟着色渲染路径。在延迟着色中,照明不是按对象计算的。
相反,延迟着色将繁重的渲染(例如照明)推迟到后期并使用两次传递。在第一个阶段(也称为 G-buffer 几何阶段)中,Unity 渲染游戏对象 (GameObjects)。此过程检索几种类型的几何属性并将它们存储在一组纹理中。
G 缓冲区纹理可以包括:
- 漫反射和镜面反射颜色
- 表面光滑度
- 遮挡
- 世界空间法线
- 发射 + 环境光 + 反射 + 光照贴图
在第二个过程或 照明过程中,Unity 根据 G 缓冲区渲染场景的照明。想象一下迭代每个像素并根据缓冲区而不是单个对象来计算照明信息。在延迟着色中添加更多非阴影投射光源不会像前向渲染那样造成性能损失。
虽然选择渲染路径本身并不是一种优化,但它可以影响您优化项目的方式。本节中的其他技术和工作流程根据您选择的渲染管道和路径而有所不同。
HDRP 和 URP 都支持 Shader Graph,这是一个基于节点的着色器创建可视化界面。它允许没有着色器编程经验的用户创建复杂的着色效果。
Shader Graph 中当前有超过 150 个 节点 可用。此外,您还可以使用 API 创建自己的自定义节点。
着色器图 (Shader Graph) 中的每个着色器都以主节点 (Master Node)开始,它决定着色器图的输出。通过在可视化界面中添加和连接节点和操作符来构建着色器逻辑。
然后,着色器视图 (Shader Graph) 传递到渲染管道的后端。最终结果是 ShaderLab 着色器,其 功能与用 HLSL 或 Cg 编写的着色器类似 。
优化 Shader Graph 遵循许多适用于传统 HLSL 或 Cg 着色器的相同规则;其中重要的一条是,Shader Graph 的处理越多,对应用程序性能的影响就越大。
如果您受到 CPU 限制,优化着色器不会提高帧速率,但可能会延长移动平台的电池寿命。
如果您受到 GPU 限制,请遵循以下准则来提高 Shader Graph 的性能:
删除未使用的节点:除非有必要,否则不要更改任何默认值或连接节点。Shader Graph 会自动编译掉所有未使用的功能。如果可能的话,将值烘焙到纹理中。例如,不要使用节点来使纹理变亮,而是将额外的亮度应用到纹理资产本身。
尽可能使用较小的数据格式:如果您的项目允许,请考虑使用 Vector2 而不是 Vector3,或者降低精度(例如,使用 half 而不是 float)。
减少数学运算:着色器操作每秒运行多次,因此请尽可能尝试优化数学运算符。旨在混合结果而不是创建逻辑分支。在应用向量之前使用常量并组合标量值。最后,将任何不需要出现在 Inspector 中的属性转换为内联节点。所有这些渐进式增强都可以帮助您的框架预算。
分支预览:随着图表变得越来越大,编译速度可能会变得越来越慢。使用单独的、较小的分支(仅包含您当前想要预览的操作)来简化您的工作流程。然后,在这个较小的分支上更快地进行迭代,直到获得所需的结果。如果分支未连接到主节点,您可以安全地将预览分支留在图表中。Unity 在编译期间删除不影响最终输出的节点。
手动优化: 即使是经验丰富的图形程序员仍然可以使用 Shader Graph 为基于脚本的着色器制定一些样板代码。选择Shader Graph资源,然后从上下文菜单中选择“复制着色器”。创建一个新的 HLSL/Cg 着色器,然后粘贴复制的 Shader Graph。这是一个单向操作,但它可以让您通过手动优化来获取额外的性能。
从 “始终包含的着色器”列表中删除每个未使用的着色器,该列表位于 图形 设置(编辑 > 项目设置 > 图形)中。在此处添加应用程序生命周期所需的任何着色器。
使用 Shader 编译指令 来使着色器的编译适应每个目标平台。然后使用着色器关键字(或 Shader Graph 关键字 节点)来创建启用或禁用某些功能的 着色器变体 。
着色器变体对于特定于平台的功能很有用,但也会增加构建时间和文件大小。如果您知道不需要着色器变体,则可以阻止将其包含在您的构建中。
首先,解析 Editor.log 以了解着色器时间和大小。然后找到以 Compiled shader 和 Compressed shader开头的行。
此示例日志显示以下统计数据:
编译着色器“测试标准(镜面设置)”耗时 31.23 秒
d3d9(总内部程序:482,唯一:474)
d3d11(总内部程序:482,唯一:466)
金属(总内部程序:482,唯一:480)
glcore(内部程序总数:482,唯一:454)
将 d3d9 上的着色器“测试标准(镜面设置)”从 1.04MB 压缩到 0.14MB
d3d11 上的压缩着色器“测试标准(镜面设置)”从 1.39MB 缩减至 0.12MB
金属上的压缩着色器“测试标准(镜面设置)”从 2.56MB 缩减至 0.20MB
将 glcore 上的压缩着色器“测试标准(镜面设置)”从 2.04MB 压缩到 0.15MB
这些统计数据告诉您有关着色器的一些信息:
- 由于#pragma multi_compile和shader_feature,它扩展为 482 个变体。
- Unity 将游戏数据中包含的着色器压缩为大致等于压缩大小的总和:0.14+0.12+0.20+0.15 = 0.61 MB.
- 在运行时,Unity 将压缩数据保存在内存中(0.61 MB),而当前使用的图形 API 的数据保持未压缩状态。例如,如果您当前的 API 是 Metal,则将占用 2.56 MB。
构建后, 项目审核员 (实验性)可以解析 Editor.log 以显示编译到项目中的所有着色器、着色器关键字和着色器变体的列表。还可以分析游戏运行后的 Player.log。这向您显示了应用程序在运行时实际编译和使用的变体。
利用这些信息构建可编写脚本的着色器剥离系统并减少变体的数量。这可以改善构建时间、构建大小和运行时内存使用情况。
阅读 剥离可编写脚本的着色器变体文章 来详细了解此过程。
抗锯齿通过减少锯齿边缘和最大限度地减少 镜面锯齿有助于提高图像质量。
如果您使用内置渲染管道进行前向渲染,则 质量 设置中可以使用 多重采样抗锯齿 (MSAA)。MSAA 可产生高质量的抗锯齿效果,但成本较高。下拉菜单中的 MSAA 样本数 设置定义了渲染器使用多少个样本来评估效果(无、2X、4X、8X)。如果您将前向渲染与 URP 或 HDRP 结合使用,则可以分别在 URP 资源 或 HDRP 资源 上启用 MSAA。
或者,您可以添加抗锯齿作为后期处理效果。它出现在 相机 组件( 抗锯齿下)上,并带有几个选项:
- 快速近似抗锯齿(FXAA) 可在逐像素级别平滑边缘。这是资源占用最少的抗锯齿类型。它使最终的图像稍微模糊一些。
- 子像素形态抗锯齿(SMAA) 根据图像的边界混合像素。它提供比 FXAA 更清晰的效果,适合平面、卡通或清晰的艺术风格。
在 HDRP 中,您还可以在相机的 后期处理抗锯齿 中使用 FXAA 和 SMAA,并附加一个选项:
- 时间抗锯齿(TAA) 使用历史缓冲区中的帧来平滑边缘。这比 FXAA 更有效,但需要运动矢量才能工作。TAA 还可以改善环境光遮蔽和体积。它的质量通常比 FXAA 更高,但需要额外的资源并且偶尔会产生重影。
创建照明的最快选项是不需要按帧计算的选项。使用 光照贴图 仅烘焙一次静态照明,而不是实时计算。
使用 全局照明 (GI) 为静态几何体添加戏剧性的灯光。勾选 “贡献 GI” 选项,以便对象以光照贴图的形式存储高质量光照。
生成光照贴图环境的过程比仅在场景中放置光源需要更长的时间,但它提供了以下关键优势:
- 每像素两个灯光的运行速度提高两到三倍
- 通过全局照明改进视觉效果,可以计算逼真的直接和间接照明,而光照贴图器可以平滑和去噪生成的地图
- 烘焙阴影和灯光渲染,不会对实时灯光和阴影造成性能影响
更复杂的场景可能需要更长的烘焙时间。如果您的硬件支持 渐进式 GPU 光照贴图器 (预览版),则此选项可以通过使用 GPU 而不是 CPU 显著加快光照贴图生成速度。
按照 本指南 开始使用 Unity 中的光照贴图。
每个 网格渲染器 和光线都可以禁用阴影投射。尽可能禁用阴影以减少绘制调用。您还可以使用应用于角色下方的简单网格或四边形的模糊纹理来创建假阴影。否则,您可以使用自定义着色器创建斑点阴影。
特别是,避免为 点光源启用阴影。每个带有阴影的点光源都需要六次阴影贴图传递,相比之下, 聚光源只需一次阴影贴图传递。当动态阴影绝对必要的时候,考虑用聚光源代替点光源。如果您可以避免动态阴影,请将立方体贴图作为 Light.cookie 与点光源一起使用。
在某些情况下,您可以应用简单的技巧,而不是添加多个额外的灯光。例如,不要创建直接照射到相机中的灯光来产生边缘照明效果,而是使用着色器来模拟 边缘照明 (有关 HLSL 中的实现,请参阅 表面着色器示例 )。
对于具有许多灯光的复杂场景,使用图层分离对象,然后将每个灯光的影响限制在特定的 Culling Mask中。
光照探测器存储有关场景中空白空间的烘焙照明信息,同时提供高质量照明(直接照明和间接照明)。它们使用 球谐函数,与动态光相比,计算速度更快。这对于通常无法接收烘焙光照贴图的移动物体特别有用。
光探测器也可以应用于静态网格。在 Mesh Renderer 组件中,找到 Receive Global Illumination 下拉菜单并将其从 Lightmaps 切换到 Light Probes。
继续使用光照贴图来绘制突出的级别几何图形,但切换到光照探针来照亮较小的细节。光探针照明不需要适当的 UV,从而节省了展开网格的额外步骤。由于探测器不会生成光照贴图纹理,因此还可以减少磁盘空间。
请参阅 使用光探测器的静态照明文章,以及 在 Unity 中制作可信的视觉效果 以了解更多信息。
我们迄今为止最全面的指南之一收集了 80 多条有关如何针对 PC 和游戏机优化游戏的可行技巧。这些深入的提示由我们的专业成功和Accelerate Solutions工程师创建,将帮助您充分利用 Unity 并提升游戏性能。