Practice: Adding Toon & MyCustom1 Shading Models实践:添加 Toon 与 MyCustom1 着色模型
Goal & Constraints目标与约束
This page records the process of adding two new shading model slots to a forked UE 5.6.1 source build:
本页记录在 fork 的 UE 5.6.1 源码中添加两个新着色模型槽的过程:
- MSM_Toon — a cel-shading (toon) model with a 3-step diffuse ramp and hard specular highlight, functional in the legacy (non-Substrate) deferred pipeline.
- MSM_Toon — 卡通渲染模型,使用 3 级漫反射渐变和硬高光,在旧版(非 Substrate)延迟管线中可用。
- MSM_MyCustom1 — a reserved placeholder slot that currently falls back to DefaultLit BxDF; ready for a custom BxDF implementation later.
- MSM_MyCustom1 — 保留的占位符槽,当前回退到 DefaultLit BxDF;待后续实现自定义 BxDF。
The fundamental hardware constraint is that the shading model ID is packed into the lower 4 bits of the GBufferB.a channel (shared with SelectiveOutputMask in the upper 4 bits). This gives a hard maximum of 16 unique IDs (0–15).
根本的硬件约束是:着色模型 ID 被打包到 GBufferB.a 通道的低 4 位(与高 4 位的 SelectiveOutputMask 共享)。这给出了 16 个唯一 ID(0–15)的硬性上限。
| Before修改前 | After修改后 |
|---|---|
| MSM_NUM = 13 (slots 0–12 used)MSM_NUM = 13(已用 0–12) | MSM_NUM = 15 (slots 0–14 used)MSM_NUM = 15(已用 0–14) |
| SHADINGMODELID_NUM = 14SHADINGMODELID_NUM = 14 | SHADINGMODELID_NUM = 15SHADINGMODELID_NUM = 15 |
| 2 free GBuffer slots (13, 14)2 个空闲 GBuffer 槽(13、14) | 1 free GBuffer slot (15)1 个空闲 GBuffer 槽(15) |
| ShadingModelMask : 14 bitsShadingModelMask : 14 位 | ShadingModelMask : 15 bitsShadingModelMask : 15 位 |
Slot Assignment槽位分配
| C++ Enum | Value值 | SHADINGMODELID_* (shader) | Debug Color调试颜色 | BxDF (legacy path)BxDF(旧版路径) |
|---|---|---|---|---|
| MSM_Toon | 13 | SHADINGMODELID_SUBSTRATE_TOON (reused) | Orange橙色 | ToonBxDF_Legacy |
| MSM_MyCustom1 | 14 | SHADINGMODELID_MYCUSTOM1 (new) | Purple紫色 | DefaultLitBxDF (placeholder) |
SHADINGMODELID_SUBSTRATE_TOON (13) was already defined in ShadingCommon.ush for the Substrate pipeline's toon variant. By assigning MSM_Toon = 13, the same GBuffer slot is reused, and both paths (Substrate and legacy) write the same ID. The existing Substrate ToonBxDF handles the Substrate path; the new ToonBxDF_Legacy handles the non-Substrate path.
SHADINGMODELID_SUBSTRATE_TOON(13)在 ShadingCommon.ush 中已为 Substrate 管线的 Toon 变体定义。通过将 MSM_Toon = 13,相同的 GBuffer 槽被复用,两条路径(Substrate 和旧版)写入相同 ID。现有的 Substrate ToonBxDF 处理 Substrate 路径;新的 ToonBxDF_Legacy 处理非 Substrate 路径。
Files Modified — Summary修改文件汇总
-
Engine/Source/Runtime/Engine/Classes/Engine/EngineTypes.h
Added
MSM_ToonandMSM_MyCustom1to theEMaterialShadingModelenum beforeMSM_NUM. MSM_NUM is now 15.在EMaterialShadingModel枚举中MSM_NUM之前添加了MSM_Toon和MSM_MyCustom1。MSM_NUM 现在为 15。 -
Engine/Shaders/Private/ShadingCommon.ush
Added
SHADINGMODELID_MYCUSTOM1 14; bumpedSHADINGMODELID_NUMto 15; added orange/purple debug colors for both new models inGetShadingModelColor().添加了SHADINGMODELID_MYCUSTOM1 14;将SHADINGMODELID_NUM增至 15;在GetShadingModelColor()中为两个新模型添加了橙色/紫色调试颜色。 -
Engine/Shaders/Private/ShadingModelsMaterial.ush
Added
#if MATERIAL_SHADINGMODEL_MYCUSTOM1GBuffer encoding block after the existing Toon block. (The Toon block already existed for the Substrate path.)在现有 Toon 块之后添加了#if MATERIAL_SHADINGMODEL_MYCUSTOM1GBuffer 编码块。(Toon 块已为 Substrate 路径存在。) -
Engine/Shaders/Private/ShadingModels.ush
Added
ToonBxDF_Legacy()outside theSUBSTRATE_ENABLEDguard (3-step ramp + hard specular). UpdatedIntegrateBxDF(): Toon dispatches toToonBxDF_Legacyon non-Substrate /ToonBxDFon Substrate; MyCustom1 dispatches toDefaultLitBxDFas placeholder.在SUBSTRATE_ENABLED保护外添加了ToonBxDF_Legacy()(3 级渐变 + 硬高光)。更新了IntegrateBxDF():Toon 在非 Substrate 路径分发到ToonBxDF_Legacy,在 Substrate 路径分发到ToonBxDF;MyCustom1 分发到DefaultLitBxDF作为占位符。 -
Engine/Source/Runtime/Engine/Private/Materials/HLSLMaterialTranslator.cpp
Extended the
MATERIAL_SHADINGMODEL_TOONcondition to also fire onHasShadingModel(MSM_Toon). Added a new block forMATERIAL_SHADINGMODEL_MYCUSTOM1.扩展了MATERIAL_SHADINGMODEL_TOON条件,使其也在HasShadingModel(MSM_Toon)时触发。添加了MATERIAL_SHADINGMODEL_MYCUSTOM1的新块。 -
Engine/Source/Runtime/Engine/Private/Materials/MaterialIRToHLSLTranslator.cpp
Added
MSM_ToonandMSM_MyCustom1cases toGetShadingModelParameterName(). Without this, the IR translator path would hitUE_MIR_UNREACHABLE()and crash at shader compile time.在GetShadingModelParameterName()中添加了MSM_Toon和MSM_MyCustom1的 case。若不添加,IR 翻译器路径会触发UE_MIR_UNREACHABLE()并在着色器编译时崩溃。 -
Engine/Source/Runtime/Engine/Private/Materials/MaterialShader.cpp
Added name strings
"MSM_Toon"and"MSM_MyCustom1"to the shading model name switch (used for permutation key generation). Also added both to theHasAnyShadingModellist for the lit-material shader stats counter.在着色模型名称 switch(用于排列键生成)中添加了名称字符串"MSM_Toon"和"MSM_MyCustom1"。还将两者添加到HasAnyShadingModel列表中,用于 lit 材质着色器统计计数器。 -
Engine/Source/Runtime/Engine/Public/Materials/MaterialRelevance.h
ShadingModelMask: 14 → 15 bits (to cover bit index 14 for MSM_MyCustom1).SubstrateClosureCountMask: 8 → 7 bits to keep the firstuint32at exactly 32 bits.ShadingModelMask:14 → 15 位(覆盖 MSM_MyCustom1 的位索引 14)。SubstrateClosureCountMask:8 → 7 位,以保持第一个uint32正好为 32 位。
Detailed Code Changes代码修改详情
1EngineTypes.h — C++ Enum
// Engine/Source/Runtime/Engine/Classes/Engine/EngineTypes.h MSM_Strata UMETA(DisplayName="Substrate", Hidden), + MSM_Toon UMETA(DisplayName="Toon"), + MSM_MyCustom1 UMETA(DisplayName="My Custom 1"), MSM_NUM UMETA(Hidden), // was 13, now 15
2ShadingCommon.ush — Shader IDs & Debug Colors
// Engine/Shaders/Private/ShadingCommon.ush #define SHADINGMODELID_SUBSTRATE_TOON 13 // reused as Toon ID + #define SHADINGMODELID_MYCUSTOM1 14 #define SHADINGMODELID_NUM 15 // was 14 // In GetShadingModelColor() — switch branch: + case SHADINGMODELID_SUBSTRATE_TOON: return float3(1.0f, 0.5f, 0.0f); // Orange + case SHADINGMODELID_MYCUSTOM1: return float3(0.5f, 0.0f, 1.0f); // Purple
3ShadingModelsMaterial.ush — GBuffer Encoding
// Engine/Shaders/Private/ShadingModelsMaterial.ush #if MATERIAL_SHADINGMODEL_TOON else if (ShadingModel == SHADINGMODELID_SUBSTRATE_TOON) { /* GBuffer.CustomData is used */ } #endif + #if MATERIAL_SHADINGMODEL_MYCUSTOM1 + else if (ShadingModel == SHADINGMODELID_MYCUSTOM1) + { /* No custom GBuffer data yet — extend here when needed */ } + #endif
4ShadingModels.ush — BxDF & Dispatch
A new ToonBxDF_Legacy() function is added outside the SUBSTRATE_ENABLED guard for the non-Substrate path. It implements a simple 3-step diffuse ramp and a hard specular blob (step(0.5, pow(NoH, specPow))).
在 SUBSTRATE_ENABLED 保护外添加了新的 ToonBxDF_Legacy() 函数,用于非 Substrate 路径。它实现了简单的 3 级漫反射渐变和硬高光(step(0.5, pow(NoH, specPow)))。
// Engine/Shaders/Private/ShadingModels.ush // Added just before IntegrateBxDF(), outside #if SUBSTRATE_ENABLED: + FDirectLighting ToonBxDF_Legacy(FGBufferData GBuffer, half3 N, half3 V, + FAreaLight AreaLight, FShadowTerms Shadow) + { + BxDFContext Context; + Init(Context, N, V, AreaLight.SpecularL); + SphereMaxNoH(Context, AreaLight.SphereSinAlpha, true); + float NoL = max(0.0f, dot(N, AreaLight.DiffuseL)); + + // 3-step diffuse ramp + float Ramp = NoL < 0.3f ? 0.0f : (NoL < 0.6f ? 0.5f : 1.0f); + + // Hard specular highlight + float SpecPow = max(1.0f, (1.0f - GBuffer.Roughness) * 64.0f); + float HardSpec = step(0.5f, pow(saturate(Context.NoH), SpecPow)); + + FDirectLighting Lighting; + Lighting.Diffuse = AreaLight.FalloffColor * GBuffer.DiffuseColor * Ramp * Shadow.SurfaceShadow; + Lighting.Specular = AreaLight.FalloffColor * GBuffer.SpecularColor * HardSpec * Shadow.SurfaceShadow; + Lighting.Transmission = 0; + return Lighting; + } // In IntegrateBxDF() dispatch switch: // was: #if SUBSTRATE_ENABLED / case SHADINGMODELID_SUBSTRATE_TOON / #endif // now: + #if SUBSTRATE_ENABLED + case SHADINGMODELID_SUBSTRATE_TOON: + return ToonBxDF(GBuffer, N, V, AreaLight, Shadow); + #else + case SHADINGMODELID_SUBSTRATE_TOON: // MSM_Toon in the legacy pipeline + return ToonBxDF_Legacy(GBuffer, N, V, AreaLight, Shadow); + #endif + case SHADINGMODELID_MYCUSTOM1: + return DefaultLitBxDF(GBuffer, N, V, AreaLight, Shadow); // placeholder
5HLSLMaterialTranslator.cpp — Shader Defines
// Engine/Source/.../HLSLMaterialTranslator.cpp // was: only fires for Substrate Toon feature flag if (EnumHasAnyFlags(...SubstrateMaterialBsdfFeatures, ESubstrateBsdfFeature::Toon)) + // now: also fires for the C++ MSM_Toon enum + if (EnvironmentDefines->HasShadingModel(MSM_Toon) || + EnumHasAnyFlags(...SubstrateMaterialBsdfFeatures, ESubstrateBsdfFeature::Toon)) { OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_TOON"), TEXT("1")); } + if (EnvironmentDefines->HasShadingModel(MSM_MyCustom1)) + { + OutEnvironment.SetDefine(TEXT("MATERIAL_SHADINGMODEL_MYCUSTOM1"), TEXT("1")); + }
6MaterialIRToHLSLTranslator.cpp — IR Translator
// Engine/Source/.../MaterialIRToHLSLTranslator.cpp — GetShadingModelParameterName() case MSM_ThinTranslucent: return TEXT("MATERIAL_SHADINGMODEL_THIN_TRANSLUCENT"); + case MSM_Toon: return TEXT("MATERIAL_SHADINGMODEL_TOON"); + case MSM_MyCustom1: return TEXT("MATERIAL_SHADINGMODEL_MYCUSTOM1"); default: UE_MIR_UNREACHABLE();
7MaterialShader.cpp — Name Switch & Stats
// Name switch (used for permutation key generation): case MSM_ThinTranslucent: ShadingModelName = TEXT("MSM_ThinTranslucent"); break; + case MSM_Toon: ShadingModelName = TEXT("MSM_Toon"); break; + case MSM_MyCustom1: ShadingModelName = TEXT("MSM_MyCustom1"); break; // Lit-shader stats counter — HasAnyShadingModel list: ShadingModels.HasAnyShadingModel({ MSM_DefaultLit, ..., MSM_ThinTranslucent, MSM_Toon, MSM_MyCustom1 })
8MaterialRelevance.h — Bitfield Expansion
// Engine/Source/Runtime/Engine/Public/Materials/MaterialRelevance.h // First uint32 must stay at exactly 32 bits total: // 15 + 4 + 6 + 7 = 32 bits ✓ + uint32 ShadingModelMask : 15; // was 14 — expanded to cover bit 14 (MSM_MyCustom1) uint32 SubstrateTileTypeMask : 4; uint32 SubstrateUintPerPixel : 6; + uint32 SubstrateClosureCountMask: 7; // was 8 — reduced by 1 to balance the uint32
Rebuilding the Engine重新构建引擎
Prerequisites (one-time)前置条件(仅一次)
- Visual Studio 2022 with workloads: Desktop development with C++ and Game development with C++
- Visual Studio 2022,需包含工作负载:使用 C++ 的桌面开发 和 使用 C++ 的游戏开发
- Individual components: .NET 6 SDK, Windows 10/11 SDK
- 单个组件:.NET 6 SDK、Windows 10/11 SDK
Step 1 — Download Dependencies步骤 1 — 下载依赖
Run once after cloning. Downloads ~30 GB of binary dependencies not stored in git.
clone 后运行一次。下载约 30 GB 的 git 中未存储的二进制依赖。
# Run as Administrator in the repo root
Setup.bat
Step 2 — Generate Project Files步骤 2 — 生成项目文件
Creates UE5.sln. Re-run any time new .cpp / .h files are added.
创建 UE5.sln。每次添加新 .cpp / .h 文件时重新运行。
GenerateProjectFiles.bat
Step 3 — Build步骤 3 — 构建
Open UE5.sln in Visual Studio 2022, set configuration to Development Editor / Win64, right-click UE5 → Build. Or use the command line for faster incremental builds:
在 Visual Studio 2022 中打开 UE5.sln,将配置设为 Development Editor / Win64,右键 UE5 → 生成。或使用命令行进行更快的增量构建:
# Incremental build from command line (recommended after source changes)
Engine\Build\BatchFiles\Build.bat UnrealEditor Win64 Development -WaitMutex -FromMsBuild
Engine module (EngineTypes, Material translators, MaterialShader, MaterialRelevance). This is an incremental build — only the affected modules recompile. Expect 10–30 minutes rather than the multi-hour full build time.
构建范围:我们的修改只涉及 Engine 模块(EngineTypes、Material 翻译器、MaterialShader、MaterialRelevance)。这是一次增量构建——只有受影响的模块重新编译。预计 10–30 分钟,而不是数小时的完整构建时间。
Step 4 — Verify in Editor步骤 4 — 在编辑器中验证
Engine\Binaries\Win64\UnrealEditor.exe
Create a new Material asset. Click the Shading Model dropdown. You should see Toon and My Custom 1 in the list.
创建一个新的材质资产。点击 Shading Model 下拉菜单。你应该在列表中看到 Toon 和 My Custom 1。
Iterating on Shaders Without Rebuild无需重新构建即可迭代着色器
Once the C++ side is compiled once, all visual changes to the BxDF (ramp shape, specular style, etc.) live entirely in ShadingModels.ush. These are HLSL shader files — they are compiled at editor launch by the shader compiler, not by the C++ build system. You can hot-reload them in-editor:
一旦 C++ 侧编译完成,BxDF 的所有视觉修改(渐变形状、高光风格等)完全存在于 ShadingModels.ush 中。这些是 HLSL 着色器文件——它们在编辑器启动时由着色器编译器编译,而不是由 C++ 构建系统编译。你可以在编辑器中热重载它们:
# In the UE editor console (backtick ` to open):
recompileshaders changed
The workflow for visual iteration is therefore:
因此,视觉迭代的工作流程为:
| Change Type修改类型 | File文件 | Action Required所需操作 |
|---|---|---|
| Diffuse ramp, specular shape, rim light, etc.漫反射渐变、高光形状、边缘光等 | ShadingModels.ush | recompileshaders changed |
| GBuffer custom data layoutGBuffer 自定义数据布局 | ShadingModelsMaterial.ush | recompileshaders changed |
| Debug visualization color调试可视化颜色 | ShadingCommon.ush | recompileshaders changed |
| New material input pin (CustomData)新材质输入引脚(CustomData) | MaterialAttributeDefinitionMap.cpp + .ush | C++ rebuild required需要 C++ 重新构建 |
| New shading model slot新着色模型槽 | All 8 files above上述全部 8 个文件 | C++ rebuild required需要 C++ 重新构建 |
Git WorkflowGit 工作流
The repository is forked from Epic's release branch. Committing custom engine changes directly on release mixes your work with Epic's upstream commits, making future upstream merges difficult. The recommended approach is to keep your changes on a dedicated branch:
该仓库是从 Epic 的 release 分支 fork 的。直接在 release 上提交自定义引擎修改会将你的工作与 Epic 的上游提交混合,使未来的上游合并变得困难。推荐的方法是将你的修改保留在专用分支上:
# Create a personal branch from the current state of release git checkout -b custom/shading-models # Stage and commit only the 8 modified files git add Engine/Source/Runtime/Engine/Classes/Engine/EngineTypes.h git add Engine/Shaders/Private/ShadingCommon.ush git add Engine/Shaders/Private/ShadingModelsMaterial.ush git add Engine/Shaders/Private/ShadingModels.ush git add Engine/Source/Runtime/Engine/Private/Materials/HLSLMaterialTranslator.cpp git add Engine/Source/Runtime/Engine/Private/Materials/MaterialIRToHLSLTranslator.cpp git add Engine/Source/Runtime/Engine/Private/Materials/MaterialShader.cpp git add Engine/Source/Runtime/Engine/Public/Materials/MaterialRelevance.h git commit -m "Add MSM_Toon and MSM_MyCustom1 shading models"
When Epic releases a patch and you want to pull it:
当 Epic 发布补丁并且你想拉取时:
# Sync your release branch with upstream git fetch upstream git checkout release git merge upstream/release # Rebase your custom work on top of the updated release git checkout custom/shading-models git rebase release
Next Steps后续步骤
For MSM_Toon关于 MSM_Toon
- The current
ToonBxDF_Legacyis a minimal 3-step ramp. Consider adding: a texture-based ramp (CustomData.xas ramp UV), rim lighting, shadow attenuation tweaks. - 当前的
ToonBxDF_Legacy是最简单的 3 级渐变。考虑添加:基于纹理的渐变(CustomData.x作为渐变 UV)、边缘光、阴影衰减调整。 - To expose a ramp texture or outline width as a material input pin, modify
MaterialAttributeDefinitionMap.cppto add custom data pins (requires a C++ rebuild). - 要将渐变纹理或轮廓线宽度作为材质输入引脚公开,需要修改
MaterialAttributeDefinitionMap.cpp以添加自定义数据引脚(需要 C++ 重新构建)。 - A screen-space outline pass can be added as a separate post-process step via a custom
ISceneViewExtensionwithout modifying the shading model at all. - 屏幕空间轮廓线通道可以通过自定义
ISceneViewExtension作为独立的后处理步骤添加,完全无需修改着色模型。
For MSM_MyCustom1关于 MSM_MyCustom1
- Replace the
DefaultLitBxDFplaceholder inShadingModels.ushwith a custom BxDF when the use case is decided. This only requires editingShadingModels.ushand runningrecompileshaders changed— no C++ rebuild needed. - 当确定使用场景后,将
ShadingModels.ush中的DefaultLitBxDF占位符替换为自定义 BxDF。这只需要编辑ShadingModels.ush并运行recompileshaders changed——无需 C++ 重新构建。 - To rename it, do a find-and-replace of
MSM_MyCustom1/SHADINGMODELID_MYCUSTOM1/MATERIAL_SHADINGMODEL_MYCUSTOM1across all 8 modified files, followed by a C++ rebuild. - 要重命名,在全部 8 个修改的文件中对
MSM_MyCustom1/SHADINGMODELID_MYCUSTOM1/MATERIAL_SHADINGMODEL_MYCUSTOM1进行查找替换,然后进行 C++ 重新构建。