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. 源文件映射
| 文件 | 层 | 类型 | 角色 |
| SubstrateDefinitions.h | L1 | .h | BSDF 枚举 + 位宽常量 |
| HLSLMaterialTranslator.cpp | L2 | .cpp | 材质节点 → HLSL 翻译 |
| SubstrateTranslatorCommon.cpp | L2 | .cpp | 通用翻译器 + 循环展开 |
| Substrate.ush | L3 | .ush | 主框架 + BSDF Dispatch 宏 |
| SubstrateToonBSDF.ush | L3 | .ush | Toon BSDF 实现(本报告核心) |
| SubstrateEvaluation.ush | L4 | .ush | 评估调度 + 宏循环 |
| SubstrateDeferredLighting.ush | L4 | .ush | 延迟光照 TOON 分支 |
| SubstrateExport.ush | L5 | .ush | GBuffer 打包/解包 |
| SubstrateVisualize.cpp | L5 | .cpp | C++ 可视化调试 |
| SubstrateMaterial.cpp | L5 | .cpp | 材质系统集成 |
| ToonProfileCommon.ush | L6 | .ush | 3D Atlas 采样 |
| ToonProfileDefinitions.h | L6 | .h | Atlas 布局常量 |
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 | 阴影排线分布 Ramp | R |
TOON_PROFILE_SPEC_IND_RAMP_Y | 间接镜面反射 Ramp | R |
TOON_PROFILE_EVAL_PARAMS_X | EvaluateSettings (xyzw) | RGBA |
TOON_PROFILE_EVAL_PARAMS2_X | EvaluateSettings 扩展 (x) | R |
TOON_PROFILE_TEXTURES_Y + offset | Dither 图案 + 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_RAMP | 1 |
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 UV | Shadow Hatching | Diffuse/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.ush 被 Substrate.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