
1. 项目概述从辐射场到实时渲染的跨越最近在社区里看到不少朋友在讨论UE5里集成高斯泼溅3D Gaussian Splatting 简称3DGS的事儿尤其是随着一些开源插件和论文代码的放出大家从“看个热闹”逐渐转向了“想自己动手试试”。我花了大概两个月时间从零开始把一个纯学术论文里的3DGS C实现一步步整合、优化并最终在Unreal Engine 5里跑了起来实现了实时、高保真的场景渲染。这个过程踩的坑、绕的弯以及最终让帧率稳定在60fps以上的那些“魔法”调整我觉得很有必要拿出来和大家聊聊。简单来说3D高斯泼溅是一种用于新颖视图合成的技术。它不像传统三维重建那样输出网格Mesh而是用一大堆带有特定属性位置、颜色、透明度、协方差矩阵决定的形状的“高斯椭球”来表征一个场景。渲染时这些椭球像一个个半透明的“颜料点”被“泼溅”到屏幕上通过从后往前的排序和阿尔法混合最终合成出任意角度的图像。它的核心优势在于对于复杂场景尤其是那些有大量植物、毛发等难以用网格表达的细节它能以相对较低的数据量实现远超传统网格贴图方式的视觉保真度并且天生适合GPU并行计算为实时化提供了可能。那么为什么我们要费劲把它弄进UE5原因很直接生产管线融合与性能潜力挖掘。在影视动画、虚拟制作、数字孪生等领域我们常常需要将无人机扫描或相机阵列拍摄的真实场景无缝整合到虚幻引擎的虚拟制片流程中。传统的摄影测量网格动辄数千万面LOD管理复杂内存和显存压力巨大。而3DGS数据量小、视觉质量高如果能稳定集成就意味着我们可以在引擎内直接驱动这些超高精度的“点云场景”与虚拟角色、特效进行实时交互。此外UE5本身的Nanite、Lumen等系统主要针对网格优化3DGS作为一种截然不同的表达方式或许能打开另一扇高性能渲染的窗。这篇文章我会从一个实践者的角度带你走通“在UE5中实现高效3DGS渲染”的全流程。我们不会停留在理论介绍而是深入到插件集成、数据转换、渲染管线定制、性能瓶颈分析与优化等每一个实战环节。无论你是图形程序员、技术美术还是对前沿渲染技术充满好奇的开发者相信都能从中找到可以直接复用的代码片段和调优思路。2. 核心原理与UE5集成架构解析在动手敲代码之前我们必须先吃透3DGS的原理并想清楚它在UE5的渲染框架里应该如何“安家”。这决定了后续所有工作的效率和最终效果的天花板。2.1 3D高斯泼溅技术内核拆解一个3D高斯“泼溅点”的核心属性可以用一个结构体来定义这几乎是所有实现的起点struct GaussianPoint { float3 position; // 中心位置 (x, y, z) float3 color; // 球谐函数系数或RGB颜色 float opacity; // 不透明度 (alpha) float3 scale; // 各轴向缩放决定椭球大小 float4 rotation; // 四元数决定椭球朝向 // 协方差矩阵 Σ 可由 scale 和 rotation 推导得出 };渲染的关键步骤是泼溅Splatting将每个3D高斯椭球投影到2D屏幕空间形成一个2D高斯分布。这个过程涉及视图变换、投影变换以及计算投影后2D高斯的协方差矩阵。随后我们需要对这些投影后的2D高斯进行从后往前Back-to-Front的深度排序因为最终的像素颜色是通过阿尔法混合叠加而成的。注意这里的排序是性能瓶颈之一。一个中等复杂度的场景可能包含数十万甚至上百万个高斯点每一帧都对所有点进行全局排序是不现实的。实践中通常采用基于图块Tile-Based的近似排序或启发式方法。在UE5中集成我们面临几个架构选择纯Compute Shader路径在Compute Shader中完成所有点的变换、排序和光栅化将结果写入一个Render Target再通过全屏Pass合成到场景。这条路最灵活性能潜力最高但开发难度也最大需要自己管理整个光栅化流程。Geometry Shader / Mesh Shader路径将每个高斯点实例化为一个始终面向相机Billboard的四边形Quad在Shader中根据其3D属性动态计算每个像素的贡献。这条路利用了UE5现有的几何管线实现相对简单但可能会受限于几何发射Geometry Emission的性能和Overdraw。自定义PrimitiveComponent路径继承UPrimitiveComponent在FPrimitiveSceneProxy中提交自定义的顶点/索引缓冲区本质上还是生成代理几何体。这条路与引擎渲染流程集成度最深可以享受视锥剔除、遮挡查询等引擎优化但定制渲染算法的空间较小。经过权衡我选择了路径2的变体结合Mesh Shader与Compute Shader的混合路径。原因如下纯Compute路径虽然高效但需要完整实现一套软光栅化逻辑复杂且难以调试。而传统的Geometry Shader在移动端支持不佳且性能开销较大。UE5.1对Mesh Shader有了更好的支持我们可以用Mesh Shader来高效地生成和管理这些代理几何体每个高斯点对应一个微小的网格而将每个点的颜色、透明度、形变等核心计算放在Pixel Shader以及前置的Compute Shader中进行。这样既利用了硬件的新特性又保持了算法的核心逻辑。2.2 UE5渲染管线适配与插件设计我们的目标是创建一个UE5插件它能够导入标准的.ply格式这是3DGS社区常用的输出格式例如来自gaussian-splatting原始项目的3DGS数据并在场景中渲染出来。插件核心模块设计FGaussianSplattingFactory 继承UFactory负责导入.ply文件解析其中的位置、颜色球谐系数、透明度、缩放、旋转数据并创建我们的主要资产类UGaussianSplattingAsset。UGaussianSplattingAssetUObject派生类作为数据容器。它存储所有高斯点的原始数据并负责在加载时将这些数据上传到GPU友好的缓冲区如Structured Buffer。AGaussianSplattingActor 继承AActor作为可放置的场景实体。它持有一个UGaussianSplattingAsset引用并包含一个UGaussianSplattingComponent。UGaussianSplattingComponent 继承UPrimitiveComponent。这是核心它负责创建对应的FGaussianSplattingSceneProxy。管理渲染相关的参数如全局缩放、亮度调节。处理与引擎的交互如边界框计算。FGaussianSplattingSceneProxy 继承FPrimitiveSceneProxy。渲染线程的代表。它的GetDynamicMeshElements方法将被调用在这里我们提交自定义的FMeshBatch。关键点在于我们不是提交真实的几何体而是提交一个“调用命令”告诉渲染器“请用我指定的Shader和参数来绘制这些点”。自定义Shader 一系列.usf文件包括GaussianSplatting.usf 包含Vertex、Mesh、Pixel Shader。Vertex Shader可能只做简单的传递Mesh Shader负责根据点数据生成四边形Pixel Shader是核心根据该像素对应的所有高斯点的贡献进行混合计算。GaussianSplattingSorting.usf 一个Compute Shader用于在渲染前对可见区域的高斯点进行基于深度的分块排序。数据流与渲染流程用户导入.ply文件创建UGaussianSplattingAsset。插件解析文件将数据存储在TArrayFGaussianPoint中并调用FRenderResource接口创建Structured Buffer将数据上传至GPU。用户将AGaussianSplattingActor拖入场景。UGaussianSplattingComponent被创建。每帧引擎询问UGaussianSplattingComponent的SceneProxy需要渲染什么。FGaussianSplattingSceneProxy准备一个FMeshBatch设置其VertexFactory使用自定义的FGaussianSplattingVertexFactoryMaterialRenderProxy指向我们特制的材质并将GPU Buffer的SRVShader Resource View作为参数传入。引擎渲染线程执行到该FMeshBatch时会调用我们绑定的Shader。在Shader中首先可能执行一个Compute PassGaussianSplattingSorting对当前视图下的高斯点进行排序输出排序后的索引列表。Mesh Shader根据排序后的索引读取每个高斯点的数据为其生成一个始终面向相机的四边形两个三角形。这个四边形的屏幕空间大小由高斯点的3D缩放和与相机的距离动态计算得出。Pixel Shader对每个像素遍历所有覆盖该像素的四边形通过提前的深度测试和保守光栅化或查询排序列表根据该像素到四边形中心对应高斯点中心投影的距离计算该高斯点的2D高斯权重再结合其颜色和不透明度进行加权混合。最终输出混合后的颜色。这个架构将大部分计算密集型工作留给了GPUCPU端主要是数据管理和渲染命令提交符合现代图形引擎的设计哲学。3. 从数据到画面完整实现流程拆解理论架构清晰后我们进入实战环节。我将以创建一个名为“GaussianSplattingForUE5”的插件为例分步说明。3.1 步骤一创建插件与基础资产类首先在UE5引擎目录下通过命令行或编辑器创建插件模板。# 在UE5工程目录或引擎插件目录下 .\Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.exe -Plugin GaussianSplattingForUE5或者直接在编辑器内创建“空白插件”。创建后得到GaussianSplattingForUE5目录。创建核心数据类FGaussianPoint和UGaussianSplattingAsset在插件的Public和Private目录下创建头文件和源文件。// GaussianPoint.h #pragma once #include “CoreMinimal.h” #include “GaussianPoint.generated.h” USTRUCT(BlueprintType) struct FGaussianPoint { GENERATED_BODY() FVector Position; FVector Color; // 初始为RGB后续可能扩展为球谐系数 float Opacity; FVector Scale; FQuat Rotation; // 可以添加其他派生或优化用的属性 };UGaussianSplattingAsset需要继承UObject并实现FRenderResource接口以便管理GPU资源。它包含一个TArrayFGaussianPoint用于CPU端数据以及FStructuredBufferRHIRef和FShaderResourceViewRHIRef用于GPU端数据。3.2 步骤二实现PLY文件导入器.ply文件有ASCII和二进制格式3DGS输出的一般是二进制小端序。我们需要编写解析器。UE5提供了fopen等标准C库支持但更工程化的做法是使用IFileHandle接口。在FGaussianSplattingFactory::FactoryCreateBinary方法中我们需要读取文件头确认格式ply,format binary_little_endian 1.0。解析element vertex count知道有多少个点。解析每个property确认其顺序和类型float x, y, z, nx, ny, nz, f_dc_0, f_dc_1, f_dc_2, opacity, scale_0, scale_1, scale_2, rot_0, rot_1, rot_2, rot_3。注意原始数据中的scale和rotation经过激活函数处理exp和归一化我们需要在解析时或Shader中做相应处理。按顺序将二进制数据读入FGaussianPoint数组。实操心得这里最容易出错的是字节对齐和对齐。确保你的FGaussianPoint结构体在C和Shader中的内存布局完全一致。一个稳妥的做法是在Shader中使用float4数组来存储位置和颜色用单独的float4数组存储缩放和旋转将四元数存在float4中而不是试图用一个复杂的结构体匹配。解析时注意f_dc_0/1/2是球谐函数的0阶系数代表了基础颜色我们初期可以只使用这个。3.3 步骤三构建渲染组件与场景代理UGaussianSplattingComponent的核心是重写CreateSceneProxy方法返回我们自定义的FGaussianSplattingSceneProxy。FGaussianSplattingSceneProxy的构造函数需要从Component获取UGaussianSplattingAsset的GPU Buffer SRV。它的GetDynamicMeshElements方法是关键virtual void GetDynamicMeshElements(const FSceneView* View, int32 ViewIndex, FMeshElementCollector Collector) const override { // 设置自定义的VertexFactory FGaussianSplattingVertexFactory* VertexFactory Collector.AllocateOneFrameResourceFGaussianSplattingVertexFactory(); VertexFactory-Init(GaussianDataSRV); // 传入数据缓冲区的SRV FMeshBatch Mesh Collector.AllocateMesh(); Mesh.VertexFactory VertexFactory; Mesh.MaterialRenderProxy GaussianMaterial-GetRenderProxy(); // ... 设置其他MeshBatch参数如PrimitiveUniformBuffer Mesh.Type PT_TriangleList; Mesh.DepthPriorityGroup SDPG_World; Mesh.bCanApplyViewModeOverrides false; // 我们实际上没有顶点缓冲区通过Mesh Draw Command来绘制实例 FMeshBatchElement BatchElement Mesh.Elements[0]; BatchElement.IndexBuffer nullptr; BatchElement.FirstIndex 0; BatchElement.NumPrimitives 0; // 实际图元数由Shader通过实例数计算 BatchElement.MinVertexIndex 0; BatchElement.MaxVertexIndex 0; BatchElement.NumInstances TotalPointCount; // 告诉引擎我们要绘制这么多“实例” BatchElement.InstanceBuffer nullptr; Collector.AddMesh(ViewIndex, Mesh); }这里我们玩了一个“花招”我们提交了一个NumPrimitives为0的MeshBatch但设置了NumInstances为点数。这意味着我们不会触发传统的顶点着色器流程。我们需要一个自定义的Mesh Shader来替代这个流程。这需要在FMaterial和Shader中做特殊设置。3.4 步骤四编写核心渲染Shader这是最复杂的部分。我们需要创建.usf文件并修改引擎的Shader编译配置通过Shader目录下的.usf文件和C声明。1. 声明Shader类在C中我们需要声明从FMeshMaterialShader派生的类例如FGaussianSplattingMS(Mesh Shader),FGaussianSplattingPS(Pixel Shader)并重写ModifyCompilationEnvironment来设置宏定义。2. 编写Mesh Shader (GaussianSplatting.ms.usf):这个Shader的输入是NumInstances点数。它的任务是对于每一个实例即每一个高斯点从Structured Buffer中读取该点的属性。根据当前视图矩阵和投影矩阵计算该点在屏幕空间的包围四边形。输出这个四边形的顶点4个和索引6个两个三角形。 由于输出顶点数量是动态的4 * 实例数我们需要使用Mesh Shoder的[numthreads(X, Y, Z)]和DispatchMesh来分配线程组。每个线程组处理固定数量的点并分配输出所需的顶点和索引内存。3. 编写Pixel Shader (GaussianSplatting.ps.usf):这是混合发生的地方。输入是Mesh Shader生成的四边形和对应的点ID。对于每个像素我们需要知道有哪些高斯四边形覆盖了它。这可以通过保守光栅化(Conservative Rasterization)或更常见的基于图块的列表Per-Tile List来实现。基于图块的列表是更主流的高效做法在Mesh Shader或一个单独的Compute Shader中我们将每个高斯点根据其屏幕空间包围盒添加到它覆盖的所有图块如16x16像素的块的列表中。Pixel Shader中只需读取当前像素所在图块的列表遍历其中的高斯点进行计算。对于列表中的每个点计算该像素到该点投影中心的2D马氏距离使用投影后的2D协方差矩阵的逆代入2D高斯函数得到权重。权重乘以该点的颜色f_dc_0/1/2和不透明度经过sigmoid激活进行从前到后因为列表已按深度排序的阿尔法混合。// 伪代码 float4 finalColor float4(0, 0, 0, 0); for (int i 0; i tilePointCount; i) { int pointIdx tilePointList[i]; GaussianPoint point GaussianBuffer[pointIdx]; float2 pixelOffset (pixelCoord - point.projectedCenter) * point.invCov2D; float weight exp(-0.5 * dot(pixelOffset, pixelOffset)); float alpha weight * sigmoid(point.opacity); float3 pointColor // 从球谐系数计算初期直接用f_dc finalColor.rgb finalColor.rgb (1.0 - finalColor.a) * alpha * pointColor; finalColor.a finalColor.a (1.0 - finalColor.a) * alpha; if (finalColor.a 0.99) break; // 提前终止优化 }4. 材质与Shader绑定我们需要创建一个UMaterial或UMaterialInstanceConstant将其Material Domain设置为MD_SurfaceBlend Mode设置为BLEND_TranslucentShading Model设置为Unlit。然后在C代码中通过FMaterial的渲染代理将我们自定义的Shader映射到这个材质上。3.5 步骤五性能优化关键策略当百万级别的点数据加载后最初的版本可能只有个位数的FPS。以下是几个立竿见影的优化方向1. 视锥剔除与细节层次LODCPU视锥剔除在FGaussianSplattingSceneProxy的GetViewRelevance和提交渲染时根据点的整体包围盒进行粗略剔除。但这对于分布广泛的点云不够精细。GPU-Driven剔除在Compute Shader中根据每个点的世界位置和其最大半径由Scale决定进行精确的视锥剔除和遮挡剔除如果与引擎的HZB系统结合。将不可见的点标记出来后续Mesh Shader跳过它们。LOD这是3DGS实时化的核心。可以为数据预计算多级LOD。一种简单方法是使用空间哈希如八叉树对点进行聚类在距离远时渲染聚类后的代表点其属性为聚类内点的均值。更高级的方法则需在原始训练/优化阶段就生成层次化表示。2. 排序优化全局排序O(N log N)不可行。采用分块排序Tile-Based Sorting将屏幕分割成多个图块如32x32。在Compute Shader中为每个图块维护一个深度排序的点索引列表。每个高斯点被添加到它覆盖的所有图块的列表中使用原子操作。在每个图块内部使用更高效的排序算法如双调排序Bitonic Sort适合GPU对少量点进行排序。Pixel Shader直接读取排序好的图块列表进行混合。3. 着色计算优化球谐函数简化原始3DGS使用3阶球谐函数16个系数来建模视角相关的颜色。在实时渲染中可以降阶到2阶9个系数甚至1阶4个系数即RGB一个常数大幅减少带宽和计算量。在移动端甚至可以直接使用视角无关的RGB颜色。近似与查表2D高斯函数exp(-0.5 * x^T Σ^-1 x)的计算涉及矩阵求逆和乘法。可以预先计算每个点在视图空间下的2D协方差矩阵及其逆并存储起来。甚至可以将高斯函数预计算成一张小的纹理进行查找。4. 渲染状态与带宽优化深度测试虽然高斯点是半透明的但我们可以启用深度写入并设置合理的深度比较函数如LESS让靠前的点遮挡靠后的点减少Pixel Shader的过度绘制。但这需要谨慎处理因为高斯点本身是体积化的简单的深度测试可能导致错误的结果。一种折中是使用“深度剥离Depth Peeling”的变体但开销较大。数据压缩FGaussianPoint中的Scale和Rotation可以使用更小的数据类型如half或甚至量化到uint16存储在Shader中解码。颜色通道也可以使用UNORM8格式。实例化渲染我们虽然使用了“实例”的概念但通过Mesh Shader自定义了几何体生成。确保整个Draw Call是一次性提交的避免每点一个Draw Call的灾难性性能。4. 实战调试与疑难问题排查集成过程中你会遇到各种光怪陆离的问题。这里记录几个典型问题及其解决方案。4.1 问题一渲染结果全黑或闪烁可能原因1GPU Buffer数据未正确上传或绑定。排查在RenderDoc或Nsight中捕获一帧检查你自定义Shader阶段Compute或Mesh Shader的输入缓冲区Structured Buffer是否被正确绑定其中的数据是否与CPU端一致。检查Buffer的创建标志BUF_Static等和SRV的格式描述。解决确保在UGaussianSplattingAsset的InitRHI()中正确创建和填充Buffer。使用FRHIResourceCreateInfo并指定数据指针。在SceneProxy中将SRV通过FMeshDrawShaderBindings设置到正确的Slot上。可能原因2Shader编译错误或未生效。排查在项目设置的“着色器”部分查看是否有编译错误或警告。在引擎输出日志中搜索“Shader”。在RenderDoc中查看最终绘制调用使用的Pixel Shader是否是你自定义的。解决仔细检查.usf文件的语法。确保C中声明的Shader类与.usf文件中的入口函数名匹配。确保材质真正使用了你的Shader有时需要设置材质的MaterialDomain为MD_PostProcess或自定义的MD_User域并在C中特殊处理。可能原因3深度排序错误导致混合异常。现象画面闪烁或某些本应被遮挡的部分透了出来。排查在Pixel Shader中输出深度值或点ID到颜色可视化查看排序结果。检查排序Compute Shader中的深度计算是视图空间Z还是齐次裁剪空间的W是否正确排序算法特别是原子操作和线程同步是否有竞态条件。解决确保用于排序的深度值在所有点上计算方式一致且线性。对于基于图块的排序确保每个点被添加到所有覆盖图块的逻辑正确可以使用保守的包围盒扩大一像素来避免缝隙。4.2 问题二性能低下GPU占用率异常可能原因1Overdraw过度绘制严重。现象Pixel Shader占用时间极高尤其是场景中心区域。排查使用Unreal Insights的GPU Trace功能查看GaussianSplatting.ps阶段的耗时。在Shader中输出每个像素处理的高斯点数量如果某些像素数量巨大50说明过度绘制严重。解决实施更激进的剔除如背面剔除、小投影面积剔除。在Pixel Shader混合循环中加入“提前终止”判断当累积不透明度alpha接近1.0时如0.99立即跳出循环停止处理后续点。优化图块列表确保每个列表中的点都是真正对该图块有显著贡献的。可能原因2Mesh Shader或Compute Shader线程效率低。现象Mesh Shader或排序Compute Shader阶段耗时很长。排查检查Shader中的分支和内存访问模式。是否有很多线程因为条件判断而分化Thread Divergence访问Structured Buffer是否是合并访问Coalesced Access解决尽量让同一个Warp32个线程内的线程执行相同的路径。对数据进行预处理或重排使相邻线程访问相邻的内存地址。在排序算法中选择适合GPU的并行排序算法如双调排序或基数排序。可能原因3数据带宽成为瓶颈。现象GPU核心利用率不高但帧时间依然很长。排查在Profiler中查看L2 Cache命中率和显存带宽使用情况。检查你的FGaussianPoint结构体在GPU上的大小是否包含大量对齐填充。解决压缩数据。将position和color从float3打包到float4中减少对齐浪费。将scale和rotation存储为half4。考虑使用纹理Texture2D而不是Structured Buffer来存储静态点数据利用纹理缓存。4.3 问题三与UE5后期处理、抗锯齿等效果冲突现象高斯泼溅的场景在开启Temporal AATSR后出现重影或模糊或与屏幕空间反射、全局光照等效果不兼容。原因我们的渲染通道是自定义的没有为UE5的延迟渲染管线Deferred Rendering提供完整的GBuffer数据如世界法线、粗糙度、金属度等。TSR等后处理需要历史帧数据来进行时域滤波而我们的自定义渲染目标可能没有被正确地纳入历史缓冲管理。解决渲染路径选择将我们的渲染整合到延迟渲染管线中非常复杂。一个更简单有效的方案是使用前向渲染Forward Rendering路径。在UE5中可以通过将材质的Shading Model设为Unlit并确保渲染在透明通道Translucent Pass进行这样大部分后处理如Tonemapping, Bloom仍然有效但TSR、SSR等可能效果不佳或需要特殊处理。自定义深度/模板如果需要与场景中的网格交互如遮挡可以渲染一张自定义的深度图。在Mesh Shader中将高斯点的代理几何体的深度写入一个自定义的深度渲染目标。但这张深度图是“不连续”的由许多小四边形组成使用时需注意。与Lumen兼容要让Lumen全局光照照亮3DGS场景需要提供有意义的场景距离场SDF。可以从3DGS数据近似生成一个低分辨率的SDF但这又是一个独立的研究课题。目前实用的做法是将3DGS作为主要视觉而光照仍依赖于传统的Lightmap或动态光源对代理几何体的照射效果有限。4.4 常见问题速查表问题现象可能原因排查工具解决思路画面全黑1. GPU数据未绑定2. Shader编译失败3. 渲染状态错误如深度测试遮挡RenderDoc, Unreal Shader Log检查SRV绑定查看Shader编译日志禁用深度写入测试画面闪烁/错乱1. 深度排序错误2. 线程同步问题Compute Shader3. 缓冲区越界RenderDoc可视化深度/IDNsight Compute调试排序Shader检查原子操作添加边界检查帧率极低1. 过度绘制严重2. 排序或剔除算法效率低3. 单个Draw Call顶点数超限Unreal Insights GPU Trace, RGP实现提前终止混合优化剔除拆分大规模Draw Call内存/显存占用过高1. 点数据未压缩2. 中间缓冲区过多3. 多级LOD数据常驻Windows任务管理器GPU-Z量化存储数据及时释放中间缓冲流式加载LOD与后期效果冲突1. 渲染目标格式不匹配2. 缺少运动矢量Motion Vector3. 非标准GBufferRenderDoc查看RT格式检查后处理输入输出运动矢量尝试在前向透明通道渲染禁用部分后处理最后我想分享一点在调试过程中的深刻体会可视化调试是你最好的朋友。不要只盯着最终输出颜色。在Shader中大胆地将中间变量输出到颜色通道比如将点ID映射为彩虹色、将深度值可视化、将每个像素处理的高斯点数量映射为灰度值、甚至将2D协方差矩阵的特征值画出来。这些“奇怪”的调试视图能瞬间帮你定位问题是出在数据、算法还是渲染流程上。在UE5中你可以通过自定义的ScreenPass在后期处理阶段叠加这些调试信息或者直接修改Pixel Shader的输出进行快速验证。这个过程虽然繁琐但每一次成功的调试都让你对这项技术和引擎的理解更深一层。