使用 RenderDoc Meta Fork 优化您的应用 - 第 2 部分
更新时间: 2026年4月17日
本指南将向您介绍 RenderDoc Meta Fork 的几个关键场景,以便您使用该工具来优化您的应用。本指南重点关注可轻松识别的渲染模式,有助于找出渲染线程和 GPU 的优化机会。本指南旨在说明如何识别这些问题并采取缓解措施。
前向渲染只绘制您通过绘制调用发送的网格并直接渲染到帧缓冲区。系统会计算片断和/或顶点着色器中的照明,并将其应用于发出的绘制调用中的模型。这种方法的缺点在于动态/重要光线可能需要额外的通道。GPU 成本由每个模型的几何图形量、所需通道的数量和照明模型的复杂度决定,而在延迟渲染中,系统会计算屏幕空间中的照明。
延迟渲染基本上意味着照明计算被推迟到基础通道完成之后。延迟渲染中的基础通道不同于前向渲染中的基础通道,因为您写入了多个称为 Gbuffer(几何缓冲区)的渲染目标。Gbuffer 包含多个渲染目标,它们定义 Albedo(颜色)、法线、粗糙度值以及计算照明所需的其他任何元素,而无需通过图形管线重新运行模型。写入所有这些值后,下一个阶段是使用之前生成的 Gbuffer 在屏幕空间生成照明通道。该方法的优点在于,照明计算会在屏幕空间中完成,这样就无需考虑几何复杂度。延迟渲染的问题在于,在照明通道阶段,无论 Gbuffer 中有多少个渲染目标需要解决,仅仅这些目标就占用了帧预算的近一半。这甚至不包括相当昂贵的照明通道,因为您需要计算高分辨率 Meta Quest 屏幕上每个像素的照明。
重要:Meta Quest 使用基于图块的 GPU 架构,在该架构中,解析多个 GBuffer 渲染目标的成本特别高昂。强烈建议在 Quest 开发中使用前向渲染。Meta XR 插件的项目设置工具默认强制使用前向渲染。确保在项目设置中将
r.ForwardShading 设置为
True。有关更多详情,请参阅
前向渲染器。
上面的 RenderDoc 截图显示了使用延迟渲染方法的游戏画面。箭头显示了每个单独的绘制调用所绘制的渲染目标。这些渲染目标随后被用作照明计算的输入纹理。作为对比,请参阅下面使用前向渲染方法的游戏 RenderDoc 截图(1 组视觉缓冲区绑定):
为每个绘制调用生成的着色器字节码可以在 RenderDoc 中查看,以明确了解着色器编译成的代码。最好的优化方法是,查看哪些绘制调用在几何图形计数较低且没有大量像素覆盖范围的情况下,需要花费相对较长的时间来完成。RenderDoc 使用得越多,您就越能了解应该检查哪个部分。开发者发现,25-50 个指令通常是着色器的理想选择,这样可以达到理想的外观,同时还能保持最佳性能,但您必须考虑复杂的数学指令、纹理查找和潜在的像素覆盖。一般来说,建议制作一组数量有限的 Uber 着色器,以最大限度地提高批处理和最大限度地减少 SetPass 或管线变化。一个附带的好处是,您每次只需关注一小组着色器生成的代码。当使用一组数量有限的 Uber 着色器时,只有这一组着色器的性能增强会传播到这些逻辑组中的所有网格。例如,如果您的游戏在某个城市进行,您可以为建筑物、混凝土人行道、屋顶等设置一个布景着色器,再为蒙皮角色设置一个着色器。这样,您可以轻松地合并网格并创建网格 LOD(细节级别)或 HLOD(分层细节级别),而无需担心管线状态会破坏它们。
作为提示,所有对象都可以使用不同的输入纹理,但您可以为尽可能多的使用同一着色器的对象使用纹理数组和查找编号(或创建纹理图集),以最大限度地减少 SetPass 和最大限度地增加批处理。
下面介绍了如何使用 RenderDoc 查看当前选定的绘制调用的输入纹理或统一规定:
LOD(细节级别)系统允许您在近距离观看时使用复杂的精细网格,而在远距离观看时切换为模拟相同模型的粗略的几何图形网格。您在从精细 LOD 切换到粗略 LOD 时之所以没有看到模型跳变,是因为在比例保持不变的情况下,网格覆盖的像素数量会随着距离变远而减少。当一个雕像位于 500 个世界单位外,只覆盖少数像素时,系统不需要渲染出 10 万个顶点网格。应充分利用现代商业游戏引擎提供的内置 LOD 系统。
您可以对 LOD 系统进行完整性检查,查看 RenderDoc 中的顶点数量,并与对象的像素覆盖范围进行匹配,确保看起来距离很远、靠近远剪裁平面的对象使用最低的相关 LOD。然后,您可以对这些问题追踪回到您的素材中,确保 LOD 使用适当的相机距离进行切换。
在下图的简单场景中,一个拥有非常复杂的几何结构的壁炉模型距离相机非常远:
HLOD(分层细节级别)系统是 LOD 系统的扩展,有助于节省对远方大规模对象的绘制调用。下面我们用例子来解释这一概念。
假设您正在尝试渲染一座大型城市,城市里有很多街区,其中每个对象都要用对应的单独绘制调来渲染,会怎样?即使遥远的街区包含构成完整城市街区的每个网格的最低 LOD,仅仅逐个绘制低 LOD 也会需要大量的绘制调用。借助 HLOD 系统,您可以将所有最低 LOD 网格合并成远处的离线街区,然后让引擎使用单个绘制调用渲染整个街区。请注意,如果您拥有使用纹理图集或纹理数组查找的 Uber 布景着色器(如上文所述),您只需要合并网格并重新映射 UV。HLOD 系统的缺点在于,由于您为构成 HLOD 的所有低 LOD 复制几何图形,因此系统会使用更多内存。请务必确保拥有足够的内存。
引擎会使用几种不同类型的绘制调用/剔除系统,通过跳过不必要的绘制调用,确保游戏尽可能高效地渲染。本节将介绍主要的类型。
视锥体剔除是指跳过落在相机视锥体外的绘制调用。如果对象位于视锥体外,它将不会投射到屏幕空间中的任何像素,因此可以安全地放弃绘制调用,以减少处理几何图形的 GPU 利用和无缘无故设置绘制调用的 CPU 利用。
开发者可以设置几个参数来确定视锥体大小(近/远剪裁平面 + 视野),但在 Meta Quest 上,Meta XR 插件会自动管理视锥体参数,以实现舒适的 VR 渲染。使用 HMD 插件提供的默认设置来保持舒适度。
在 RenderDoc 中,您可以使用网格查看器选项卡查看输入网格和生成的边界体积。您还可以选择纵切输出版块,查看网格如何投射到屏幕空间。
绘制距离剔除是指跳过绘制离相机一定距离的对象。如果一个对象比上文的视锥体剔除部分中提到的远剪裁平面距离更远,引擎会自动执行此操作。但如果某些物体较小,位于相机视锥体内,且仅覆盖几个像素,也可以避免发出绘制调用。跳过这些在视觉效果上不重要的绘制调用,可以节省主要/渲染线程的 CPU 时间,并无需处理这些对象的几何图形。如果我们回到 LOD 部分的炉灶示例,我们可以做得更好。只要炉灶的距离很远,对于游戏布景来说并不重要,我们可以禁用网格:
这是发生在相机视锥体内的剔除步骤。遮挡剔除背后的想法是跳过完全被另一个网格遮挡的网格的绘制调用。这种情况的一个常见例子是,假设您站在树林中的小木屋外,小木屋没有窗户,门也关着。在这种情况下,没有必要渲染小木屋内的任何对象。在开始设置渲染线程之前,您可以在 CPU 上进行一些计算,确定哪些对象被其他对象遮挡了。这将节省 GPU 的额外绘制调用和几何图形处理时间。遮挡剔除在渲染队列部分中也被提及,因为它们具有一定的相关性。两者之间的主要区别是,z-rejection 仍会处理几何图形并为网格添加绘制调用(省去了执行片段着色器的被遮挡像素),而遮挡剔除将跳过整个被遮挡的网格(绘制调用准备、几何图形处理以及所有片段着色器操作)。
在 RenderDoc 中,您可以看到帧内所有的渲染对象,并使用常识来确定实现遮挡剔除技术是否有助于您的游戏。下方的截图中包含最终完成的渲染目标,以及 RenderDoc 在帧渲染完成率约为 20% 时追踪绘制调用的截图。在第二张截图中,您可以清楚地看到小木屋内部有许多不需要绘制的物体被渲染了出来。
CPU 上的遮挡剔除算法计算确实需要处理能力来确定被遮挡的网格,但对于某些游戏类型和情况来说,这是值得的。有许多技术可供选择,Unity 和 Unreal 引擎都提供内置解决方案,可用于测试时间和效率。在某些情况下,有的技术比其他技术效果更好,因此在选择使用或创建哪项技术时,请务必考虑您的场景布局或游戏风格。
在 Unreal Engine 中,Meta XR 插件的项目设置工具默认启用了遮挡剔除 (r.AllowOcclusionQueries=True)。Unreal 的内置遮挡剔除系统使用 TaskGraph 进行多线程处理,充分利用了 Meta Quest 上的多核 CPU。验证项目设置中是否启用了遮挡查询,并检查 RenderDoc 中的绘制调用次数,确认是否正在剔除遮挡的网格。
为了确保上文提及的每个剔除系统尽可能有效,请确保批次中没有稀疏的几何图形区域,并且批次中的几何图形范围不会过大。如果您将具有很高几何细节的网格合并到一个集中区域,但有几个长细的网格突出来,这将使该批次生成的边界体积非常大,从而导致剔除算法中有大量误报。结果将导致该批次中的所有几何图形在许多情况下处理的都是零到极小的像素覆盖范围。确保网格批次周围生成的边界体积尽可能集中。问一问自己,如果我必须将这个巨大的网格装在玻璃盒子内,会有多少空位?确保空位尽可能地少,必要时将其分成独立的批次。
下图并不是极端的示例(没有包含很多几何图形),但它的确展示了如何查看提交的批次以及如何将它们拆分。图片展示了单批次绘制。假设玩家站在(世界空间的)边界体积中间,则应分成批次 1 和批次 2,以便省去几何图形投影的成本。如果边界体积群中有非常密集的对象,这一操作会更加重要:
有时候,当您在材质上使用透明混合状态时,您需要完全淡出对象。
有时,当对象完全淡出 (Alpha = 0) 后,开发者会忘记隐藏网格组件。这可能会产生轻度(淡出小型可消耗对象)到重度(淡入并淡出全屏渐晕效果)的成本影响。如果您在完全淡出对象后仍显示网格组件,您可能会认为引擎将在幕后自动跳过绘制调用,但大多数情况下并非如此,因为引擎并不了解您的意图。也就是说,绘制调用将走完整个图形管线,而不会更新任何像素,这就完全浪费了 CPU 上的绘制调用和 GPU 上的所有渲染工作。如果 Alpha = 0,部分像素着色器会提前进行条件检查,而您仍在浪费宝贵的 GPU 时间来处理几何图形。
简而言之,在帧中寻找对最终颜色缓冲区没有作用的绘制调用,并在引擎上追踪这些绘制调用。添加一个条件检查,以便在 Alpha 值达到 0 时调用 Blueprint 或 C++ 代码中的网格组件上的 SetVisibility(false) 或 SetHiddenInGame(true)。
以下截图展示了使用透明材质的长方体。通过漫反射纹理 Alpha 通道调低 Alpha 值来淡出对象。
在 RenderDoc 中,完全透明的对象仍在渲染,但由于 Alpha 值为 0,没有输出任何颜色变化。
在 Unreal Engine 中,确保将完全透明对象的网格组件可见度设置为 false(通过 Blueprints 或 C++ 中的 SetVisibility(false) 或 SetHiddenInGame(true))。
如果您发现渲染伪影,您可以找到引起该问题的绘制调用,并缩小搜索范围。RenderDoc 的 .rdc 文件的另一个好处是,您可以在与捕捉该文件的设备共享相同的 gfx 驱动程序版本的 Meta Quest 上重新运行该文件。如果您想让 QA 寻找性能热点,并将捕捉的信息传递给负责图形和性能的热点,这将很有帮助。