UE5.7 FDeferredShadingSceneRenderer::Render 函数学习 之 FSceneRenderer::RenderVelocities
总结作用速度缓冲区Velocity 渲染用于 TAA、运动模糊、延迟抗锯齿。区分不透明物体速度、半透明物体速度两个分支。const bool bIsTranslucentClippedDepthPass VelocityPass EVelocityPass::TranslucentClippedDepth; const bool bSupportsTranslucentClippedDepth SupportsTranslucentClippedDepth(ShaderPlatform); const bool bIsTranslucentClippedDepthEnabled CVarVelocityOutputTranslucentClippedDepthEnabled.GetValueOnRenderThread() ! 0; if (bIsTranslucentClippedDepthPass (!bSupportsTranslucentClippedDepth || !bIsTranslucentClippedDepthEnabled)) { return; }判断当前通道类型bIsTranslucentClippedDepthPass检查当前的VelocityPass是否等于枚举值TranslucentClippedDepth即是否为处理半透明裁剪物体的速度通道。检查平台支持bSupportsTranslucentClippedDepth通过SupportsTranslucentClippedDepth(ShaderPlatform)查询当前着色器平台如PC、主机、移动端是否支持这一特性。检查用户设置bIsTranslucentClippedDepthEnabled从控制台变量CVarVelocityOutputTranslucentClippedDepthEnabled读取运行时值判断用户是否启用了该功能非0为启用。条件返回如果当前是半透明裁剪深度通道但平台不支持或用户未启用则直接return跳过该通道的执行。这里的clip不是屏幕裁剪而是材质的半透明或者mask为什么需要专门的“速度通道”这就是你之前看到的代码要处理的核心问题。挑战对于普通的半透明物体渲染它们的速度信息本身就很复杂且性能开销大。而对于“半透明裁剪”物体因为它的形状是靠“裁剪”形成的边缘的像素在“透明”与“不透明”之间剧烈变化计算其运动速度会更加困难和不可靠容易产生视觉噪点或错误。解决方案因此引擎专门设计了一个名为TranslucentClippedDepth的速度通道来处理这类物体。为了精确计算它们的速度这个通道会额外利用深度信息Depth来辅助判断问所以如果我半透明度为非0我的速度信息该怎么表达因为半透明物体一个是半透明物体本身的速度另一个是半透明物体后面的速度答Unreal Engine 的“单选题”策略对于真正的混合半透明Opacity ≠ 1UE 并不试图融合两者而是提供两种策略由开发者决定谁优先策略 A默认行为选背景——半透明物体“摆烂”大多数半透明材质默认不写入速度缓冲区也不写入主深度。此时速度缓冲里保留的是背后不透明物体的速度。后果TAA/运动模糊在处理这个像素时认为“画面运动等于背景速度”。因此背景是清晰的但移动的半透明物体如旋转的玻璃杯会因为重投影错位出现严重的拖尾、重影或抖动。这是UE里半透明物体动态画质差的常见原因。策略 B强制覆盖选前景——半透明物体“抢麦”开发者可以在材质中勾选Output Velocity或者通过 CVarr.Translucency.Velocity强制开启。此时半透明物体会在渲染通道中将自己的速度写入缓冲区暴力覆盖Overwrite掉背景速度。后果半透明物体本身变得清晰稳定运动模糊正确。但透过半透明物体看背景时TAA会使用前景的速度去卷绕背景像素导致背景出现扭曲、撕裂或错误的模糊。RDG_CSV_STAT_EXCLUSIVE_SCOPE(GraphBuilder, RenderVelocities); SCOPED_NAMED_EVENT(FSceneRenderer_RenderVelocities, FColor::Emerald); SCOPE_CYCLE_COUNTER(STAT_RenderVelocities);后面两个之前的章节已经多次讲过这里就不再赘述重点讲第一个作用面向自动化性能测试CSV 统计的专属计时器。RDG指渲染依赖图Render Dependency Graph这个宏会将计时数据嵌入到 RDG 的执行流中。CSV指逗号分隔值Comma-Separated Values引擎会将这个作用域的耗时输出到.csv日志文件中供 QA 或性能测试团队离线分析。EXCLUSIVE表示独占统计即这个计时器只计算RenderVelocities本身的耗时不包含其内部调用的子模块时间防止重复累加。用途帮助开发者在长时间运行的游戏录像中精准定位这个函数是否在某些地图场景下突然变慢。uint32 bNeedsClearMask HasBeenProduced(SceneTextures.Velocity) ? 0 : ((1u GNumExplicitGPUsForRendering) - 1);核心逻辑查重与跳过HasBeenProduced(SceneTextures.Velocity)是 RDG 系统的一个查询接口返回true意味着在当前帧的渲染图谱中在此之前已经有其他 Pass比如不透明物体渲染通道向速度纹理写入过有效数据了。此时bNeedsClearMask 0无清除掩码。不执行清除操作直接保留现有数据。这是一个巨大的性能优化避免了重复清空显存节省带宽。返回false说明当前帧还没人碰过这张速度纹理里面残留的是上一帧的“垃圾数据”或未初始化的显存脏值。此时必须执行清除并将所有像素的速度值归零代表“静止”防止 TAA 或运动模糊读取到上一帧的错乱数据导致画面撕裂。RDG_EVENT_SCOPE_STAT(GraphBuilder, RenderVelocities, RenderVelocities(%s),GetVelocityPassName(VelocityPass)); RDG_GPU_STAT_SCOPE(GraphBuilder, RenderVelocities);RDG_EVENT_SCOPE_STAT(...)作用在 GPU 命令流中插入“命名标签”并将这个标签挂钩到 CPU 统计系统。给 GPU “贴标签”当使用RenderDoc、PIX或Unreal InsightsGPU 追踪抓取帧时这个宏会在 GPU 时间线上生成一个名为RenderVelocities(具体通道名)的彩色/分层区块。比如当VelocityPass是TranslucentClippedDepth时你在性能分析工具里会看到一个醒目的长条写着RenderVelocities(TranslucentClippedDepth)。动态命名GetVelocityPassName(VelocityPass)让你能一目了然地区分当前执行的是“普通速度通道”还是“半透明裁剪速度通道”方便技美针对性优化。CPU 关联虽然它贴的是 GPU 标签但它同时将这段 GPU 工作挂载到了 CPU 的RenderVelocities统计组下确保在stat命令中能关联起来。2.RDG_GPU_STAT_SCOPE(GraphBuilder, RenderVelocities)作用实际开启 GPU 硬件计时器精确测量这段 GPU 代码的执行时长。开启 GPU 查询这个宏会向 GPU 命令队列插入一对“开始/结束”时间戳查询Timestamp Queries。GPU 内部有专门的硬件计数器来记录这些时间点。填充stat GPU数据当你在控制台输入stat GPU或stat RHI时屏幕上显示的“RenderVelocities”那行的毫秒数ms主要就是由这个宏负责累加计算出来的。独立于 CPUCPU 发命令可能只花 0.1ms但 GPU 画这些速度可能花掉 2ms因为要等待纹理采样和显存读写。这个宏专门捕捉这 2ms 的 GPU 墙钟时间。const EMeshPass::Type MeshPass GetMeshPassFromVelocityPass(VelocityPass); const bool bIsOpaquePass VelocityPass EVelocityPass::Opaque; FExclusiveDepthStencil ExclusiveDepthStencil (bIsOpaquePass !(Scene-EarlyZPassMode DDM_AllOpaqueNoVelocity)) ? FExclusiveDepthStencil::DepthRead_StencilWrite : FExclusiveDepthStencil::DepthWrite_StencilWrite; ExclusiveDepthStencil bIsTranslucentClippedDepthPass ? FExclusiveDepthStencil::DepthRead_StencilNop : ExclusiveDepthStencil;MeshPass GetMeshPassFromVelocityPass(VelocityPass)将速度通道枚举转换为具体的网格渲染通道类型如Opaque、Masked等。bIsOpaquePass标记当前是不是普通不透明物体的速度通道。场景 A常见情况bIsOpaquePass true且EarlyZPassMode不是DDM_AllOpaqueNoVelocity。这意味着引擎在“不透明物体渲染”阶段已经提前运行过 Early-Z 通道深度缓冲Depth Buffer里已经有了有效的深度数据。因此速度通道只需要读取Read深度值用于计算像素的世界位置和速度无需再写一遍但为了后续的遮挡剔除或标记需要写入Write模板Stencil。权限设为DepthRead_StencilWrite深度只读模板可写。场景 B罕见/特殊如果不透明物体没有提前写入深度EarlyZPassMode DDM_AllOpaqueNoVelocity意味着当前深度缓冲是无效的或空的。速度通道如果只读就会读到垃圾数据导致计算错误。所以速度通道必须同时写入深度Write和模板Write把深度值补上。权限设为DepthWrite_StencilWrite深度和模板均可写。bool bHasAnyPixelShaderMotionVectorWorldOffsetMaterials false; GetMotionVectorOutputFlag(InViews, MeshPass, bForceVelocity, bHasAnyPixelShaderMotionVectorWorldOffsetMaterials); const bool bSupportPixelShaderMotionVectorWorldOffset SupportsPixelShaderMotionVectorWorldOffset(ShaderPlatform) bIsOpaquePass bHasAnyPixelShaderMotionVectorWorldOffsetMaterials; //Only opaque pass supports per pixel override. FRDGTextureRef MotionVectorWorldOffsetTexture nullptr; if (bSupportPixelShaderMotionVectorWorldOffset) { MotionVectorWorldOffsetTexture GraphBuilder.CreateTexture(SceneTextures.Velocity-Desc,TEXT(MotionVectorWorldOffsetTexture)); AddClearRenderTargetPass(GraphBuilder, MotionVectorWorldOffsetTexture); }它要解决什么特殊问题通常情况下物体的运动速度是由CPU/顶点着色器计算物体顶点位置在前后帧的偏移量得出的比如角色骨骼动画、刚体位移。但有些材质效果无法通过顶点动画实现只能在像素着色器里扰动画面比如流动的水面波纹顶点没动但法线和UV在动。飘动的旗帜上的局部褶皱。带有滚动纹理的熔岩或能量护盾。如果只靠顶点算速度这些像素在TAA眼里就是“静止”的但画面内容却在剧烈变化会导致重影和撕裂。因此材质允许开发者直接在像素着色器里输出一个“额外偏移量”来修正速度。为什么要单独建一张纹理MotionVectorWorldOffsetTexture这是最关键的设计点主速度纹理SceneTextures.Velocity已经在当前帧的不透明速度通道中被写入了基础速度基于顶点计算。如果像素着色器偏移直接覆盖主纹理没有偏移的像素数据就会被清空导致大面积画面出错。因此引擎的策略是“分步合成”第一步当前代码判断场景里是否有材质开启了像素着色器偏移bHasAny...。如果有就新建一张独立的、全黑的临时纹理MotionVectorWorldOffsetTexture作为“增量累加器”。第二步在后面未贴出的合成Pass中引擎会读取主速度纹理 这张偏移纹理将两者相加得到最终速度再写回主纹理这个主纹理就是velocitypass最后渲染出来的速度场。AddClearRenderTargetPass的作用把这整张新纹理刷成纯黑0,0。这样后续像素着色器在计算时只在需要偏移的地方写入非零值不需要偏移的地方保持0相加后不影响主速度。相当于单独建立一个纹理然后清空清空过后计算屏幕中材质的像素偏移度(计算PS导致的速度)然后累加在这张新纹理做完后累加到主velocity图中velocitypass也是Pass它执行了vs才能获取到顶点速度像素着色器偏移 (Pixel Shader Offset)这才是你问题中真正关心的“像素级别”偏移。它发生在像素着色器阶段能够实现顶点着色器无法完成的、更精细的屏幕空间效果例如基于UV动画的流动效果如水流。基于屏幕空间坐标的扭曲效果如热浪。材质的颜色或亮度在移动但几何体本身静止可以理解为一种“光流”。这种情况会计算到临时创建的速度图两个概念的区别特性World Position Offset (WPO)像素着色器偏移你引用的那段描述执行阶段顶点着色器像素着色器本质直接移动模型的顶点世界坐标在屏幕空间或纹理空间进行采样偏移不改变几何体例子树叶摆动、顶点动画、物体变形UV 平移动画水流、热浪扭曲折射偏移、基于屏幕坐标的采样扰动产生运动向量会因为前后帧顶点位置不同不会因为几何体实际上没动只是贴图或光照在动“光流”效果渲染时是否进入 Velocity Pass是如果开了相关选项否它只是一般材质着色不写入速度缓冲for (int32 ViewIndex 0; ViewIndex InViews.Num(); ViewIndex) { FViewInfo View InViews[ViewIndex]; checkf(!(View.Family-EngineShowFlags.StereoMotionVectors PlatformSupportsOpenXRMotionVectors(View.GetShaderPlatform())), TEXT(Normal velocity rend遍历所有视图InViews是当前帧需要渲染的所有视图例如左眼、右眼、场景捕获等循环逐一检查。获取每个视图的配置View.Family-EngineShowFlags包含各种渲染功能开关。StereoMotionVectors是旧版立体渲染中的运动向量motion vector支持标志。检查平台能力PlatformSupportsOpenXRMotionVectors(View.GetShaderPlatform())判断当前 RHI/Shader 平台是否支持 OpenXR 的运动向量扩展。if (View.ShouldRenderView()) { const bool bHasAnyDraw HasAnyDraw(View.ParallelMeshDrawCommandPasses[MeshPass]); if (!bHasAnyDraw !bForceVelocity) { continue; }View.ShouldRenderView()检查该视图是否需要被实际渲染例如视图不可见、被完全遮挡、或为无效视角时可跳过。HasAnyDraw(View.ParallelMeshDrawCommandPasses[MeshPass])ParallelMeshDrawCommandPasses是预先收集好的、按 MeshPass 分类的绘制命令数组。MeshPass在这里应该是速度 Pass例如EMeshPass::Velocity。HasAnyDraw判断这个 Pass 中是否有任何需要绘制的可见网格体。bForceVelocity一个外部传入的布尔标志表示“即使没有任何可见的网格体绘制也必须强制输出速度纹理”。典型场景启用了TAA/DLSS/TSR等需要历史运动向量的特性即便是全屏后处理也需要一个“零速度”或前一帧运动向量来维持历史缓冲。某些平台或 VR 场景要求始终提供运动向量。Velocity Pass 并不是所有物体都会参与ParallelMeshDrawCommandPasses[MeshPass]里收集的是当前视图中需要输出运动向量的网格体绘制命令。物体能被收录需要同时满足多个条件物体本身需要写入运动向量只有被标记为“可能运动”或渲染设置需要输出速度的 Primitive 才会生成速度绘制命令。例如使用了World Position OffsetWPO或骨骼动画的动态物体。开启了物体运动向量Per-Object Motion Blur的静态网格体。场景中移动的粒子、贴花等。完全静态且从未移动的物体通常不会生成速度 Pass 命令。RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);RDG_GPU_MASK_SCOPE是 UE 的 RDGRender Dependency Graph提供的一个宏用于设置当前 RDG 构建过程中后续 Pass 的 GPU 掩码。离开这个作用域即宏生成的for循环结束或}结束后GPU 掩码会恢复为之前的值。GraphBuilder当前的 RDG 构建器所有 Pass 通过它添加到帧图中。View.GPUMask一个位掩码通常是FRHIGPUMask表示该视图应该被哪些 GPU 渲染。在单 GPU 系统上这个掩码通常是1仅 GPU 0。在多 GPU 渲染如分屏、VR 每眼不同 GPU、交叉 GPU 渲染时不同视图可能有不同的 GPUMask确保每个视图只在分配给的 GPU 上执行相应的工作。RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask); const bool bIsParallelVelocity FVelocityRendering::IsParallelVelocity(ShaderPlatform); // Clear velocity render target explicitly when velocity rendering in parallel or no draw but force to. // Avoid adding a separate clear pass in non parallel rendering. const bool bExplicitlyClearVelocity (bNeedsClearMask View.GPUMask.GetNative()) (bIsParallelVelocity || (bForceVelocity !bHasAnyDraw)); if (bExplicitlyClearVelocity) { AddClearRenderTargetPass(GraphBuilder, SceneTextures.Velocity); bNeedsClearMask ~View.GPUMask.GetNative(); }bNeedsClearMask是一个 GPU 掩码表示哪些 GPU 上的 Velocity RT 需要清除。View.GPUMask.GetNative()是当前视图所绑定的 GPU 索引掩码。两者按位与确保只有该视图真正使用的 GPU 才执行清除避免跨 GPU 污染或多余操作。View.BeginRenderView();View.BeginRenderView()在 Velocity Pass 中的作用是初始化该视图的渲染环境确保后续的绘制命令能够正确输出到 Velocity Render Target。FParallelMeshDrawCommandPass ParallelMeshPass *View.ParallelMeshDrawCommandPasses[MeshPass]; FVelocityPassParameters* PassParameters GraphBuilder.AllocParametersFVelocityPassParameters(); PassParameters-View View.GetShaderParameters(); ParallelMeshPass.BuildRenderingCommands(GraphBuilder, Scene-GPUScene, PassParameters-InstanceCullingDrawParams); PassParameters-SceneTextures SceneTextures.GetSceneTextureShaderParameters(View.FeatureLevel); PassParameters-RenderTargets.DepthStencil FDepthStencilBinding( SceneTextures.Depth.Resolve, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil); if (bBindRenderTarget) { ERenderTargetLoadAction LoadAction (bNeedsClearMask View.GPUMask.GetNative()) ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad; if (MotionVectorWorldOffsetTexture) { // Switch Velocity and Offset texture to avoid an additional copy // // Write Velocity into the Offset texture and Offset into the Velocity texture so that // When we resolve (e.g., RWOffset[Position] Velocity[ResolvedPosition]), the resolved velocity // is stored in the Velocity texture (RWOffset) instead of Offset texture to avoid an additional copy // from Offset texture to Velocity texture. // From // V v // Offset o // Offset[p] V[rp] // V Offset // To // Offset v // V o // V[p] Offset[rp] PassParameters-RenderTargets[0] FRenderTargetBinding(MotionVectorWorldOffsetTexture,LoadAction); PassParameters-RenderTargets[1] FRenderTargetBinding(SceneTextures.Velocity, LoadAction); } else { PassParameters-RenderTargets[0] FRenderTargetBinding(SceneTextures.Velocity, LoadAction); } bNeedsClearMask ~View.GPUMask.GetNative(); }这段代码是 Velocity Pass实际提交绘制命令前的参数配置最核心的设计是注释里解释的“交换 Velocity 纹理与 Offset 纹理的绑定”。基础顶点速度直接渲染到SceneTextures.Velocity后续直接使用即可。RT0原本应该是 Velocity→ 绑定为MotionVectorWorldOffsetTexture临时偏移纹理。RT1额外绑定→ 绑定为SceneTextures.Velocity原始主速度纹理。合成阶段后面未显示的 Pass会将V作为“累加目标”把Offset的内容现在是基础速度加进去。最终结果原地留在SceneTextures.Velocity中省去了从 Offset 拷贝回 Velocity 的步骤。这要求像素着色器在写入V时要处理好初始值通常需要 Load 而非 Clear以保证保留像素偏移速度的正确合成。处理完当前视图后将对应的 GPU 掩码位清零避免重复清除多视图可能共享某些 GPU 掩码。这是典型的掩码消费模式。PassParameters-VelocityClippedDepth BindTranslucentVelocityClippedDepthPassUniformParameters(GraphBuilder,SceneTextures, bIsTranslucentClippedDepthPass, ShaderPlatform);提供深度信息用于裁剪将当前场景的深度纹理SceneTextures以及平台相关的参数打包传入 Velocity Pass 的着色器参数中。解决半透明物体背后的速度错误当场景中存在半透明物体例如玻璃、水面时其背后移动的不透明物体会产生运动向量。如果直接使用这些向量做运动模糊会导致半透明区域也跟着模糊视觉效果错误。通过启用bIsTranslucentClippedDepthPass着色器可以在计算速度时利用深度纹理进行比对如果当前像素的深度比半透明物体的深度更远即位于半透明之后则该速度被丢弃或置零。只有位于半透明物体前面的不透明物体其运动向量才会被保留。场景举例背景一个快速向右移动的不透明立方体。前景一块静止的半透明玻璃板遮挡了部分立方体。渲染速度时不透明 Velocity Pass 先执行立方体被绘制它在屏幕上的所有像素包括被玻璃挡住的部分都写入了向右的运动向量。如果直接使用这张速度图玻璃区域也会继承立方体的运动向量。运动模糊/TAA 会错误地把玻璃也向右拖拽尽管玻璃本身是静止的。问如果玻璃和车都运动呢直接答案如果玻璃和车都运动最终屏幕像素的速度完全由玻璃的运动决定汽车的运动向量在玻璃遮挡区域会被彻底抛弃。所以这样的话就是汽车虽然在跑但是看到半透明的玻璃深度在其之前速度被置0然后累加上玻璃的速度所以就是玻璃速度了if (bIsParallelVelocity) { GraphBuilder.AddDispatchPass( RDG_EVENT_NAME(VelocityParallel), PassParameters, ERDGPassFlags::Raster, [View, ParallelMeshPass, PassParameters](FRDGDispatchPassBuilder DispatchPassBuilder) { ParallelMeshPass.Dispatch(DispatchPassBuilder, PassParameters-InstanceCullingDrawParams); }); } else { GraphBuilder.AddPass( RDG_EVENT_NAME(Velocity), PassParameters, ERDGPassFlags::Raster, [View, ParallelMeshPass, PassParameters](FRDGAsyncTask, FRHICommandList RHICmdList) { SetStereoViewport(RHICmdList, View); ParallelMeshPass.Draw(RHICmdList, PassParameters-InstanceCullingDrawParams); }); }并行路径 (bIsParallelVelocity true)非并行路径 (bIsParallelVelocity false)这段代码根据bIsParallelVelocity选择调用Dispatch并行、多线程命令录制或Draw传统、单线程命令执行并确保非并行路径正确设置了立体视口。它标志着 Velocity Pass 的 CPU 端工作完成将控制权交给 GPU 去实际执行速度渲染。这里加入Velocity Pass 自己的光栅化类型if (bSupportPixelShaderMotionVectorWorldOffset) { for (int32 ViewIndex 0; ViewIndex InViews.Num(); ViewIndex) { FViewInfo View InViews[ViewIndex]; if (View.ShouldRenderView()) { const bool bHasAnyDraw HasAnyDraw(View.ParallelMeshDrawCommandPasses[MeshPass]); if ((!bHasAnyDraw !bForceVelocity) || !View.bUsesMotionVectorWorldOffset) { continue; } RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask); // Resolve { typedef FMotionVectorWorldOffsetVelocityResolveCS SHADER; SHADER::FParameters* PassParameters GraphBuilder.AllocParametersSHADER::FParameters(); PassParameters-View View.GetShaderParameters(); PassParameters-DepthTexture GraphBuilder.CreateSRV(SceneTextures.Depth.Resolve); // Switch back to avoid an additional copy. PassParameters-VelocityTexture GraphBuilder.CreateSRV(MotionVectorWorldOffsetTexture); PassParameters-RWMotionVectorWorldOffset GraphBuilder.CreateUAV(SceneTextures.Velocity); TShaderMapRefSHADER ComputeShader(View.ShaderMap); FIntVector GroupCount FComputeShaderUtils::GetGroupCount(View.ViewRect.Size(), SHADER::GetGroupSize()); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME(MotionVectorWorldOffsetVelocityResolve %dx%d, View.ViewRect.Width(), View.ViewRect.Height()), ComputeShader, PassParameters, GroupCount); } } } }这段代码正是我们之前讨论的“合成 Pass”的实际实现。它的作用是将像素着色器输出的运动向量偏移累加到主速度纹理上完成最终速度图的生成。#if !(UE_BUILD_SHIPPING) const bool bForwardShadingEnabled IsForwardShadingEnabled(ShaderPlatform); if (!bForwardShadingEnabled) { FRenderTargetBindingSlots VelocityRenderTargets; VelocityRenderTargets[0] FRenderTargetBinding(SceneTextures.Velocity, bNeedsClearMask ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ELoad); VelocityRenderTargets.DepthStencil FDepthStencilBinding( SceneTextures.Depth.Resolve, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, ExclusiveDepthStencil); StampDeferredDebugProbeVelocityPS(GraphBuilder, InViews, VelocityRenderTargets); } #endif前置条件仅在非前向着色即延迟渲染模式下执行。因为前向渲染中速度缓冲的管理方式可能不同这个调试工具只针对延迟渲染路径。配置 Render Target 绑定VelocityRenderTargets[0]绑定主速度纹理SceneTextures.Velocity加载动作根据bNeedsClearMask决定清除或保留。绑定深度模板缓冲为场景深度只读加载因为调试着色器可能需要深度信息。调用StampDeferredDebugProbeVelocityPS这是一个调试辅助函数它会向速度纹理中绘制调试标记例如在特定区域写入特殊的颜色/向量值。典型的用途通过控制台命令如vis Velocity或调试探头Debug Probe在屏幕上指定一个像素区域然后在这个区域覆盖写入醒目的运动向量值帮助开发人员可视化某一点的速度信息。函数名中的Deferred指延迟渲染Probe暗示与调试探针相关允许实时查看屏幕某像素的运动向量数值。