Shader Class HierarchyShader 类层级

All shaders in UE inherit from FShader. The hierarchy reflects the shader's scope of use:

UE 中所有 Shader 均继承自 FShader。层级结构反映了 Shader 的使用范围:

FShader ← base: source file, entry point, permutation count, parameter layout ├── FGlobalShader ← one instance per platform; not tied to any material │ └── FMyComputeCS, FMyScreenPassPS, ... ├── FMaterialShader ← one instance per (material × permutation); has access to material params │ └── FBasePassPS, FDepthOnlyPS, ... └── FMeshMaterialShader ← FMaterialShader + vertex factory data (position, UV, normals) └── FBasePassVS, TDepthOnlyVS<...>, ...
Type类型Instances per platform每平台实例数Use case使用场景
FGlobalShader1 per permutation每排列 1 个Post-process, compute, screen-space effects后处理、计算、屏幕空间效果
FMaterialShader1 per material × permutation每材质 × 排列 1 个Material-dependent passes (base pass, shadow depth)依赖材质的通道(Base Pass、阴影深度)
FMeshMaterialShader1 per (material × VF × permutation)每(材质 × VF × 排列)1 个Vertex shaders that need vertex factory data需要顶点工厂数据的顶点着色器

DECLARE / IMPLEMENT Macros — Registration SystemDECLARE / IMPLEMENT 宏 — 注册系统

The DECLARE_GLOBAL_SHADER + IMPLEMENT_GLOBAL_SHADER pair is the most common pattern. IMPLEMENT_GLOBAL_SHADER expands to IMPLEMENT_SHADER_TYPE which creates a static FShaderType object and registers it in UE's global shader type registry at static initialization time (before main()). This registry drives the shader compilation job system.

DECLARE_GLOBAL_SHADER + IMPLEMENT_GLOBAL_SHADER 对是最常见的模式。IMPLEMENT_GLOBAL_SHADER 展开为 IMPLEMENT_SHADER_TYPE,在静态初始化时(main() 之前)创建一个静态 FShaderType 对象并注册到 UE 的全局 Shader 类型注册表中。这个注册表驱动着 Shader 编译作业系统。

// === Pattern 1: Global Compute Shader ===
class FMyBlurCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FMyBlurCS);          // Declares GetStaticType(), ShaderTypeRegistration
    SHADER_USE_PARAMETER_STRUCT(FMyBlurCS, FGlobalShader); // Use parameter struct system

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_RDG_TEXTURE(Texture2D, Input)
        SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, Output)
        SHADER_PARAMETER(FVector4f, BlurParams)
    END_SHADER_PARAMETER_STRUCT()

    // Static dispatch: can this permutation compile on this platform?
    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& P)
    { return IsFeatureLevelSupported(P.Platform, ERHIFeatureLevel::SM5); }

    // Optional: inject defines into the compile environment
    static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& P,
                                              FShaderCompilerEnvironment& Env)
    {
        FGlobalShader::ModifyCompilationEnvironment(P, Env);
        Env.SetDefine(TEXT("KERNEL_RADIUS"), 5);
    }
};
// Registers at static init: source file, entry point, shader frequency
IMPLEMENT_GLOBAL_SHADER(FMyBlurCS, "/Engine/Private/MyBlur.usf", "MainCS", SF_Compute);

// IMPLEMENT_GLOBAL_SHADER expands to IMPLEMENT_SHADER_TYPE which calls (Shader.h:1754):
//   new ShaderMetaType(StaticGetTypeLayout(), "FMyBlurCS", SourceFile, FunctionName,
//                      Frequency, PermutationCount, ...);

Permutation Domain System — Compile-Time Shader Variants排列组合域系统 — 编译时 Shader 变体

Shader permutations let a single HLSL file compile into multiple variants, each with different #define values. The permutation domain is a C++ type that describes all dimensions. The total shader count = product of all dimension counts. UE compiles all permutations upfront and caches them in the DDC.

Shader 排列允许单个 HLSL 文件编译为多个变体,每个变体具有不同的 #define 值。排列组合域是描述所有维度的 C++ 类型。总 Shader 数 = 所有维度数量的乘积。UE 预先编译所有排列并在 DDC 中缓存它们。

