UE5.8 Substrate Toon Shading

Deep Pipeline Analysis · SQL-ManyThing FTS5 + Symbolic Deconstruction · 2026-06-22
基于 12 个关键源文件完整函数体运行时调用图 的深度分析。 覆盖:位布局 · 7 阶段评估流程 · 3D Atlas 采样公式 · 渲染路径矩阵 · Console Variables · 移动端分叉。 源码检索引擎:SQL-ManyThing FTS5 · UE 5.8 引擎。
📑 目录 1. 源文件映射
2. BSDF 类型注册
3. CustomData 位布局
4. 评估结果结构
5. 核心评估函数 — 7 阶段流程
6. 运行时调用图
7. ToonProfile 3D Atlas
8. Console Variables
9. 渲染路径对比矩阵
10. 移动端分叉逻辑
11. Dispatch 宏系统
12. GBuffer 导出
13. 总结

1. 源文件映射

文件类型角色
SubstrateDefinitions.hL1.hBSDF 枚举 + 位宽常量
HLSLMaterialTranslator.cppL2.cpp材质节点 → HLSL 翻译
SubstrateTranslatorCommon.cppL2.cpp通用翻译器 + 循环展开
Substrate.ushL3.ush主框架 + BSDF Dispatch 宏
SubstrateToonBSDF.ushL3.ushToon BSDF 实现(本报告核心)
SubstrateEvaluation.ushL4.ush评估调度 + 宏循环
SubstrateDeferredLighting.ushL4.ush延迟光照 TOON 分支
SubstrateExport.ushL5.ushGBuffer 打包/解包
SubstrateVisualize.cppL5.cppC++ 可视化调试
SubstrateMaterial.cppL5.cpp材质系统集成
ToonProfileCommon.ushL6.ush3D Atlas 采样
ToonProfileDefinitions.hL6.hAtlas 布局常量

2. BSDF 类型注册

SubstrateDefinitions.h 中定义所有 BSDF 枚举常量。Toon BSDF 注册为 类型 6

// 位宽约束(编译期常量)
#define SUBSTRATE_MAX_CLOSURE_COUNT  8   // 4bit × 8 = 32bit
#define SUBSTRATE_MAX_OPERATOR_COUNT 15
#define SUBSTRATE_FULLY_SIMPLIFIED_NUM_UINTS 8  // 32 字节

#define SUBSTRATE_BSDF_TYPE_SLAB               0
#define SUBSTRATE_BSDF_TYPE_VOLUMETRICFOGCLOUD 1
#define SUBSTRATE_BSDF_TYPE_UNLIT              2
#define SUBSTRATE_BSDF_TYPE_HAIR               3
#define SUBSTRATE_BSDF_TYPE_SINGLELAYERWATER   4
#define SUBSTRATE_BSDF_TYPE_EYE                5
#define SUBSTRATE_BSDF_TYPE_TOON               6  ← Toon BSDF

3. CustomData 位布局

PackToonCustomData() 将 UV + ProfileId 打包为 1 个 uint32

U (PatternUVs.x)
bits [11:0] · 0-4095
V (PatternUVs.y)
bits [23:12] · 0-4095
ProfileId
bits [31:24] · 0-255
Pack / Unpack 源码
uint PackToonCustomData(float2 UVs, float ProfileId, float Dither) {
    uint U12bits = saturate(UVs.x) * 4095.0f;
    uint V12bits = saturate(UVs.y) * 4095.0f;
    uint Packed  = U12bits;
    Packed |= V12bits << 12;
    Packed |= (uint(ProfileId) & 0xFF) << 24;
    return Packed;
}

void UnpackToonCustomData(uint Packed, inout float2 UVs, inout float ProfileId) {
    uint U12bits = BitFieldExtractU32(Packed, 12, 0);
    uint V12bits = BitFieldExtractU32(Packed, 12, 12);
    UVs = float2(U12bits, V12bits) * (1.0f / 4095.0f);
    ProfileId = float(BitFieldExtractU32(Packed, 8, 24));
}

Dither 参数被声明但未写入 Packed 字段——仅局部使用于抖动偏移计算。

4. 评估结果结构

FSubstrateEvaluateToonResult — 12 字段
字段语义Toon 特殊值
IntegratedDiffuseValue漫反射积分值含 Shadow × Falloff × Color × DiffuseRamp
IntegratedSpecularValue镜面反射积分值含 SurfaceShadow × NoL × Falloff × SpecularRamp
DiffuseColor漫反射颜色= BaseColor × DiffuseRamp(已作用 Ramp)
EmissivePathValue自发光直接透传 Emissive 参数
DiffusePathValue漫反射路径值= Diffuse_Lambert(DiffuseColor)
SpecularPathValue镜面反射路径值= SpecularColor × SpecularRamp
DiffusePDF漫反射 PDF= saturate(NoL) / π
ThroughputV体积穿透恒为 0(Toon 不透明)
TransmittanceAlongN沿法线透射率恒为 0(Toon 不透明)
bPostProcessSubsurface次表面后处理恒为 false
ToonDataWeight统一漫反射权重= TransmittanceShadow × Falloff
ToonDataShadowNoL带阴影 NoL [0,1]= (NoL×0.5+0.5) × (可选 Shadow)

