--- title: 自定义后处理 date: 2026-05-03 00:00:00 excerpt: REDPostProcess 全套自定义后处理:Diffusion Filter、角色辉光、自定义 DOF tags: - ARC - Rendering - PostProcess - Toon rating: ⭐ --- # 自定义后处理 返回 [[Rendering]] ## 概述 ARC 引擎新增了完整的自定义后处理系统(REDPostProcess),提供 Diffusion Filter(扩散滤镜)、角色辉光(Chara Glow)和自定义景深(DOF)三大功能。这些效果专门为动画风格渲染设计,与标准 UE4 后处理管线并行工作。 ## Shader 端:REDPostProcess.usf ### Diffusion Filter(扩散滤镜) 基于亮度的柔焦/辉光效果,使用 13-tap 高斯模糊(7 权重核): ```hlsl // 从场景颜色提取高亮区域 float Luminance = dot(SceneColor.rgb, float3(0.299, 0.587, 0.114)); float LuminanceMask = pow(Luminance, LuminancePow); LuminanceMask = saturate(LuminanceMask - LuminanceThreshold); // 高斯模糊后以 Screen 混合模式叠加 // Screen blend: 1 - (1-A)(1-B) float3 Result = 1.0 - (1.0 - SceneColor.rgb) * (1.0 - BlurredHighlight); ``` 可调参数: - `DiffusionFilterLuminancePow` — 亮度幂次(控制提取范围) - `DiffusionFilterLuminanceThreshold` — 亮度阈值 ### Diffusion Filter 2(变体) ```hlsl // Screen 混合 + Power 调整 float3 BlendResult = 1.0 - (1.0 - A) * (1.0 - B); Result = pow(BlendResult, Power); ``` ### 角色辉光(Chara Glow) 从 GBufferD 的 B 通道读取 OutlineID 作为辉光遮罩,进行可变半径的 Box Blur: ```hlsl // 读取 GBufferD.b 作为遮罩 float Mask = GBufferD.b; // 可变半径 Box Blur for (int y = -Radius; y <= Radius; y++) for (int x = -Radius; x <= Radius; x++) Sum += SampleMask(UV + offset); // 以 CharaGlowColor 叠加 OutColor = Sum * CharaGlowColor * CharaGlowArea; ``` 可调参数: - `CharaGlowArea` — 辉光范围 - `CharaGlowColor` — 辉光颜色 ### 自定义 DOF 替代标准 DOF 的 RED 专用版本,支持近景/远景独立模糊: ```hlsl // 高斯模糊 Pass(水平 + 垂直分离) // 13-tap 核,权重:0.0044, 0.0540, 0.2420, 0.3990, ... ``` ### Soft Focus / Glow / Sepia 其他后处理变体: - **Soft Focus**:场景与模糊版本的简单混合 + 饱和度控制 - **Glow**:亮度阈值 + Screen 混合辉光 - **Sepia**:Diffusion Filter 2 的棕褐色变体 ## C++ 端:REDPostProcess.h/.cpp ### 后处理 Pass 类型 ```cpp // 4 种主要 Pass enum class EREDPostProcessPass { DiffusionFilter, // 扩散滤镜 CharaGlow, // 角色辉光 DownSample, // 降采样 BlurH, BlurV, // 水平/垂直高斯模糊 CustomGaussianDOF // 自定义 DOF }; ``` ### 插入点控制 通过 CVar 控制后处理在管线中的插入位置: ```cpp // r.REDPostprocessAfterTranslucency // 0 = 在 SeparateTranslucency 之前 // 1 = 在 SeparateTranslucency 之后 // 2 = 在 Bloom 之后 ``` ### PostProcessSettings 扩展 ```cpp // Scene.h 新增 float DiffusionFilterLuminancePow; float DiffusionFilterLuminanceThreshold; float CharaGlowArea; FLinearColor CharaGlowColor; ``` ## 完整代码解析 ### REDPostProcess.usf 完整着色器注解 #### 辅助函数 ```hlsl // 灰度计算(ITU-R BT.601 标准权重) float GetGrayscale(float3 Color) { return dot(Color, float3(0.299f, 0.587f, 0.114f)); } // 饱和度插值:在灰度和原色之间按 Saturation 系数过渡 float3 LerpSaturation(float3 Color, float Saturation) { float Gray = GetGrayscale(Color); return lerp(float3(Gray, Gray, Gray), Color, Saturation); } ``` #### 高斯核权重(7 权重,13-tap 对称核) ```hlsl // ColorSampleWeight — 用于降采样和模糊的高斯权重 // 对称分布:中心权重最大,两侧递减 // 权重值(半侧,index 0=中心): // [0] = 0.3990 (中心) // [1] = 0.2420 // [2] = 0.0540 // [3] = 0.0044 // [4] = 0.0001 (接近零) // 合计 ≈ 1.0(归一化) static const float ColorSampleWeight[7] = { 0.0044f, 0.0540f, 0.2420f, 0.3990f, 0.2420f, 0.0540f, 0.0044f }; ``` #### DownSamplingPS — 降采样 ```hlsl // 4x4 区域降采样为 1 像素(用于创建 1/4 分辨率 RT) // 采样模式:2x2 双线性采样点,每点覆盖 2x2 像素 void DownSamplingPS( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { float2 UVOffset = 0.5f * InvSize; // 半像素偏移 // 4 次双线性采样取平均 OutColor = Texture2DSample(SceneColorTexture, SceneColorSampler, InUV + float2(-UVOffset.x, -UVOffset.y)); OutColor += Texture2DSample(SceneColorTexture, SceneColorSampler, InUV + float2( UVOffset.x, -UVOffset.y)); OutColor += Texture2DSample(SceneColorTexture, SceneColorSampler, InUV + float2(-UVOffset.x, UVOffset.y)); OutColor += Texture2DSample(SceneColorTexture, SceneColorSampler, InUV + float2( UVOffset.x, UVOffset.y)); OutColor *= 0.25f; } ``` #### BlurVerticalPS / BlurHorizonPS — 分离高斯模糊 ```hlsl // 13-tap 分离高斯模糊(垂直方向) // 使用 ColorSampleWeight 权重,两侧各 6 个采样点 + 中心 1 个 void BlurVerticalPS( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { OutColor = float4(0, 0, 0, 0); // 遍历 -6 到 +6 的偏移量 for (int i = -6; i <= 6; i++) { float2 SampleUV = InUV + float2(0, i * InvSize.y); // 垂直偏移 float Weight = ColorSampleWeight[abs(i)]; // 对称权重 OutColor += Texture2DSample(SceneColorTexture, SceneColorSampler, SampleUV) * Weight; } } // 13-tap 分离高斯模糊(水平方向) // 结构与垂直相同,仅偏移方向改为水平 void BlurHorizonPS( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { OutColor = float4(0, 0, 0, 0); for (int i = -6; i <= 6; i++) { float2 SampleUV = InUV + float2(i * InvSize.x, 0); // 水平偏移 float Weight = ColorSampleWeight[abs(i)]; OutColor += Texture2DSample(SceneColorTexture, SceneColorSampler, SampleUV) * Weight; } } ``` #### DiffusionFilterPS — 基于亮度的辉光提取 ```hlsl // Diffusion Filter:从场景中提取高亮区域用于后续模糊 // 步骤: // 1. 采样场景颜色 // 2. 计算亮度 → 幂次调整 → 阈值裁剪 // 3. 输出 = 原始颜色 × 亮度遮罩 void DiffusionFilterPS( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { float4 SceneColor = Texture2DSample(SceneColorTexture, SceneColorSampler, InUV); // 亮度计算 float Luminance = GetGrayscale(SceneColor.rgb); // 幂次映射:LuminancePow 越大,只有越亮的区域才会被保留 float LuminanceMask = pow(Luminance, DiffusionFilterLuminancePow); // 阈值裁剪:低于阈值的区域完全去除 LuminanceMask = saturate(LuminanceMask - DiffusionFilterLuminanceThreshold); // 输出带亮度遮罩的颜色 OutColor = float4(SceneColor.rgb * LuminanceMask, 1.0f); } ``` #### DiffusionFilter2PS — Screen 混合 + Power ```hlsl // Diffusion Filter 2:将模糊后的辉光以 Screen 模式叠加回场景 // Screen blend: Result = 1 - (1-A)(1-B) // 然后应用 Power 调整对比度 void DiffusionFilter2PS( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { // 采样原始场景 float4 SceneColor = Texture2DSample(SceneColorTexture, SceneColorSampler, InUV); // 采样模糊后的辉光 float4 BlurColor = Texture2DSample(BlurTexture, BlurSampler, InUV); // Screen 混合 float3 BlendResult = 1.0f - (1.0f - SceneColor.rgb) * (1.0f - BlurColor.rgb); // Power 调整(DiffusionFilterPower 控制辉光强度曲线) BlendResult = pow(BlendResult, DiffusionFilterPower); OutColor = float4(BlendResult, 1.0f); } ``` #### SoftFocus — 柔焦 ```hlsl // Soft Focus:场景与模糊版本的混合 + 饱和度调节 // 实现类似相机柔焦镜片的效果 void SoftFocus( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { float4 SceneColor = Texture2DSample(SceneColorTexture, SceneColorSampler, InUV); float4 BlurColor = Texture2DSample(BlurTexture, BlurSampler, InUV); // 在清晰和模糊之间按 SoftFocusBlend 系数混合 float3 Result = lerp(SceneColor.rgb, BlurColor.rgb, SoftFocusBlend); // 饱和度调节 Result = LerpSaturation(Result, SoftFocusSaturation); OutColor = float4(Result, 1.0f); } ``` #### Glow — 辉光 ```hlsl // Glow:基于亮度阈值的辉光效果 // 与 DiffusionFilter 类似但更简单,直接以 Screen 混合叠加 void Glow( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { float4 SceneColor = Texture2DSample(SceneColorTexture, SceneColorSampler, InUV); float4 BlurColor = Texture2DSample(BlurTexture, BlurSampler, InUV); // Screen 混合辉光 float3 Result = 1.0f - (1.0f - SceneColor.rgb) * (1.0f - BlurColor.rgb * GlowIntensity); OutColor = float4(Result, 1.0f); } ``` #### CharaGlowPS — 角色辉光模糊 ```hlsl // 角色辉光 Pass 1:可变半径 Box Blur // 从 GBufferD.b 读取辉光遮罩(由 OutlineID 编码) // 以可变半径进行 Box Blur 扩散辉光范围 void CharaGlowPS( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { float Sum = 0; float Count = 0; // 从 CharaGlowArea 计算模糊半径(像素数) int Radius = (int)CharaGlowArea; // 双重循环 Box Blur for (int y = -Radius; y <= Radius; y++) { for (int x = -Radius; x <= Radius; x++) { float2 SampleUV = InUV + float2(x, y) * InvSize; // 采样 GBufferD.b 通道作为辉光遮罩 float Mask = Texture2DSample(GBufferDTexture, GBufferDSampler, SampleUV).b; Sum += Mask; Count += 1.0f; } } // 归一化 OutColor = float4(Sum / Count, 0, 0, 1); } ``` #### CharaGlowCompPS — 角色辉光合成 ```hlsl // 角色辉光 Pass 2:将模糊后的辉光遮罩与辉光颜色合成 // 叠加到场景颜色上 void CharaGlowCompPS( float4 Position : SV_POSITION, float2 InUV : TEXCOORD0, out float4 OutColor : SV_Target0) { // 采样场景颜色 float4 SceneColor = Texture2DSample(SceneColorTexture, SceneColorSampler, InUV); // 采样模糊后的辉光遮罩 float GlowMask = Texture2DSample(CharaGlowTexture, CharaGlowSampler, InUV).r; // 减去原始遮罩(只保留扩散出去的边缘部分) float OriginalMask = Texture2DSample(GBufferDTexture, GBufferDSampler, InUV).b; GlowMask = saturate(GlowMask - OriginalMask); // 以 CharaGlowColor 叠加辉光 float3 Result = SceneColor.rgb + GlowMask * CharaGlowColor.rgb * CharaGlowColor.a; OutColor = float4(Result, 1.0f); } ``` ### C++ 端实现 ```cpp // REDPostProcess.h — 后处理 Pass 类型枚举 enum REDPostProcess_Type { EREDPostProcess_DownSample, // 降采样 EREDPostProcess_BlurH, // 水平高斯模糊 EREDPostProcess_BlurV, // 垂直高斯模糊 }; // 基础后处理 Pass(降采样 + 模糊) class FRCPassREDPostProcess : public TRenderingCompositePassBase<1, 1> { REDPostProcess_Type Type; bool bToHalf; // 是否降采样到半分辨率 void RenderDownSamplingPass(FRenderingCompositePassContext& Context); void RenderBlurHPass(FRenderingCompositePassContext& Context); void RenderBlurVPass(FRenderingCompositePassContext& Context); }; // Diffusion Filter Pass(亮度提取 + Screen 混合) class FRCPassREDPostProcess_DiffusionFilter : public TRenderingCompositePassBase<2, 1> { ... }; // Diffusion Filter 2 Pass(Power + 阈值 + 可选 CharaGlow 合成) class FRCPassREDPostProcess_DiffusionFilter2 : public TRenderingCompositePassBase<4, 1> { ... }; // CharaGlow Pass(可变半径 BoxBlur on GBufferD.b) class FRCPassREDPostProcess_CharaGlow : public TRenderingCompositePassBase<1, 1> { ... }; ``` ```cpp // PostProcessing.cpp — 后处理管线插入 // CVar 控制插入位置 static TAutoConsoleVariable CVarREDPostprocessAfterTranslucency( TEXT("r.REDPostprocessAfterTranslucency"), // 0: SeparateTranslucency 之前 // 1: SeparateTranslucency 之后 // 2: Bloom 之后 ); // AddREDPostProcess — 构建后处理图 static void AddREDPostProcess(FPostprocessContext& Context) { float pow = Context.View.FinalPostProcessSettings.DiffusionFilterLuminancePow; // 创建降采样 → 水平模糊 → 垂直模糊 → DiffusionFilter2 节点链 FRenderingCompositePass* NodeDownSample = new FRCPassREDPostProcess(EREDPostProcess_DownSample, true); FRenderingCompositePass* NodeBlurH = new FRCPassREDPostProcess(EREDPostProcess_BlurH, false); FRenderingCompositePass* NodeBlurV = new FRCPassREDPostProcess(EREDPostProcess_BlurV, false); // 可选:CharaGlow(角色辉光) float area = Context.View.FinalPostProcessSettings.CharaGlowArea; if (area > 0) { FLinearColor color = Context.View.FinalPostProcessSettings.CharaGlowColor; FRenderingCompositePass* NodeGlow = new FRCPassREDPostProcess_CharaGlow(color, area); // ... 连接到图 } // 最终 DiffusionFilter2 节点 float threshold = Context.View.FinalPostProcessSettings.DiffusionFilterLuminanceThreshold; FRenderingCompositePass* NodeFinal = new FRCPassREDPostProcess_DiffusionFilter2(pow, threshold, bWithGlow); } ``` ## 关联文档 - [[RED自定义数据通道]] — CharaGlow 读取 GBufferD.b 通道 - [[BGMultColor全局色调]] — 另一个全局色彩控制系统 ## 代码修改情况 | 文件路径 | 行号 | 修改类型 | 修改内容 | |---------|------|---------|---------| | `Shaders/Private/REDPostProcess.usf` | 全文 (473行) | **新增文件** | 完整自定义后处理着色器 | | ↳ | L49~L57 | — | `PostProcessVS` 标准顶点着色器 | | ↳ | L61~L86 | — | `PostProcessBlurVS` 高斯模糊顶点着色器(预计算7个采样点) | | ↳ | L96~L105 | — | `ColorSampleWeight[7]` 高斯权重表 | | ↳ | L117~L129 | — | `GetGrayscale` / `LerpSaturation` 辅助函数 | | ↳ | L134~L137 | — | `DownSamplingPS` 降采样 | | ↳ | L142~L191 | — | `BlurVerticalPS` 垂直高斯模糊(13-tap) | | ↳ | L196~L246 | — | `BlurHorizonPS` 水平高斯模糊(13-tap) | | ↳ | L251~L278 | — | `DiffusionFilterPS` 扩散滤镜(亮度提取 + max 混合) | | ↳ | L283~L318 | — | `DiffusionFilter2PS` 扩散滤镜2(Screen 混合 + CharaGlow 合成) | | ↳ | L323~L350 | — | `DiffusionFilter2WithOutCharaGlowPS` 不含辉光版本 | | ↳ | L355~L376 | — | `DiffusionFilter2SepiaPS` 棕褐色变体 | | ↳ | L383~L393 | — | `SoftFocus` 柔焦效果 | | ↳ | L400~L430 | — | `Glow` 辉光(亮度阈值 + Screen 混合) | | ↳ | L437~L455 | — | `CharaGlowPS` 角色辉光(可变半径 BoxBlur on GBufferD.b) | | ↳ | L461~L470 | — | `CharaGlowCompPS` 辉光合成(叠加到场景 + mask) | | `Source/Runtime/Renderer/Private/PostProcess/REDPostProcess.h` | 全文 (298行) | **新增文件** | 后处理 Pass 类声明(4个 Pass 类 + 枚举) | | `Source/Runtime/Renderer/Private/PostProcess/REDPostProcess.cpp` | 全文 (1735行) | **新增文件** | 后处理 Pass 实现(Shader 绑定/参数设置/RT 管理) | | `Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp` | L100~L129 | 新增 | `#include "REDPostProcess.h"` + `CVarREDPostprocessAfterTranslucency` CVar | | `Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp` | L224~L661 | 新增 | `AddREDPostProcess` 完整后处理图构建函数 (~438行) | | `Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp` | L669~L681 | 修改 | 根据 CVar 在管线中插入 RED 后处理 | | `Source/Runtime/Renderer/Private/PostProcess/PostProcessing.cpp` | L852~L908 | 新增 | CharaGlow 独立模式插入逻辑 | | `Source/Runtime/Engine/Classes/Engine/Scene.h` | — | 修改 | 新增 `DiffusionFilterLuminancePow/Threshold`、`CharaGlowArea/Color` |