Post-Processing Pipeline
Pipeline Execution Order — The EPass Enum管线执行顺序 — EPass 枚举
The entire post-processing chain is orchestrated by a single function:
AddPostProcessingPasses() (PostProcessing.cpp:347).
It uses a TOverridePassSequence — a priority queue of passes
where the last enabled pass receives the final viewport output. Every pass is declared as an
EPass enum value, and the order of that enum is the canonical GPU execution order.
Passes not needed for the current frame are culled by RDG automatically.
整个后处理链由单一函数 AddPostProcessingPasses()(PostProcessing.cpp:347)编排。
它使用 TOverridePassSequence——一个 Pass 优先队列,最后一个启用的 Pass 接收最终视口输出。
每个 Pass 声明为 EPass 枚举值,该枚举的顺序即为规范的 GPU 执行顺序。
当前帧不需要的 Pass 由 RDG 自动剔除。
// PostProcessing.cpp:440 — EPass enum defines the pipeline order // (some passes run before this enum: DOF, PP Materials BeforeDOF/AfterDOF) enum class EPass : uint32 { MotionBlur, // runs AFTER TSR so it has full-res output PostProcessMaterialBeforeBloom, // BL_SceneColorBeforeBloom chain Tonemap, // ACES / Filmic, Color Grading LUT, Vignette FXAA, // only when AntiAliasingMethod == AAM_FXAA PostProcessMaterialAfterTonemapping, // BL_SceneColorAfterTonemapping chain // Debug / visualize passes (stripped in Shipping builds): VisualizeLumenScene, VisualizeDepthOfField, VisualizeStationaryLightOverlap, VisualizeLightCulling, VisualizeSubstrate, VisualizeLightGrid, VisualizeSkyAtmosphere, SelectionOutline, // editor-only EditorPrimitive, // editor-only (gizmos, grid) VisualizeVirtualShadowMaps, VisualizeShadingModels, VisualizeGBufferHints, VisualizeGBufferOverview, VisualizeHDR, VisualizeLocalExposure, VisualizeMotionVectors, VisualizeTemporalUpscaler, PixelInspector, // editor-only HMDDistortion, // VR lens correction HighResolutionScreenshotMask, // Final output: PrimaryUpscale, // spatial upscale when TSR not active (e.g. DLSS) SecondaryUpscale, // second stage upscale AlphaInvert, MAX };
Pre-Temporal Stage — Before TSR/TAA时域前阶段 — TSR/TAA 之前
Several passes run before TSR/TAA, at the native primary resolution.
These are not part of the EPass enum — they are dispatched directly inside
AddPostProcessingPasses() before the enum-driven sequence begins:
多个 Pass 在 TSR/TAA 之前以原始主分辨率运行。
它们不属于 EPass 枚举——而是在枚举驱动的序列开始之前直接在
AddPostProcessingPasses() 中调度:
- PP Materials
BL_SceneColorBeforeDOF: Runs first, before any blur. Ideal for effects that need sharp depth data (e.g. custom silhouette detection, cartoon outlines). - PP 材质
BL_SceneColorBeforeDOF:最先运行,在任何模糊之前。适合需要清晰深度数据的效果(如自定义轮廓检测、卡通描边)。 - Depth of Field (DiaphragmDOF::AddPasses()): The physically accurate diaphragm bokeh simulation. Reads SceneColor + SceneDepth at primary resolution. The DOF scatter-gather algorithm runs multiple Compute passes internally.
- 景深(DiaphragmDOF::AddPasses()):物理精确的光圈散景仿真。以主分辨率读取 SceneColor + SceneDepth。DOF 散射-收集算法内部运行多个 Compute Pass。
- PP Materials
BL_SceneColorAfterDOF: After DOF blur is applied but still at primary res. Good for fog-over-depth or lens-moisture effects. - PP 材质
BL_SceneColorAfterDOF:应用 DOF 模糊后,仍在主分辨率。适合景深上的雾效或镜头潮湿效果。 - Separate Translucency composition: If
bComposeSeparateTranslucencyInTSRis false, translucency rendered at a lower resolution is composited here before TSR. - 独立半透明合成:若
bComposeSeparateTranslucencyInTSR为 false,以较低分辨率渲染的半透明在 TSR 之前于此合成。 - Subsurface Scattering (
PostProcessSubsurface.cpp): Screen-space SSS runs before TAA so temporal accumulation smooths the SSS result over time. - 次表面散射(
PostProcessSubsurface.cpp):屏幕空间 SSS 在 TAA 之前运行,以便时间累积随时间平滑 SSS 结果。
TSR / TAA — The Temporal PivotTSR / TAA — 时域枢纽
TSR (Temporal Super Resolution) is UE's in-house upscaler, implemented in
TemporalSuperResolution.cpp (174 KB — the largest single post-process file).
It sits between the pre-temporal passes and Motion Blur, and performs two jobs at once:
TSR(Temporal Super Resolution)是 UE 的自研上采样器,实现于 TemporalSuperResolution.cpp
(174 KB——最大的单个后处理文件)。它位于时域前通道和运动模糊之间,同时完成两项工作:
- Anti-aliasing: Accumulates sub-pixel coverage across frames using jittered camera positions, eliminating temporal shimmer.
- 抗锯齿:使用抖动相机位置跨帧累积亚像素覆盖,消除时间闪烁。
- Upscaling: Reconstructs a higher-resolution image from a lower-resolution primary viewport (e.g., renders at 1440p, outputs 4K). The primary viewport is the low-res rasterization viewport; the secondary viewport is the output resolution.
- 上采样:从较低分辨率的主视口重建更高分辨率图像(如以 1440p 渲染,输出 4K)。主视口是低分辨率光栅化视口;辅视口是输出分辨率。
TSR also generates half/quarter/eighth-resolution mip outputs in the same dispatch pass. These are consumed directly by the Bloom chain to avoid redundant downsampling passes. This is why Motion Blur and Bloom run after TSR — they need the full secondary-resolution output.
TSR 还在同一 Dispatch Pass 中生成半/四分之一/八分之一分辨率的 Mip 输出, Bloom 链直接消费这些输出以避免冗余下采样 Pass。 这就是为什么运动模糊和 Bloom 在 TSR 之后运行——它们需要完整的辅分辨率输出。
// PostProcessing.cpp:967 — TSR/TAA dispatch if (TAAConfig == EMainTAAPassConfig::TSR) { Outputs = AddMainTemporalSuperResolutionPasses(GraphBuilder, View, UpscalerPassInputs); } else if (TAAConfig == EMainTAAPassConfig::TAA) { Outputs = AddGen4MainTemporalAAPasses(GraphBuilder, View, UpscalerPassInputs); } else if (TAAConfig == EMainTAAPassConfig::ThirdParty) { // DLSS / FSR / XeSS registered via ITemporalUpscaler interface Outputs = AddThirdPartyTemporalUpscalerPasses(GraphBuilder, View, UpscalerPassInputs); } // TSR outputs: FullRes + HalfRes + QuarterRes + EighthRes // The downsample chain is built directly from these outputs — no extra passes needed SceneColorSlice = Outputs.FullRes; HalfResSceneColor = Outputs.HalfRes; // ← Bloom samples here QuarterResSceneColor = Outputs.QuarterRes; EighthResSceneColor = Outputs.EighthRes;
Bloom & Exposure ChainBloom 与曝光链
The Bloom / Exposure chain runs in the background, parallel to (but feeding into) the main post-processing sequence. Its inputs are the downsized mip outputs from TSR:
Bloom/曝光链在后台运行,与主后处理序列并行(但馈入其中)。其输入是来自 TSR 的缩小 Mip 输出:
- Eye Adaptation: Either a histogram pass (luminance distribution → target EV) or a basic luminance compute. Result is a 1×1 GPU buffer consumed by Tonemap. Enabled only with a valid ViewState.
r.EyeAdaptation.MethodOverride - 曝光自适应:直方图 Pass(亮度分布 → 目标 EV)或基本亮度计算。结果是 Tonemap 消费的 1×1 GPU 缓冲区。仅在有效 ViewState 时启用。
r.EyeAdaptation.MethodOverride - Local Exposure: A bilateral grid that computes per-region exposure correction, allowing highlights and shadows to be controlled independently.
r.LocalExposure.Method - 局部曝光:计算每区域曝光校正的双边网格,允许独立控制高光和阴影。
r.LocalExposure.Method - Bloom Setup: Thresholds the scene color to extract only bright pixels that should contribute to bloom. Then the Bloom chain runs a 6-level Gaussian pyramid (alternating downsample + upsample passes) via AddBloomDownPass() / AddBloomUpPass().
- Bloom Setup:阈值处理场景颜色,仅提取应贡献 Bloom 的明亮像素。然后 Bloom 链通过 AddBloomDownPass() / AddBloomUpPass() 运行 6 级高斯金字塔(交替下采样 + 上采样 Pass)。
- FFT Bloom (optional): Convolution bloom via 2D FFT; higher quality but more expensive.
r.Bloom.Method 1 - FFT Bloom(可选):通过 2D FFT 的卷积 Bloom;质量更高但开销更大。
r.Bloom.Method 1 - Lens Flares: Streak flares composited onto the Bloom output before Tonemap.
r.LensFlare 1 - 镜头炫光:在 Tonemap 之前合成到 Bloom 输出的条纹炫光。
r.LensFlare 1
Tonemap & Color Grading色调映射与色彩分级
The Tonemap pass (PostProcessTonemap.cpp) is the pivot from HDR to LDR.
It is one of the most expensive single passes because it combines many operations into one
full-screen shader dispatch to minimize bandwidth:
Tonemap Pass(PostProcessTonemap.cpp)是从 HDR 到 LDR 的枢纽。
它是最昂贵的单个 Pass 之一,因为它将许多操作合并到一个全屏着色器 Dispatch 中以最小化带宽:
- Apply Eye Adaptation (multiply by exposure)
- 应用曝光自适应(乘以曝光值)
- Sample the Color Grading LUT (a 32×32×32 volume texture baked from up to 5 post-process volumes)
- 采样色彩分级 LUT(从最多 5 个后处理体积烘焙的 32×32×32 体积纹理)
- Apply the tone curve operator (ACES, Filmic, Linear, or Unreal)
- 应用色调曲线算子(ACES、Filmic、线性或 Unreal)
- Composite Bloom onto SceneColor
- 将 Bloom 合成到 SceneColor
- Apply Local Exposure correction
- 应用局部曝光校正
- Vignette (darkening at screen edges)
- 渐晕(屏幕边缘变暗)
- Chromatic Aberration (color fringing at edges)
- 色差(边缘的颜色条纹)
- Film Grain (procedural noise)
- 胶片颗粒(程序噪声)
- Output to LDR [0,1] in the target color space (sRGB / Rec.709 / HDR10 / etc.)
- 以目标色彩空间(sRGB / Rec.709 / HDR10 等)输出到 LDR [0,1]
PostProcessCombineLUTs.cpp). It iterates all active post-process volumes, weights their LUTs by blend radius/priority, and bakes the combined result into a single 32³ texture. This is why changing a post-process volume's Color Grading settings takes effect immediately without re-compiling shaders.
色彩分级 LUT 烘焙:LUT 由 AddCombineLUTPass()(PostProcessCombineLUTs.cpp)每帧烘焙一次。它遍历所有激活的后处理体积,按混合半径/优先级为其 LUT 加权,并将组合结果烘焙到单个 32³ 纹理中。这就是为什么更改后处理体积的色彩分级设置会立即生效而无需重新编译着色器。
All Post-Processing Effects — Reference所有后处理效果 — 参考
| Effect效果 | Source File源文件 | Description & Key CVar说明与关键控制台变量 |
|---|---|---|
| Depth of Field | DiaphragmDOF.cpp | Physically accurate bokeh via scatter-gather algorithm; 3 quality modes. r.DOF.Algorithm通过散射-收集算法实现物理精确散景;3 种质量模式。r.DOF.Algorithm |
| Subsurface Scattering | PostProcessSubsurface.cpp | Screen-space SSS for skin/wax; reads GBufferD. Runs before TAA. r.SSS.Quality皮肤/蜡质的屏幕空间 SSS;读取 GBufferD。在 TAA 之前运行。r.SSS.Quality |
| TSR | TemporalSuperResolution.cpp | UE's in-house temporal upscaler (174KB source). r.TSR.History.ScreenPercentageUE 自研时间超分辨率(174KB 源码)。r.TSR.History.ScreenPercentage |
| TAA (Gen4) | TemporalAA.cpp | Temporal anti-aliasing without upscaling. r.TemporalAA.Algorithm无上采样的时间抗锯齿。r.TemporalAA.Algorithm |
| Motion Blur | PostProcessMotionBlur.cpp | Screen-space velocity-based blur; per-object + camera. r.MotionBlur.Amount基于速度的屏幕空间模糊;逐物体 + 相机。r.MotionBlur.Amount |
| Eye Adaptation | PostProcessEyeAdaptation.cpp | Auto-exposure via histogram or basic luminance. r.EyeAdaptation.MethodOverride通过直方图或基本亮度实现自动曝光。r.EyeAdaptation.MethodOverride |
| Local Exposure | PostProcessLocalExposure.cpp | Per-region bilateral grid exposure. r.LocalExposure.Method逐区域双边网格曝光。r.LocalExposure.Method |
| Bloom (Gaussian) | PostProcessWeightedSampleSum.cpp | 6-level downsample + upsample pyramid. r.Bloom.Intensity6 级下采样 + 上采样金字塔。r.Bloom.Intensity |
| Bloom (FFT) | PostProcessFFTBloom.cpp | Convolution bloom via 2D FFT. r.Bloom.Method 1通过 2D FFT 的卷积 Bloom。r.Bloom.Method 1 |
| Lens Flares | PostProcessLensFlares.cpp | Streak flares from bright spots, composited onto Bloom. r.LensFlare来自亮点的条纹炫光,合成到 Bloom。r.LensFlare |
| Color Grading LUT | PostProcessCombineLUTs.cpp | Combines ≤5 post-process volume LUTs into one 32³ texture. r.LUT.Size将最多 5 个后处理体积 LUT 合并为一个 32³ 纹理。r.LUT.Size |
| Tonemap | PostProcessTonemap.cpp | HDR→LDR: ACES/Filmic + LUT + Bloom composite + Vignette + CA + Film Grain. r.Tonemapper.TypeHDR→LDR:ACES/Filmic + LUT + Bloom 合成 + 渐晕 + 色差 + 胶片颗粒。r.Tonemapper.Type |
| FXAA | PostProcessAA.cpp | Fast AA on LDR output; no temporal lag. r.FXAALDR 输出上的快速 AA;无时间延迟。r.FXAA |
| PP Materials | PostProcessMaterial.cpp | Artist-authored materials injected at BeforeDOF / BeforeBloom / ReplacingTonemapper / AfterTonemapping.美术编写的材质,注入 BeforeDOF / BeforeBloom / ReplacingTonemapper / AfterTonemapping。 |
| Lens Distortion | LensDistortion.cpp | Barrel/pincushion correction; can run inside TSR. r.LensDistortion桶形/枕形校正;可在 TSR 内运行。r.LensDistortion |
| Neural Post Process | NeuralPostProcess.cpp | Experimental NNE-based upscaling/filtering. r.NNE.ModelData实验性 NNE 上采样/滤波。r.NNE.ModelData |
Custom Post-Processing — Three Methods自定义后处理——三种方法
Method 1: Post-Process Material (No C++)方法 1:后处理材质(无需 C++)
Create a Material with Material Domain = Post Process. Assign it to a Post Process Volume or Camera. The Blendable Location controls where in the pipeline it runs:
创建材质域为 Post Process 的材质,分配给后处理体积或相机。 可混合位置控制它在管线中的运行位置:
| Blendable Location | Stage阶段 | SceneColor stateSceneColor 状态 |
|---|---|---|
| BL_SceneColorBeforeDOF | Before Depth of Field景深之前 | HDR, primary resolution, sharpHDR,主分辨率,清晰 |
| BL_SceneColorBeforeBloom | After TSR, before BloomTSR 之后,Bloom 之前 | HDR, full secondary resolutionHDR,完整辅分辨率 |
| BL_ReplacingTonemapper | Replaces tonemapper entirely完全替换色调映射器 | HDR in, must output LDR [0,1]HDR 输入,必须输出 LDR [0,1] |
| BL_SceneColorAfterTonemapping | After tonemap, before upscale色调映射后,上采样前 | LDR [0,1], final color spaceLDR [0,1],最终色彩空间 |
Method 2: ISceneViewExtension (Plugin-Safe)方法 2:ISceneViewExtension(插件安全)
Implement ISceneViewExtension and override SubscribeToPostProcessingPass() to inject a custom RDG compute pass at a specific point in the pipeline. This is the approach used by DLSS, FSR, and XeSS:
实现 ISceneViewExtension 并重写 SubscribeToPostProcessingPass(), 在管线特定点注入自定义 RDG Compute Pass。这是 DLSS、FSR 和 XeSS 使用的方法:
// 1. Declare your ViewExtension class FMyPostProcessExtension : public FSceneViewExtensionBase { IMPLEMENT_SCENE_VIEW_EXTENSION(FMyPostProcessExtension); public: virtual void SubscribeToPostProcessingPass( ISceneViewExtension::EPostProcessingPass Pass, const FSceneView& View, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled) override { // Inject AFTER the Tonemap pass if (Pass == ISceneViewExtension::EPostProcessingPass::Tonemap) { InOutPassCallbacks.Add(FAfterPassCallbackDelegate::CreateRaw( this, &FMyPostProcessExtension::RenderMyEffect)); } } private: FScreenPassTexture RenderMyEffect( FRDGBuilder& GraphBuilder, const FSceneView& View, const FPostProcessMaterialInputs& Inputs) { FScreenPassTexture SceneColor = Inputs.GetInput(EPostProcessMaterialInput::SceneColor); // Allocate output texture FRDGTextureDesc Desc = SceneColor.Texture->Desc; Desc.Flags |= TexCreate_UAV; FRDGTextureRef Output = GraphBuilder.CreateTexture(Desc, TEXT("MyEffect.Output")); // Fill pass parameters FMyEffectCS::FParameters* Params = GraphBuilder.AllocParameters<FMyEffectCS::FParameters>(); Params->InputTexture = SceneColor.Texture; Params->Output = GraphBuilder.CreateUAV(Output); Params->InvViewSize = FVector2f(1.f/Desc.Extent.X, 1.f/Desc.Extent.Y); TShaderMapRef<FMyEffectCS> CS(GetGlobalShaderMap(View.GetFeatureLevel())); FComputeShaderUtils::AddFullscreenPass(GraphBuilder, GetGlobalShaderMap(View.GetFeatureLevel()), RDG_EVENT_NAME("MyPostEffect"), CS, Params, FIntVector(FMath::DivideAndRoundUp(Desc.Extent.X, 8), FMath::DivideAndRoundUp(Desc.Extent.Y, 8), 1)); return FScreenPassTexture(Output, SceneColor.ViewRect); } }; // 2. Register during module startup: GEngine->ViewExtensions->NewExtension<FMyPostProcessExtension>(); // Available injection points (EPostProcessingPass enum): // BeforeDOF, AfterDOF, TranslucencyAfterDOF, // MotionBlur, Tonemap, FXAA, VisualizeDepthOfField, // ReplacingTonemapper
Method 3: Direct Engine Source Modification方法 3:直接引擎源码修改
For passes that need GBuffer data, Lumen surface cache, VSM pages, or Nanite visibility info, you must modify the engine source directly — either inserting your pass into FDeferredShadingSceneRenderer::Render() or AddPostProcessingPasses(). This requires a custom engine build but gives unrestricted access to every intermediate resource in the frame.
对于需要 GBuffer 数据、Lumen 表面缓存、VSM 页面或 Nanite 可见性信息的 Pass, 必须直接修改引擎源码——将 Pass 插入 FDeferredShadingSceneRenderer::Render() 或 AddPostProcessingPasses()。这需要自定义引擎构建,但可以无限制访问帧中的每个中间资源。
Source Navigation源码导航
- PostProcess/PostProcessing.cpp:347AddPostProcessingPasses() — full pipeline orchestration完整管线编排
- PostProcess/PostProcessing.cpp:440
EPassenum — canonical execution orderEPass枚举——规范执行顺序 - PostProcess/TemporalSuperResolution.cppTSR implementation (174 KB)TSR 实现(174 KB)
- PostProcess/PostProcessTonemap.cppTonemap + Color Grading + Bloom composite色调映射 + 色彩分级 + Bloom 合成
- PostProcess/PostProcessCombineLUTs.cppColor Grading LUT baking from post-process volumes从后处理体积烘焙色彩分级 LUT
- PostProcess/DiaphragmDOF.cppPhysically accurate depth of field (scatter-gather)物理精确景深(散射-收集)
- PostProcess/PostProcessMotionBlur.cppScreen-space motion blur with velocity tiles使用速度瓦片的屏幕空间运动模糊
- PostProcess/PostProcessMaterial.cppPP Material chain dispatch at all blendable locations所有可混合位置的 PP 材质链分发