5. 核心评估函数 — 7 阶段流程

SubstrateToonEvaluateCommon()16 个参数,是 Toon BSDF 的唯一运行时入口。所有三种渲染路径(Forward / Deferred / Mobile)最终汇聚到此函数。

完整函数签名
FSubstrateEvaluateToonResult SubstrateToonEvaluateCommon(
    float3 DiffuseColor, float3 SpecularColor,
    float  Roughness,     float  ToonProfileId,
    float2 ToonPatternUVs, float  Anisotropy,
    float3 Emissive,      BxDFContext Context,
    bool   bHasAnisotropy, bool   bIsRectLight,
    bool   bThisPassAppliesDiffuseRamp,
    half3  N, half3 V,   FAreaLight AreaLight,
    float  ShadowTermsSurfaceShadow,     // ShadowTerms.SurfaceShadow
    float  ShadowTermsTransmittance);    // TransmittanceOrOpticalThickness
Phase 0 — 初始化
FSubstrateEvaluateToonResult Sample = (FSubstrateEvaluateToonResult)0;
FToonProfileEvaluateSettings ToonEvalSettings = GetToonProfileEvaluateSettings(ToonProfileId);

Sample 清零;从 Atlas 读取包含 DiffuseRampOffsetStrength/Size、SpecularRampOffsetStrength/Size、bDiffuseRampIncludeShadow 的设置结构。

Phase 1 — Dither & Hatching(条件编译)
#if TOON_CAN_USE_PATTERNS_UVS
  DiffuseDither  = EvaluateToonProfileDiffuseDither(Id, PatternUVs, Settings);
  SpecularDither = EvaluateToonProfileSpecularDither(Id, PatternUVs, Settings);
  TransmittanceShadow = EvaluateToonProfileShadowHatching(Id, UVs, ShadowSettings, Raw);
#endif

移动端无扩展 GBuffer 时全部跳过。Dither 值从 Dither 图案 Atlas 采样(2D 坐标 × ProfileId 切片),用于避免 Ramp 色带。

Phase 2 — Specular(NDF Only)
if (bIsRectLight)
    Specular = RectGGXApproxLTC(Roughness, F0, F90, N, V, Rect, Tex);
else {
    D = bHasAnisotropy ? D_GGXaniso(...) : D_GGX(Alpha2, NoH);
    Specular = D * GGXMaxEnergyLookup(Roughness, Anisotropy, NoV);
    // Fresnel + Vis 被注释掉,仅保留 NDF
}

关键简化:跳过 Fresnel 和 Vis 项,仅使用 NDF 获得卡通风格高光形状——放弃物理金属反射以换取艺术表现。

Phase 3 — Diffuse Ramp
RampDiffuseNoL = Context.NoL * 0.5 + 0.5;    // [-1,1] → [0,1]
if (bDiffuseRampIncludeShadow)
    RampDiffuseNoL *= TransmittanceShadow;
DiffuseRamp = bThisPassAppliesDiffuseRamp
    ? EvaluateToonProfileDiffuseRamp(Id, RampDiffuseNoL + Dither)
    : 1.0f;
Phase 4 — Diffuse Integration
Sample.DiffuseColor = DiffuseColor * DiffuseRamp;
Sample.DiffusePathValue = Diffuse_Lambert(DiffuseColor);
Sample.IntegratedDiffuseValue =
    (bIncludeShadow ? 1 : TransmittanceShadow)
    * AreaLight.Falloff * AreaLight.FalloffColor
    * Sample.DiffusePathValue;
    // 不含 AreaLight.NoL → 留给艺术家控制边缘柔和度
Phase 5 — Toon Unified Diffuse Data
Sample.ToonDataWeight = TransmittanceShadow * AreaLight.Falloff;
Sample.ToonDataShadowNoL = RampDiffuseNoL;

被后续统一漫反射 Pass 消费(r.Substrate.Experimental.ToonUnifiedDiffuse)。

Phase 6 — Specular Ramp + Final
SpecularRamp = SpecularColor * EvaluateToonProfileSpecularRamp(
    Id, saturate(dot(Specular, 1/3) + SpecularDither));
