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

15 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
RED阴影系统 2026-05-03 00:00:00 动态/静态阴影分离着色系统ARC 卡通渲染的核心阴影方案
ARC
Rendering
Shadow
Toon

RED 阴影系统

返回 Rendering

概述

RED 阴影系统是 ARC 引擎卡通渲染的核心机制。它将传统 Deferred Lighting 中的阴影处理替换为**阴影着色Shadow Coloring**方案——阴影区域不是简单地变暗,而是使用指定的"阴影颜色"进行着色,这是实现赛璐珞风格卡通渲染的关键技术。

核心思路

传统 PBR 阴影:最终颜色 = 光照颜色 × 阴影衰减 RED 阴影系统:最终颜色 = lerp(阴影颜色, 光照颜色, 阴影系数)

同时将动态阴影Shadow Map静态阴影Lightmap / Distance Field Shadow分离处理允许独立控制两种阴影的着色效果。

实现细节

1. 入口函数 REDDirectionalPixelMain

DeferredLightPixelShaders.usf 中新增独立的方向光入口:

// 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 中将阴影分解为动态和静态两个独立项:

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 中)作为阴影颜色:

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

// 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 宏控制是否启用自定义光照路径

关联文档

完整代码解析

DeferredLightPixelShaders.usf

DynamicShadowShade uniform 声明

// ASW Change : 2019/02/05 20:37:00 Takeshi.N
// 动态阴影明暗控制参数,从 C++ 端传入
// 0.0 = 动态阴影区域完全黑色
// 1.0 = 无动态阴影变暗效果
float DynamicShadowShade;

REDDirectionalPixelMain — 方向光阴影着色入口

// 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 覆写

// ASW Change : 2020/01/14 20:19:00 Takeshi.N
// 点光源渲染时,将 GBufferD 中存储的 CustomData 重新解释为 DiffuseColor
// 这样点光源的阴影着色可以使用与方向光相同的机制
ScreenSpaceData.GBuffer.DiffuseColor.rgb = ScreenSpaceData.GBuffer.CustomData.rgb;

点光源 BGMultColor 应用

// 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

阴影合成修改

// 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 — 阴影项分离

// 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 — 核心阴影着色函数

// 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++ 端实现

// 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);
// 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);
// LightRendering.cpp — 点光源排序
// ASW Change : 2020/10/22 Takeshi.N
// 点光源在方向光之后渲染(方向光先完成阴影着色,点光源再叠加)
SortedLightInfo->SortKey.Fields.bPointLightRED =
    (LightSceneInfoCompact.LightType != LightType_Directional);
// 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 调用)