// === Permutation Domain declaration ===
class FMyLightingCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FMyLightingCS);
    SHADER_USE_PARAMETER_STRUCT(FMyLightingCS, FGlobalShader);

    // Each dimension becomes a #define in the shader
    class FUseHairTransmittance  : SHADER_PERMUTATION_BOOL("USE_HAIR_TRANSMITTANCE");
    class FNumShadowCascades     : SHADER_PERMUTATION_INT("NUM_SHADOW_CASCADES", 4); // values 0-3
    class FShadingModelDim       : SHADER_PERMUTATION_ENUM_CLASS("SHADING_MODEL", EMyModel);

    // Combine into a domain — total = 2 × 4 × EMyModel::MAX variants
    using FPermutationDomain = TShaderPermutationDomain<FUseHairTransmittance,
                                                      FNumShadowCascades,
                                                      FShadingModelDim>;

    // Prune unnecessary permutations (reduces compile count)
    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& P)
    {
        FPermutationDomain Domain(P.PermutationId);
        // Skip hair transmittance on mobile (feature not supported)
        if (Domain.Get<FUseHairTransmittance>() && !IsFeatureLevelSupported(P.Platform, ERHIFeatureLevel::SM5))
            return false;
        return true;
    }
};

// At runtime: select the right permutation and retrieve the compiled shader
FMyLightingCS::FPermutationDomain PermutationVector;
PermutationVector.Set<FMyLightingCS::FUseHairTransmittance>(bHairEnabled);
PermutationVector.Set<FMyLightingCS::FNumShadowCascades>(NumCascades);
TShaderMapRef<FMyLightingCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel),
                                           PermutationVector);

Shader Parameter Binding — The Two SystemsShader 参数绑定 — 两种系统

UE 5 has two parameter binding systems. The modern system uses SHADER_USE_PARAMETER_STRUCT which auto-binds all members from the BEGIN_SHADER_PARAMETER_STRUCT block. The legacy system uses explicit FShaderParameter / FShaderResourceParameter member variables that are manually bound in SetParameters().

UE 5 有两种参数绑定系统。现代系统使用 SHADER_USE_PARAMETER_STRUCT,自动绑定 BEGIN_SHADER_PARAMETER_STRUCT 块中的所有成员。旧版系统使用显式的 FShaderParameter / FShaderResourceParameter 成员变量,在 SetParameters() 中手动绑定。

// === Modern system (RDG-compatible, recommended) ===
class FModernShaderCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FModernShaderCS);
    SHADER_USE_PARAMETER_STRUCT(FModernShaderCS, FGlobalShader); // auto-binding

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, SceneColor)
        SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<uint>, OutputBuffer)
        SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) // UB reference
        SHADER_PARAMETER(float, Exposure)
    END_SHADER_PARAMETER_STRUCT()
};

// === Legacy system (still used in many existing shaders) ===
class FLegacyShaderCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FLegacyShaderCS);
    class FMyParam : public FShaderParameter  { ... }
    class FMyTexture : public FShaderResourceParameter { ... }

    void SetParameters(FRHIBatchedShaderParameters& BatchedParameters, ...)
    {
        // Manually bind each parameter to its RHI slot
        SetShaderValue(BatchedParameters, FMyParam, SomeValue);
        SetTextureParameter(BatchedParameters, FMyTexture, SomeTexture);
    }
};

Compilation Pipeline — From Source to Bytecode编译管线 — 从源码到字节码

Shader compilation is asynchronous and multi-threaded. The high-level flow:

Shader 编译是异步多线程的。高级流程:

Shader type registered (static init) ↓ FShaderCompileUtilities::AddPermutationsToWorkerInput() → Creates FShaderCompileJob for each permutation → Applies ShouldCompilePermutation() filter → Calls ModifyCompilationEnvironment() to inject defines ↓ FShaderCompilingManager::AddJobs() → Distributed to worker processes (ShaderCompileWorker.exe) → Can compile in parallel: HLSL → DXIL / SPIRV / Metal MSL ↓ FShaderCompilingManager::ProcessCompiledShaderMaps() → Results stored in FShaderMap (in-memory) → Serialized to DDC (Derived Data Cache) by ShaderMapId hash ↓ TShaderMapRef<FMyShader>(GetGlobalShaderMap(...)) → Looks up compiled shader from FGlobalShaderMap → Returns typed shader reference for SetShaderParameters()
DDC (Derived Data Cache): Compiled shader bytecode is identified by a hash combining the source file hash, platform, feature level, and permutation ID. On a cache hit, compilation is skipped entirely. This is why clean builds are slow but iterative development is fast once the DDC is warm. DDC(派生数据缓存):编译后的 Shader 字节码通过源文件哈希、平台、功能级别和排列 ID 组合的哈希标识。缓存命中时完全跳过编译。这就是全新构建很慢但 DDC 预热后迭代开发很快的原因。

Material → HLSL Translation (FHLSLMaterialTranslator)材质 → HLSL 翻译(FHLSLMaterialTranslator)

