--- title: RED阴影系统 date: 2026-05-03 00:00:00 excerpt: 动态/静态阴影分离着色系统,ARC 卡通渲染的核心阴影方案 tags: - ARC - Rendering - Shadow - Toon rating: ⭐ --- # RED 阴影系统 返回 [[Rendering]] ## 概述 RED 阴影系统是 ARC 引擎卡通渲染的核心机制。它将传统 Deferred Lighting 中的阴影处理替换为**阴影着色(Shadow Coloring)**方案——阴影区域不是简单地变暗,而是使用指定的"阴影颜色"进行着色,这是实现赛璐珞风格卡通渲染的关键技术。 ## 核心思路 传统 PBR 阴影:`最终颜色 = 光照颜色 × 阴影衰减` RED 阴影系统:`最终颜色 = lerp(阴影颜色, 光照颜色, 阴影系数)` 同时将**动态阴影**(Shadow Map)和**静态阴影**(Lightmap / Distance Field Shadow)分离处理,允许独立控制两种阴影的着色效果。 ## 实现细节 ### 1. 入口函数 REDDirectionalPixelMain 在 `DeferredLightPixelShaders.usf` 中新增独立的方向光入口: ```hlsl // ASW Change : 2016/10/12 21:35:18 Takuro.K void REDDirectionalPixelMain( float2 InUV : TEXCOORD0, float3 ScreenVector : TEXCOORD1, float4 SVPos : SV_POSITION, out float4 OutColor : SV_Target0 ) { // ... 读取 GBuffer、计算光照衰减 ... OutColor = REDGetShadowColor( WorldPosition, CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, GetPerPixelLightAttenuation(InUV), Random, DynamicShadowShade); } ``` `DynamicShadowShade` 是一个 uniform 参数,控制动态阴影的明暗程度。 ### 2. 阴影项分离 REDGetShadowTerms 在 `DeferredLightingCommon.ush` 中将阴影分解为动态和静态两个独立项: ```hlsl void REDGetShadowTerms( FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float4 LightAttenuation, out float DynamicShadowTerm, out float StaticShadowTerm) { // 动态阴影:来自 Shadow Map float DynamicShadowFraction = DistanceFromCameraFade(...); DynamicShadowTerm = min( lerp(LightAttenuation.x, 1.0f, DynamicShadowFraction), StaticShadowing); // 静态阴影:来自 Lightmap / Distance Field StaticShadowTerm = StaticShadowing; } ``` ### 3. 阴影着色 REDGetShadowColor 核心函数——使用 `DiffuseColor`(存储在 GBufferD 的 CustomData 中)作为阴影颜色: ```hlsl float4 REDGetShadowColor(...) { float DynamicShadow, StaticShadow; REDGetShadowTerms(GBuffer, LightData, WorldPosition, LightAttenuation, DynamicShadow, StaticShadow); // 动态阴影颜色 = DiffuseColor × DynamicShadowShade float3 DynamicShadowColor = GBuffer.DiffuseColor * DynamicShadowShade; // 静态阴影颜色 = DiffuseColor(不额外缩放) float3 StaticShadowColor = GBuffer.DiffuseColor; StaticShadowColor = lerp(StaticShadowColor, float3(1,1,1), StaticShadow); return float4( lerp(DynamicShadowColor, StaticShadowColor, DynamicShadow), 0.0f); } ``` ### 4. PointLight 的 CustomData 使用 点光源渲染时,将 CustomData(原本存储轮廓线 ID 等数据)重新解释为 DiffuseColor: ```hlsl // ASW Change : 2020/01/14 20:19:00 Takeshi.N ScreenSpaceData.GBuffer.DiffuseColor.rgb = ScreenSpaceData.GBuffer.CustomData.rgb; ``` ## C++ 端支持 在 `LightRendering.cpp` 中: - 新增 `REDDeferredLightPS` Pixel Shader 类,绑定 `DynamicShadowShade` 参数 - 点光源通过 `bPointLightRED` 排序键控制渲染顺序(排在方向光之后) - `RED_CUSTOM_LIGHTING` 宏控制是否启用自定义光照路径 ## 关联文档 - [[RED自定义数据通道]] — DiffuseColor 如何写入 GBufferD - [[自定义光照Pass]] — REDDeferredLightPS 的 C++ 绑定 - [[BGMultColor全局色调]] — 场景全局色调叠加 ## 完整代码解析 ### DeferredLightPixelShaders.usf #### DynamicShadowShade uniform 声明 ```hlsl // ASW Change : 2019/02/05 20:37:00 Takeshi.N // 动态阴影明暗控制参数,从 C++ 端传入 // 0.0 = 动态阴影区域完全黑色 // 1.0 = 无动态阴影变暗效果 float DynamicShadowShade; ``` #### REDDirectionalPixelMain — 方向光阴影着色入口 ```hlsl // ASW Change : 2016/10/12 21:35:18 Takuro.K // 专用的方向光阴影着色 Pixel Shader 入口 // 与标准 DeferredLightPixelMain 并行存在,由 C++ 端选择调用 void REDDirectionalPixelMain( float2 InUV : TEXCOORD0, // 屏幕空间 UV float3 ScreenVector : TEXCOORD1, // 屏幕空间到世界空间的方向向量 float4 SVPos : SV_POSITION, // 屏幕位置 out float4 OutColor : SV_Target0 // 输出:阴影颜色(不是光照颜色) ) { OutColor = float4(1,1,1,0); // 默认白色 = 无阴影 float3 CameraVector = normalize(ScreenVector); FScreenSpaceData ScreenSpaceData = GetScreenSpaceData(InUV); // 只处理使用 Deferred Shading 的像素(ShadingModelID > 0) BRANCH if( ScreenSpaceData.GBuffer.ShadingModelID > 0 #if USE_LIGHTING_CHANNELS && (GetLightingChannelMask(InUV) & DeferredLightUniforms.LightingChannelMask) #endif ) { float SceneDepth = CalcSceneDepth(InUV); // 从屏幕空间重建世界坐标 float3 WorldPosition = ScreenVector * SceneDepth + View.WorldCameraOrigin; FDeferredLightData LightData = SetupLightDataForStandardDeferred(); // 生成随机数用于阴影抖动 uint2 Random = ScrambleTEA( uint2( SVPos.xy ) ); Random.x ^= View.Random; Random.y ^= View.Random; // 核心:调用 REDGetShadowColor 计算阴影着色 // 注意输出的是"阴影颜色"而非"光照颜色" OutColor = REDGetShadowColor( WorldPosition, CameraVector, ScreenSpaceData.GBuffer, ScreenSpaceData.AmbientOcclusion, ScreenSpaceData.GBuffer.ShadingModelID, LightData, GetPerPixelLightAttenuation(InUV), Random, DynamicShadowShade); // 传入动态阴影明暗参数 } } ``` #### 点光源 CustomData 覆写 ```hlsl // ASW Change : 2020/01/14 20:19:00 Takeshi.N // 点光源渲染时,将 GBufferD 中存储的 CustomData 重新解释为 DiffuseColor // 这样点光源的阴影着色可以使用与方向光相同的机制 ScreenSpaceData.GBuffer.DiffuseColor.rgb = ScreenSpaceData.GBuffer.CustomData.rgb; ``` #### 点光源 BGMultColor 应用 ```hlsl // ASW Change : 2020/07/07 16:41:00 Takeshi.N // 对点光源输出应用全局色调调制 OutColor.rgb *= View.BGMultColor.rgb; // RGB 颜色乘算 float Grayscale = dot(OutColor.rgb, float3(0.299f, 0.587f, 0.114f)); // 计算亮度 OutColor.rgb = lerp( // 饱和度控制 float3(Grayscale, Grayscale, Grayscale), // 灰度版本 OutColor.rgb, // 原色版本 View.BGMultColor.a); // A 通道控制饱和度 ``` ### DeferredLightingCommon.ush #### 阴影合成修改 ```hlsl // ASW Change : 2018/11/13 17:13:00 Takeshi.N // 原始公式:Shadow = lerp(DynamicShadow, StaticShadow, FadeFraction) // 问题:距离远时 DynamicShadow 淡出后只剩 StaticShadow, // 但动态阴影区域可能比静态阴影更暗(StaticShadow=1),导致阴影突然消失 // // 新公式:Shadow = min(lerp(DynamicShadow, 1.0, FadeFraction), StaticShadow) // 效果:动态阴影淡出后取 min(1.0, StaticShadow) = StaticShadow // 动态阴影存在时取 min(DynamicShadow, StaticShadow) = 两者中更暗的 Shadow.SurfaceShadow = min( lerp(LightAttenuation.x, 1.0f, DynamicShadowFraction), // 动态阴影淡出到 1.0 StaticShadowing); // 与静态阴影取 min ``` #### REDGetShadowTerms — 阴影项分离 ```hlsl // ASW Change : 2019/02/05 20:37:00 Takeshi.N // 将阴影分解为独立的动态和静态两项,供 REDGetShadowColor 分别着色 void REDGetShadowTerms( FGBufferData GBuffer, FDeferredLightData LightData, float3 WorldPosition, float4 LightAttenuation, // Shadow Map 衰减值 out float DynamicShadowTerm, // 输出:动态阴影项 (0=全阴影, 1=无阴影) out float StaticShadowTerm) // 输出:静态阴影项 { // 从 GBuffer 获取静态阴影(Lightmap / Distance Field) float UsesStaticShadowMap = dot(LightData.ShadowMapChannelMask, float4(1, 1, 1, 1)); float StaticShadowing = lerp(1, dot(GBuffer.PrecomputedShadowFactors, LightData.ShadowMapChannelMask), UsesStaticShadowMap); if (LightData.bRadialLight) { // 点光源/聚光灯:直接使用 LightAttenuation.zw DynamicShadowTerm = LightAttenuation.z * StaticShadowing; StaticShadowTerm = LightAttenuation.w * StaticShadowing; } else { // 方向光:根据距离淡化动态阴影 float DynamicShadowFraction = DistanceFromCameraFade( GBuffer.Depth, LightData, WorldPosition, View.WorldCameraOrigin); // 动态阴影:距离远时淡化到 1.0(无阴影) DynamicShadowTerm = lerp(LightAttenuation.x, 1.0f, DynamicShadowFraction); // 静态阴影:不受距离影响 StaticShadowTerm = StaticShadowing; // 叠加光照函数 (Light Function) DynamicShadowTerm *= LightAttenuation.z; StaticShadowTerm *= LightAttenuation.z; } } ``` #### REDGetShadowColor — 核心阴影着色函数 ```hlsl // ASW Change : 2016/10/12 21:35:18 Takuro.K // 核心函数:将阴影从"衰减"转变为"着色" // 传统 PBR:最终颜色 = 光照 × 阴影衰减(变暗) // RED 系统:最终颜色 = lerp(阴影颜色, 光照颜色, 阴影系数)(变色) float4 REDGetShadowColor( float3 WorldPosition, float3 CameraVector, FGBufferData GBuffer, float AmbientOcclusion, uint ShadingModelID, FDeferredLightData LightData, float4 LightAttenuation, uint2 Random, float DynamicShadowShade) // 动态阴影明暗参数 { float DynamicShadow = 1; // 动态阴影项(1=无阴影) float StaticShadow = 1; // 静态阴影项 BRANCH if (LightData.ShadowedBits) { // 有阴影贴图时:分离计算动态和静态阴影 REDGetShadowTerms(GBuffer, LightData, WorldPosition, LightAttenuation, DynamicShadow, StaticShadow); } else { // 无阴影贴图时:使用 AO 作为阴影近似 DynamicShadow = AmbientOcclusion; } // 动态阴影颜色 = DiffuseColor × DynamicShadowShade // DiffuseColor 来自 GBufferD.CustomData(美术指定的阴影基色) // DynamicShadowShade 控制暗度(0=全黑, 1=与阴影基色相同) float3 DynamicShadowColor = GBuffer.DiffuseColor * DynamicShadowShade; // 静态阴影颜色 = DiffuseColor(不额外缩放) float3 StaticShadowColor = GBuffer.DiffuseColor; // 静态阴影:从阴影颜色到白色(无阴影)的过渡 StaticShadowColor = lerp(StaticShadowColor, float3(1,1,1), StaticShadow); // 最终合成:动态阴影颜色到静态阴影颜色的过渡 // DynamicShadow=0 时显示动态阴影颜色 // DynamicShadow=1 时显示静态阴影颜色(可能已经过渡到白色) return float4(lerp(DynamicShadowColor, StaticShadowColor, DynamicShadow), 0.0f); } ``` ### C++ 端实现 ```cpp // LightRendering.cpp — REDDeferredLightPS 类定义 // ASW Change : 2018/12/18 20:59:02 Kazuhito // 继承标准 FDeferredLightPS,复用所有 permutation 参数 class REDDeferredLightPS : public FDeferredLightPS { DECLARE_GLOBAL_SHADER(REDDeferredLightPS) REDDeferredLightPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) : FDeferredLightPS(Initializer) { // ASW: 绑定 DynamicShadowShade uniform 参数 DynamicShadowShade.Bind(Initializer.ParameterMap, TEXT("DynamicShadowShade")); } REDDeferredLightPS() {} // DynamicShadowShade 参数成员 LAYOUT_FIELD(FShaderParameter, DynamicShadowShade); }; // 注册 Shader:绑定到 DeferredLightPixelShaders.usf 的 REDDirectionalPixelMain 入口 #define RED_CUSTOM_LIGHTING 1 IMPLEMENT_GLOBAL_SHADER(REDDeferredLightPS, "/Engine/Private/DeferredLightPixelShaders.usf", "REDDirectionalPixelMain", SF_Pixel); ``` ```cpp // LightRendering.cpp — DynamicShadowShade 参数设置 // 方向光:有预计算光照时 shade=0.3(阴影较暗),无预计算时 shade=1.0(无额外变暗) SetShaderValue(RHICmdList, ShaderRHI, DynamicShadowShade, LightSceneInfo->IsPrecomputedLightingValid() ? 0.3f : 1.0f); // 点光源:shade 固定为 1.0(不额外变暗) SetShaderValue(RHICmdList, ShaderRHI, DynamicShadowShade, 1.f); ``` ```cpp // LightRendering.cpp — 点光源排序 // ASW Change : 2020/10/22 Takeshi.N // 点光源在方向光之后渲染(方向光先完成阴影着色,点光源再叠加) SortedLightInfo->SortKey.Fields.bPointLightRED = (LightSceneInfoCompact.LightType != LightType_Directional); ``` ```cpp // LightRendering.cpp — 方向光渲染路径(RED_CUSTOM_LIGHTING) // 混合模式改为 Multiply(DestColor × SourceColor) // 标准 UE4 用 Additive,RED 用 Multiply 因为阴影颜色系统输出的是"乘算遮罩" GraphicsPSOInit.BlendState = TStaticBlendState< CW_RGBA, BO_Add, BF_DestColor, BF_Zero, // RGB: Dest × Source(乘法混合) BO_Add, BF_One, BF_One // Alpha: 加法 >::GetRHI(); // 使用 REDDeferredLightPS 替代标准 FDeferredLightPS TShaderMapRef PixelShader(View.ShaderMap, PermutationVector); ``` ## 代码修改情况 | 文件路径 | 行号 | 修改类型 | 修改内容 | |---------|------|---------|---------| | `Shaders/Private/DeferredLightPixelShaders.usf` | L106 | 新增 | `DynamicShadowShade` uniform 声明 | | `Shaders/Private/DeferredLightPixelShaders.usf` | L109~L145 | 新增 | `REDDirectionalPixelMain` 方向光阴影着色入口函数 | | `Shaders/Private/DeferredLightPixelShaders.usf` | L236~L239 | 新增 | 点光源 CustomData → DiffuseColor 覆写 | | `Shaders/Private/DeferredLightPixelShaders.usf` | L253~L258 | 新增 | 点光源 BGMultColor 色调应用 | | `Shaders/Private/DeferredLightingCommon.ush` | L166~L172 | 修改 | 阴影合成公式改为 `min(lerp(...), StaticShadowing)` | | `Shaders/Private/DeferredLightingCommon.ush` | L457~L505 | 新增 | `REDGetShadowTerms` 动态/静态阴影项分离函数 | | `Shaders/Private/DeferredLightingCommon.ush` | L508~L557 | 新增 | `REDGetShadowColor` 核心阴影着色函数 | | `Source/Runtime/Renderer/Private/LightRendering.cpp` | L383~L394 | 新增 | `DynamicShadowShade.Bind` 参数绑定 | | `Source/Runtime/Renderer/Private/LightRendering.cpp` | L421~L432 | 新增 | `DynamicShadowShade` 方向光值设置 (0.3/1.0) | | `Source/Runtime/Renderer/Private/LightRendering.cpp` | L434~L445 | 新增 | `DynamicShadowShade` 点光源值设置 (1.0) | | `Source/Runtime/Renderer/Private/LightRendering.cpp` | L682~L688 | 新增 | `LAYOUT_FIELD(FShaderParameter, DynamicShadowShade)` | | `Source/Runtime/Renderer/Private/LightRendering.cpp` | L706~L744 | 新增 | `REDDeferredLightPS` 类 + `IMPLEMENT_GLOBAL_SHADER` | | `Source/Runtime/Renderer/Private/LightRendering.cpp` | L950~L961 | 新增 | `bPointLightRED` 排序键 | | `Source/Runtime/Renderer/Private/LightRendering.cpp` | L2111~L2303 | 新增 | RED 方向光渲染路径(Multiply 混合 + REDDeferredLightPS 调用) |