Files
BlueRoseNote/03-UnrealEngine/卡通渲染相关资料/渲染功能/ARC/Rendering/RED阴影系统.md

397 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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
// 混合模式改为 MultiplyDestColor × SourceColor
// 标准 UE4 用 AdditiveRED 用 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<REDDeferredLightPS> 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 调用) |