当前位置: 首页 > news >正文

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; }
  1. 判断当前通道类型
    bIsTranslucentClippedDepthPass检查当前的VelocityPass是否等于枚举值TranslucentClippedDepth,即是否为处理半透明裁剪物体的速度通道。

  2. 检查平台支持
    bSupportsTranslucentClippedDepth通过SupportsTranslucentClippedDepth(ShaderPlatform)查询当前着色器平台(如PC、主机、移动端)是否支持这一特性。

  3. 检查用户设置
    bIsTranslucentClippedDepthEnabled从控制台变量CVarVelocityOutputTranslucentClippedDepthEnabled读取运行时值,判断用户是否启用了该功能(非0为启用)。

  4. 条件返回
    如果当前是半透明裁剪深度通道,但平台不支持用户未启用,则直接return,跳过该通道的执行。

这里的clip不是屏幕裁剪,而是材质的半透明或者mask

为什么需要专门的“速度通道”?

这就是你之前看到的代码要处理的核心问题。

  • 挑战:对于普通的半透明物体,渲染它们的速度信息本身就很复杂且性能开销大。而对于“半透明裁剪”物体,因为它的形状是靠“裁剪”形成的,边缘的像素在“透明”与“不透明”之间剧烈变化,计算其运动速度会更加困难和不可靠,容易产生视觉噪点或错误。

  • 解决方案:因此,引擎专门设计了一个名为TranslucentClippedDepth的速度通道来处理这类物体。为了精确计算它们的速度,这个通道会额外利用深度信息(Depth)来辅助判断

问:所以如果我半透明度为非0,我的速度信息该怎么表达,因为半透明物体一个是半透明物体本身的速度,另一个是半透明物体后面的速度

答:

Unreal Engine 的“单选题”策略

对于真正的混合半透明(Opacity ≠ 1),UE 并不试图融合两者,而是提供两种策略,由开发者决定谁优先

  • 策略 A:默认行为(选背景——半透明物体“摆烂”)

    • 大多数半透明材质默认不写入速度缓冲区(也不写入主深度)。

    • 此时,速度缓冲里保留的是背后不透明物体的速度。

    • 后果:TAA/运动模糊在处理这个像素时,认为“画面运动等于背景速度”。因此,背景是清晰的,但移动的半透明物体(如旋转的玻璃杯)会因为重投影错位,出现严重的拖尾、重影或抖动。这是UE里半透明物体动态画质差的常见原因。

  • 策略 B:强制覆盖(选前景——半透明物体“抢麦”)

    • 开发者可以在材质中勾选Output Velocity,或者通过 CVar(r.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 “贴标签”:当使用RenderDocPIXUnreal Insights(GPU 追踪)抓取帧时,这个宏会在 GPU 时间线上生成一个名为RenderVelocities(具体通道名)彩色/分层区块。比如,当VelocityPassTranslucentClippedDepth时,你在性能分析工具里会看到一个醒目的长条,写着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 GPUstat RHI时,屏幕上显示的“RenderVelocities”那行的毫秒数(ms),主要就是由这个宏负责累加计算出来的。

  • 独立于 CPU:CPU 发命令可能只花 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):将速度通道枚举转换为具体的网格渲染通道类型(如OpaqueMasked等)。

  • bIsOpaquePass:标记当前是不是普通不透明物体的速度通道。

  • 场景 A(常见情况)bIsOpaquePass == trueEarlyZPassMode不是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已经在当前帧的不透明速度通道中被写入了基础速度(基于顶点计算)。

  • 如果像素着色器偏移直接覆盖主纹理,没有偏移的像素数据就会被清空,导致大面积画面出错。

  • 因此,引擎的策略是“分步合成”

    1. 第一步(当前代码):判断场景里是否有材质开启了像素着色器偏移(bHasAny...)。如果有,就新建一张独立的、全黑的临时纹理MotionVectorWorldOffsetTexture)作为“增量累加器”。

    2. 第二步(在后面未贴出的合成Pass中):引擎会读取主速度纹理 + 这张偏移纹理,将两者相加,得到最终速度,再写回主纹理,这个主纹理就是velocitypass最后渲染出来的速度场

AddClearRenderTargetPass的作用:把这整张新纹理刷成纯黑(0,0)。这样,后续像素着色器在计算时,只在需要偏移的地方写入非零值;不需要偏移的地方保持0,相加后不影响主速度。