Sample.SpecularPathValue = SpecularRamp;
Sample.IntegratedSpecularValue += ShadowTermsSurfaceShadow
    * AreaLight.NoL * AreaLight.Falloff
    * AreaLight.FalloffColor * Sample.SpecularPathValue;

Sample.DiffusePDF = saturate(NoL) / PI;
Sample.ThroughputV = Sample.TransmittanceAlongN = 0;
return Sample;

6. 运行时调用图

Substrate.ush: Substrate_for(ClosureIndex < ClosureCount)
  ├─ UnpackSubstrateBSDF(Buffer, Addressing, Header) → FSubstrateBSDF
  └─ switch(BSDF.BSDFType)
       ├─ case SUBSTRATE_BSDF_TYPE_SLAB: ...
       ├─ case SUBSTRATE_BSDF_TYPE_TOON:
       │   └─ SubstrateToonEvaluateCommon()  [SubstrateToonBSDF.ush]
       │       ├─ GetToonProfileEvaluateSettings()  [ToonProfileCommon.ush]
       │       │   └─ SampleLevel(ToonProfileTexture, params2 + settings)
       │       ├─ EvaluateToonProfileDiffuseDither()  [ToonProfileCommon.ush]
       │       │   └─ SampleLevel(ToonProfileTexture, DitherPattern UV)
       │       ├─ EvaluateToonProfileSpecularDither() [ToonProfileCommon.ush]
       │       ├─ EvaluateToonProfileShadowHatching() [ToonProfileCommon.ush]
       │       ├─ EvaluateToonProfileDiffuseRamp()   [ToonProfileCommon.ush]
       │       │   └─ EvaluateToonProfileRamp(NoL, DIFFUSE_RAMP_Y, Id)
       │       ├─ EvaluateToonProfileSpecularRamp()  [ToonProfileCommon.ush]
       │       │   └─ EvaluateToonProfileRamp(SpecValue, SPECULAR_RAMP_Y, Id)
       │       ├─ D_GGX() / D_GGXaniso()      [BRDF.ush]
       │       ├─ RectGGXApproxLTC()        [AreaLightCommon.ush]
       │       └─ Diffuse_Lambert()         [BRDF.ush]
       └─ case SUBSTRATE_BSDF_TYPE_HAIR: ...

7. ToonProfile 3D Atlas

所有 ToonProfile 数据存储在单张 3D 纹理 View.ToonProfileTexture 中,Z 轴切片索引 = ToonProfileId [0, 255]。

Atlas 行布局(Y 轴)

常量内容通道
TOON_PROFILE_DIFFUSE_RAMP_Y漫反射 Ramp (NoL → RGB)RGB
TOON_PROFILE_SPECULAR_RAMP_Y镜面反射 Ramp (Spec → RGB)RGB
TOON_PROFILE_SHADOW_RAMP_Y阴影排线分布 RampR
TOON_PROFILE_SPEC_IND_RAMP_Y间接镜面反射 RampR
TOON_PROFILE_EVAL_PARAMS_XEvaluateSettings (xyzw)RGBA
TOON_PROFILE_EVAL_PARAMS2_XEvaluateSettings 扩展 (x)R
TOON_PROFILE_TEXTURES_Y + offsetDither 图案 + Shadow Hatch 图案R
采样公式

Diffuse Ramp 采样

float3 Coord = float3(
    NoL,                                              // X: 漫反射角 [-1,1]
    (RAMP_Y + 0.5) * InvSize.w,                        // Y: 行中心
    ProfileId);                                       // Z: 切片索引
return ToonProfileTexture.SampleLevel(Sampler, Coord, 0).xyz;

Dither 图案采样

PatternUVs = frac(PatternUVs * OffsetSize);          // 平铺
BakeCoords = BakeOrigin + PatternUVs * DitherSize;     // 像素坐标
Coord = float3(BakeCoords.xy * InvSize.zw, ProfileId);
Dither = OffsetStrength * (Tex.SampleLevel(..., Coord).x * 2 - 1);

Dither 值在 [-Strength, +Strength] 范围内,加到 Ramp 采样位置以消除色带。

EvaluateSettings 读取
struct FToonProfileEvaluateSettings {
    float DiffuseRampOffsetStrength;   // Data.x
    float DiffuseRampOffsetSize;       // Data.y
    float SpecularRampOffsetStrength;  // Data.z
    float SpecularRampOffsetSize;      // Data.w
    float bDiffuseRampIncludeShadow;   // Data2.x
};

需要两次 SampleLevel:一次读取 xyzw(EVAL_PARAMS_X),一次读取 x(EVAL_PARAMS2_X)。

8. Console Variables

变量默认效果
r.Substrate.Experimental.ToonUnifiedDiffuse 0 =1 时启用统一漫反射 Pass,合并所有 Toon BSDF 的漫反射贡献
r.Substrate.Experimental.ToonReflectionQuantizationEnabled 0 =1 时量化镜面反射项,节省 GBuffer 带宽