Material graphs in the editor are not shaders — they are node graphs stored as UMaterialExpression objects. The translation from graph → HLSL happens in FHLSLMaterialTranslator (HLSLMaterialTranslator.cpp, 17,000+ lines). This translator walks the expression graph, emitting HLSL code chunks for each node, and assembles them into the final shader template.

编辑器中的材质图不是着色器——它们是存储为 UMaterialExpression 对象的节点图。图 → HLSL 的翻译发生在 FHLSLMaterialTranslatorHLSLMaterialTranslator.cpp,17000+ 行)中。该翻译器遍历表达式图,为每个节点生成 HLSL 代码块,并将它们组装到最终的着色器模板中。

// Translation flow (simplified)
// 1. For each material output (BaseColor, Roughness, Normal, etc.),
//    FHLSLMaterialTranslator recursively translates the connected expression tree:
//    UMaterialExpressionTextureSample::Compile() → "Texture2DSample(Tex0, Samp0, TexCoord)"
//    UMaterialExpressionMultiply::Compile()      → "(A * B)"
//    UMaterialExpressionAdd::Compile()            → "(A + B)"
//
// 2. GetMaterialShaderCode() (line 3316) assembles the final HLSL by injecting
//    generated code chunks into the template: /Engine/Private/MaterialTemplate.ush
//    This template provides the CalcMaterialParameters(), GetMaterialBaseColor(), etc. functions
//
// 3. The resulting HLSL is keyed by FMaterialShaderMapId (shader map id hash)
//    and sent to the shader compiler worker.
//
// Generated code example for "Multiply(TextureSample, Constant(0.5))":
//   half3 Local0 = Texture2DSample(Material_Texture2D_0, ..., Parameters.TexCoords[0].xy);
//   half3 Local1 = 0.5f;
//   half3 Local2 = (Local0 * Local1);  ← output

// Key function in FHLSLMaterialTranslator:
FString GetMaterialShaderCode()  // line 3316 — assembles everything into final HLSL
FString GenerateFunctionCode(uint32 Index, ECompiledPartialDerivativeVariation)  // per-output code
Shader permutation explosion: Every unique combination of material settings (blend mode, two-sided, masked, static lighting) creates a new shader permutation. A complex material with 5 bool settings generates 32 permutations × number of mesh passes × vertex factory variants. This is why material complexity directly impacts loading times and memory. Shader 排列组合爆炸:材质设置的每个唯一组合(混合模式、双面、遮罩、静态光照)都会创建新的 Shader 排列。一个具有 5 个布尔设置的复杂材质会生成 32 个排列 × 网格通道数 × 顶点工厂变体数。这就是为什么材质复杂度直接影响加载时间和内存的原因。

ShaderMap & CachingShaderMap 与缓存

A ShaderMap is a collection of compiled shader instances for a specific (material, platform, quality level) combination. FMaterialShaderMap and FGlobalShaderMap store shaders indexed by type and permutation. They are serialized to the DDC using a hash key (FMaterialShaderMapId) that captures all inputs that affect compilation — source code hash, platform, quality settings, static parameters.

ShaderMap 是特定(材质、平台、质量级别)组合的编译 Shader 实例集合。FMaterialShaderMapFGlobalShaderMap 按类型和排列索引存储 Shader。它们使用捕获所有影响编译的输入的哈希键(FMaterialShaderMapId)序列化到 DDC——源代码哈希、平台、质量设置、静态参数。

TShaderMapRef — Runtime Shader LookupTShaderMapRef — 运行时 Shader 查找

// Look up a compiled global shader at runtime (render thread)
TShaderMapRef<FMyBlurCS> Shader(GetGlobalShaderMap(GMaxRHIFeatureLevel));

// For a specific permutation:
FMyLightingCS::FPermutationDomain PermutationVector;
PermutationVector.Set<FMyLightingCS::FUseHairTransmittance>(true);
TShaderMapRef<FMyLightingCS> LightingShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector);

// Dispatch via FComputeShaderUtils (helper wrapper around SetShaderParameters + Dispatch)
FComputeShaderUtils::AddFullscreenPass(GraphBuilder,
    GetGlobalShaderMap(GMaxRHIFeatureLevel),
    RDG_EVENT_NAME("MyBlur"),
    Shader, PassParams,
    FIntVector(DispatchX, DispatchY, 1));

Source Navigation源码导航

Unreal Engine 5.6 · Engine/Source/Runtime/RenderCore/ & Engine/Private/Materials/
Source analysis · For learning purposes