相当于单独建立一个纹理,然后清空,清空过后计算屏幕中材质的像素偏移度(计算PS导致的速度)然后累加在这张新纹理,做完后累加到主velocity图中

velocitypass也是Pass它执行了vs才能获取到顶点速度

  1. 像素着色器偏移 (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
  1. 遍历所有视图
    InViews是当前帧需要渲染的所有视图(例如左眼、右眼、场景捕获等),循环逐一检查。

  2. 获取每个视图的配置
    View.Family->EngineShowFlags包含各种渲染功能开关。
    StereoMotionVectors是旧版立体渲染中的运动向量(motion vector)支持标志。

  3. 检查平台能力
    PlatformSupportsOpenXRMotionVectors(View.GetShaderPlatform())判断当前 RHI/Shader 平台是否支持 OpenXR 的运动向量扩展。


if (View.ShouldRenderView()) { const bool bHasAnyDraw = HasAnyDraw(View.ParallelMeshDrawCommandPasses[MeshPass]); if (!bHasAnyDraw && !bForceVelocity) { continue; }
  1. View.ShouldRenderView()
    检查该视图是否需要被实际渲染(例如:视图不可见、被完全遮挡、或为无效视角时可跳过)。

  2. HasAnyDraw(View.ParallelMeshDrawCommandPasses[MeshPass])
    ParallelMeshDrawCommandPasses是预先收集好的、按 MeshPass 分类的绘制命令数组。
    MeshPass在这里应该是速度 Pass(例如EMeshPass::Velocity)。
    HasAnyDraw判断这个 Pass 中是否有任何需要绘制的可见网格体。

  3. bForceVelocity
    一个外部传入的布尔标志,表示“即使没有任何可见的网格体绘制,也必须强制输出速度纹理”。
    典型场景:

    • 启用了TAA/DLSS/TSR等需要历史运动向量的特性,即便是全屏后处理也需要一个“零速度”或前一帧运动向量来维持历史缓冲。

    • 某些平台或 VR 场景要求始终提供运动向量。

Velocity Pass 并不是所有物体都会参与

ParallelMeshDrawCommandPasses[MeshPass]里收集的是当前视图中需要输出运动向量的网格体绘制命令。物体能被收录,需要同时满足多个条件:

  • 物体本身需要写入运动向量
    只有被标记为“可能运动”或渲染设置需要输出速度的 Primitive 才会生成速度绘制命令。例如:

    • 使用了World Position Offset(WPO)骨骼动画的动态物体。

    • 开启了物体运动向量(Per-Object Motion Blur)的静态网格体。

    • 场景中移动的粒子、贴花等。

    • 完全静态且从未移动的物体通常不会生成速度 Pass 命令。


RDG_GPU_MASK_SCOPE(GraphBuilder, View.GPUMask);
  • RDG_GPU_MASK_SCOPE
    是 UE 的 RDG(Render 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.AllocParameters<FVelocityPassParameters>(); 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);
  1. 提供深度信息用于裁剪
    将当前场景的深度纹理(SceneTextures)以及平台相关的参数打包,传入 Velocity Pass 的着色器参数中。

  2. 解决半透明物体背后的速度错误
    当场景中存在半透明物体(例如玻璃、水面)时,其背后移动的不透明物体会产生运动向量。如果直接使用这些向量做运动模糊,会导致半透明区域也跟着模糊,视觉效果错误。
    通过启用bIsTranslucentClippedDepthPass,着色器可以在计算速度时利用深度纹理进行比对:

    • 如果当前像素的深度比半透明物体的深度更远(即位于半透明之后),则该速度被丢弃或置零。

    • 只有位于半透明物体前面的不透明物体,其运动向量才会被保留。

场景举例

  • 背景:一个快速向右移动的不透明立方体。

  • 前景:一块静止的半透明玻璃板,遮挡了部分立方体。

渲染速度时:

  1. 不透明 Velocity Pass 先执行:立方体被绘制,它在屏幕上的所有像素(包括被玻璃挡住的部分)都写入了向右的运动向量。

  2. 如果直接使用这张速度图:玻璃区域也会继承立方体的运动向量。

  3. 运动模糊/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.AllocParameters<SHADER::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); TShaderMapRef<SHADER> 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
  1. 前置条件:仅在非前向着色(即延迟渲染)模式下执行。因为前向渲染中速度缓冲的管理方式可能不同,这个调试工具只针对延迟渲染路径。

  2. 配置 Render Target 绑定

    • VelocityRenderTargets[0]绑定主速度纹理SceneTextures.Velocity,加载动作根据bNeedsClearMask决定清除或保留。

    • 绑定深度模板缓冲为场景深度(只读加载),因为调试着色器可能需要深度信息。

  3. 调用StampDeferredDebugProbeVelocityPS

    • 这是一个调试辅助函数,它会向速度纹理中绘制调试标记(例如在特定区域写入特殊的颜色/向量值)。

    • 典型的用途:通过控制台命令(如vis Velocity)或调试探头(Debug Probe)在屏幕上指定一个像素区域,然后在这个区域覆盖写入醒目的运动向量值,帮助开发人员可视化某一点的速度信息。

    • 函数名中的Deferred指延迟渲染,Probe暗示与调试探针相关(允许实时查看屏幕某像素的运动向量数值)。

http://www.gsyq.cn/news/1568450.html

相关文章:

  • ClaudeCode对接GLM-4.7:协议网关构建指南
  • 2026年光伏智慧公共设施选型参考:常州旗硕智慧科技有限公司深度解析 - 速递信息
  • 炉石传说脚本终极指南:如何用智能自动化解放你的游戏时间
  • 国内合规使用Gemini API的两步实操指南
  • 英雄联盟终极助手:如何用League Akari实现游戏自动化与数据智能管理
  • 深度解析:光伏赋能智慧公共设施 原理与应用实践 - 速递信息
  • Ubuntu 18.04终端录屏实战:Terminalizer全链路部署与隐私合规指南
  • 2026年常州旗硕智慧科技有限公司深度测评:智慧公共设施如何选择最佳方案 - 速递信息
  • 2026年6月朗格官方售后维修服务网点,全国统一咨询电话与线下门店完整地址汇总 - 速递信息
  • 汽车软件AUTOSAR迁移实战:从私有架构到标准化的挑战与飞思卡尔服务解析
  • 2026年常州旗硕智慧科技有限公司深度分析:智慧公共设施方案选择指南 - 速递信息
  • 一文讲透:微信投票活动该如何制作(云帆投票vs腾讯投票) - 投票小程序
  • 锐龙AI Max + OpenClaw:本地智能体全链路实战指南
  • 安乐镇汽车汽修厂推荐 星达汽车维修(原程金汽车维修)优势解析 - 百航
  • 嵌入式USB DFU Bootloader实现:从内存规划到固件升级全流程解析
  • 南京工业大学浦江学院在全国 / 省内排名多少?是不是双一流 / 省重点院校? - 寻茫精选
  • 终极Midea AC LAN家庭自动化指南:3分钟实现美的智能设备本地控制
  • 2026宁波营业性演出许可证一站式代办推荐 - 速递信息
  • Ubuntu 14.04 安装 Node.js 实用指南:兼容性、安全与生产部署
  • 投票小程序微信怎么弄?云帆投票vs腾讯投票,2026免费制作教程 - 投票小程序
  • AI编程实操手册:Token、上下文与提示词工程核心指南
  • 本地私有AI知识库:可控语义索引+可信溯源+离线推理实战指南
  • Ubuntu 18.04 部署 ERPNext v13 实战指南:兼容性优先的生产级配置
  • RT500安全GPIO配置实战:堵住TrustZone外设信息泄露漏洞
  • 虎子
  • 2026年6月有名的薄膜生产厂家哪家强,膜/手机膜/薄膜/热熔胶膜/复合材料薄膜/橡胶膜,薄膜供应商哪个好 - 品牌推荐师
  • 2026威信汽车维修选购指南|标杆门店俊发汽修深度测评 - 百航
  • 2026大连怎么找靠谱的营业性演出许可证代办机构 - 速递信息
  • 2026年6月广州亨得利手表受撞击停走检修深度测评:粤海天河城大厦官方售后劳力士欧米茄卡地亚摔落磕碰机芯卡死全解析 - 亨得利腕表维修中心
  • 2026东营本地正规瓷砖空鼓维修服务商盘点|无损免拆砖修复,全域上门售后有保障 - 宅安选房屋修缮