编译期宏

默认
SUBSTRATE_LIGHTPASS_APPLIES_TOON_DIFFUSE_RAMP1
TOON_CAN_USE_PATTERNS_UVS桌面 1 / 移动扩展GB 1 / 移动无扩展 0

9. 渲染路径对比矩阵

Forward
✓ Diffuse Ramp
✓ Specular Ramp
✓ Dither + Shadow Hatch
✓ Pattern UVs
GBuffer: 0(inline shading)
Specular: D_GGX only
Deferred
✓ Diffuse Ramp
✓ Specular Ramp
✓ Dither + Shadow Hatch
✓ Pattern UVs
GBuffer: 1 uint (CustomData)
Specular: D_GGX only
Mobile
✓ Diffuse Ramp
✓ Specular Ramp
✗ Dither + Shadow Hatch
✗ Pattern UVs
GBuffer: 0(inline shading)
需扩展 GBuffer 恢复全功能

10. 移动端分叉逻辑

#define TOON_CAN_USE_PATTERNS_UVS \
  (!SHADING_PATH_MOBILE || \
   (!MOBILE_DEFERRED_SHADING || MOBILE_EXTENDED_GBUFFER))
平台条件Ramp UVShadow HatchingDiffuse/Specular Ramp
桌面 / 非 Mobile 路径
Mobile + Deferred + ExtGBuffer
Mobile + 无 ExtGBuffer

11. Dispatch 宏系统

Substrate.ush 定义了三个变体宏控制运行时调度:

FastPath vs 标准 Unpack + 循环展开
#if SUBSTRATE_FASTPATH
    #define UnpackSubstrateBSDF  UnpackFastPathSubstrateBSDFIn
#else
    #define UnpackSubstrateBSDF  UnpackSubstrateBSDFIn
#endif

// 循环展开:单 Closure 直接用 if,多 Closure 用静态展开 for
#if SUBSTRATE_CLAMPED_CLOSURE_COUNT == 1
    #define Substrate_for_unroll(X,Y,Z) X; if(Y)
#else
    #define Substrate_for_unroll(X,Y,Z) \
        SUBSTRATE_UNROLL_N(CLOSURE_COUNT) for(X;Y;Z)
#endif

静态展开避免真实 GPU 循环引起的寄存器膨胀。包含 Toon BSDF 的 SubstrateToonBSDF.ushSubstrate.ush 的 include 链直接引入。

12. GBuffer 导出

Toon BSDF 在 Deferred 路径中通过 CustomData GBuffer 通道传递:

Base Pass → PackToonCustomData(UVs, ProfileId, 0) → CustomData GBuffer
Deferred Light Pass → 读取 CustomData → UnpackToonCustomData() → 恢复 UV + ProfileId
→ 重建完整 SubstrateToonEvaluateCommon() 调用

移动端不经过 GBuffer(inline shading),CustomData 打包仅在桌面 Deferred 路径生效。

13. 总结

编译时(L1-L2)
BSDF_TYPE_TOON = 6 在 SubstrateDefinitions.h 注册
HLSLMaterialTranslator switch-case 生成 HLSL includes
SubstrateTranslatorCommon 处理循环展开
Shader 生成(L3)
SubstrateToonBSDF.ush 被 Substrate.ush include
SubstrateToonEvaluateCommon() = 16 参数中央入口
7 阶段流程:Init → Dither → Spec → Ramp → Integrate → Unified → Final
运行时(L4-L5)
SubstrateEvaluation.ush 宏循环遍历 BSDF 类型
Forward/Deferred/Mobile 三种路径汇聚
Deferred 通过 CustomData GBuffer 传递
C++ 端:SubstrateVisualize.cpp 调试 + 材质集成
Atlas 采样(L6)
3D 纹理,Z=ProfileId [0,255]
7 行布局:Diffuse/Specular/Shadow/Indirect Ramp + EvalSettings
Dither 图案 2D UV × ProfileId 切片
两次 SampleLevel 读取 Settings
关键简化:Toon Specular 仅使用 GGX NDF(D 项),跳过 Fresnel 和 Vis —— 物理正确让步于卡通风格表现。
移动端:TOON_CAN_USE_PATTERNS_UVS 在无扩展 GBuffer 时关闭 Ramp UV 和 Shadow Hatching,仅保留基础 Diffuse/Specular Ramp。
统一漫反射:ToonDataWeight + ToonDataShadowNoL 输出到独立 Pass,由 r.Substrate.Experimental.ToonUnifiedDiffuse 控制。

全部源文件 · LaTeX version: C:\Projects\NoReturn\_bridge\reports\substrate_toon\substrate_toon_deep_report.tex