USF vs HLSL — What's Different?USF 与 HLSL 的区别

.usf (Unreal Shader Format) and .ush (Unreal Shader Header) files are HLSL extended with UE macros and virtual include paths. At their core, they are standard HLSL — every HLSL intrinsic, type, and syntax works identically. What UE adds on top:

.usf(Unreal Shader Format)和 .ush (Unreal Shader Header)文件是使用 UE 宏和虚拟包含路径扩展的 HLSL。 从本质上说,它们是标准 HLSL——每个 HLSL 内置函数、类型和语法工作方式完全相同。 UE 在此基础上增加:

Feature功能Description描述
Virtual include paths虚拟包含路径#include "/Engine/Private/Common.ush" — paths resolve through UE's virtual filesystem, not disk paths#include "/Engine/Private/Common.ush" — 路径通过 UE 虚拟文件系统解析,而非磁盘路径
Platform macros平台宏PLATFORM_PS5, PLATFORM_METAL, COMPILER_DXC — injected by the shader compiler由 Shader 编译器注入
Feature level macros特性级别宏FEATURE_LEVEL_ES3_1, FEATURE_LEVEL_SM5, FEATURE_LEVEL_SM6用于按硬件能力条件编译
Permutation defines排列组合宏C++ permutation dimensions become #define MY_FLAG 0/1 — enables compile-time branchingC++ 排列组合维度变为 #define MY_FLAG 0/1——启用编译期分支
UE utility functionsUE 工具函数GetSceneTextureUV(), ReconstructWorldPosition(), Luminance() etc.大量内置工具函数通过 include 引入
Multi-backend compilation多后端编译Same .usf compiled to DXIL (DX12), SPIRV (Vulkan), Metal IR, PSSL (PS5) by respective compilers同一 .usf 被各自编译器编译为 DXIL、SPIRV、Metal IR、PSSL

File System & Include Paths文件系统与包含路径

UE maps virtual paths to real directories on disk. You never use absolute paths in #include — always use the virtual form.

UE 将虚拟路径映射到磁盘上的真实目录。在 #include 中从不使用绝对路径——始终使用虚拟形式。

Virtual path → Real disk path mappings: /Engine/Private/ → Engine/Shaders/Private/ (engine internals) /Engine/Public/ → Engine/Shaders/Public/ (engine public API) /Engine/Generated/ → (generated at compile time) /Project/ → YourGame/Shaders/ (your project) /Plugin/PluginName/ → Plugins/PluginName/Shaders/ (plugin shaders) Rules: - .ush files = header-only, included by other files (like .h in C++) - .usf files = shader entry points (like .cpp in C++) - Private/ files can be included by anyone (despite the name) - You CANNOT include /Engine/Private/ from a plugin without adding it as a dependency (ShaderCore module needed)

Key Include Files — What's In Them关键头文件 — 内容说明

Always start a new .usf with #include "/Engine/Private/Common.ush". It transitively pulls in most things you need.

新建 .usf 始终以 #include "/Engine/Private/Common.ush" 开头。它会传递性地引入大多数所需内容。

File文件Key Definitions关键定义
/Engine/Public/Platform.ushPlatform/compiler macros, HLSL version detection平台/编译器宏,HLSL 版本检测
/Engine/Private/Common.ushStart here. Math utilities, PI, saturate, lerp, rcp, pow2/4/5, BRANCH, UNROLL, LOOP从这里开始。数学工具函数、常量、控制流提示宏
/Engine/Private/DeferredShadingCommon.ushFGBufferData struct, GetGBufferData(), DecodeGBuffer() — essential for deferred passesGBuffer 数据结构和解码函数——延迟通道必须
/Engine/Private/GBufferCommon.ushGBuffer channel layout constants, encode/decode helpersGBuffer 通道布局常量,编码/解码辅助函数
/Engine/Private/ShadingModels.ushAll BRDF functions: DefaultLitBxDF, HairBxDF, etc.所有 BRDF 函数
/Engine/Private/SceneTexturesCommon.ushSceneDepthTexture, SceneColorTexture, velocity, GBuffer SRVs场景深度/颜色/速度/GBuffer 纹理绑定
/Engine/Private/Random.ushHash functions: RandFast(), Rand3DPCG16(), blue noise哈希/随机函数
/Engine/Private/BRDF.ushLow-level BRDF building blocks: D_GGX, Vis_SmithJoint, F_Schlick底层 BRDF 构建块
/Engine/Private/ScreenPass.ushFull-screen pass helpers: FScreenTransform, UV transforms for post-process全屏通道辅助函数,后处理 UV 变换
/Engine/Private/ComputeShaderUtils.ushCompute group utilities, wave intrinsics wrappers计算组工具函数,Wave 内置函数封装

Shader Types — Choosing the Right Base ClassShader 类型 — 选择正确的基类

UE5 has a strict C++ class hierarchy for shaders. Choosing the wrong base class will either fail to compile or give you access to parameters you don't need (wasting memory).

UE5 的 Shader 有严格的 C++ 类层次结构。选择错误的基类要么编译失败,要么给你访问不需要的参数(浪费内存)。

Base Class基类 Use When使用场景 Available Data可用数据
FGlobalShaderEngine-wide effects: post-process, RDG compute, custom passes. No material, no mesh.引擎级效果:后处理、RDG 计算、自定义通道。无材质,无网格。View UB, custom parameters onlyView UB、自定义参数
FMaterialShaderShaders that need material parameters (BaseColor, Roughness etc.) but not mesh vertex data.需要材质参数但不需要网格顶点数据的 Shader。+Material parameters, Material UB+材质参数、Material UB
FMeshMaterialShaderVertex/Pixel shaders in the mesh drawing pipeline. Full access to vertex factory, material, and view.网格绘制管线中的 VS/PS。完整访问顶点工厂、材质和视图。+Vertex factory, local-to-world matrix+顶点工厂、局部到世界矩阵
For most custom effects, you want FGlobalShader. It's the simplest — you supply all parameters yourself via BEGIN_SHADER_PARAMETER_STRUCT, and it integrates directly with RDG's AddPass(). Use FMeshMaterialShader only when you need to hook into the Mesh Drawing Pipeline (base pass, depth pass, shadow pass) and your shader needs material expression outputs. 对于大多数自定义效果,使用 FGlobalShader。它最简单——你通过 BEGIN_SHADER_PARAMETER_STRUCT 自己提供所有参数,并直接与 RDG 的 AddPass() 集成。只有当需要挂入网格绘制管线(Base Pass、Depth Pass、Shadow Pass)且 Shader 需要材质表达式输出时,才使用 FMeshMaterialShader

Declaring a Shader in C++ — Complete Template在 C++ 中声明 Shader — 完整模板

// === MyEffect.h ===
#pragma once
#include "GlobalShader.h"
#include "RenderGraphUtils.h"
#include "ShaderParameterUtils.h"

// --- Compute shader ---
class FMyEffectCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FMyEffectCS);
    SHADER_USE_PARAMETER_STRUCT(FMyEffectCS, FGlobalShader);

    // Parameter struct: RDG binds these automatically
    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputTexture)         // SRV
        SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, Output) // UAV
        SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler)
        SHADER_PARAMETER(FVector4f, Params)            // simple scalar data
        SHADER_PARAMETER(FIntPoint, TextureSize)
        SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) // camera
    END_SHADER_PARAMETER_STRUCT()

    // Only compile for SM5+ (skip mobile/ES31)
    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& P)
    {
        return IsFeatureLevelSupported(P.Platform, ERHIFeatureLevel::SM5);
    }

    // Declare permutation if needed (see §6)
    using FPermutationDomain = TShaderPermutationDomain<>;
};

// === MyEffect.cpp ===
// Maps shader class → .usf file → entry point function → shader stage
IMPLEMENT_GLOBAL_SHADER(FMyEffectCS,
    "/Project/Private/MyEffect.usf", // virtual path
    "MainCS",                          // HLSL entry function name
    SF_Compute);                       // SF_Vertex / SF_Pixel / SF_Compute

IMPLEMENT_MODULE(FDefaultModuleImpl, MyModule);
Important: SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) gives your shader all camera data (View matrices, screen size, near/far planes, etc.) for free. Always include it for world-space effects. Bind it in C++ with: PassParams->View = View.ViewUniformBuffer; 重要:SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View) 免费提供所有相机数据(视图矩阵、屏幕尺寸、近/远裁剪面等)。始终为世界空间效果包含它。在 C++ 中绑定:PassParams->View = View.ViewUniformBuffer;

Permutation Domains — Compile-Time Shader Variants排列组合域 — 编译期 Shader 变体

A permutation domain creates multiple compiled variants of a single .usf file. Each combination of permutation values produces a separate shader binary. In the .usf, permutation values are exposed as #defines. This is how UE avoids runtime branches for things like "does this pass use MSAA?" or "is shadow enabled?".

排列组合域创建单个 .usf 文件的多个编译变体。 每种排列组合值的组合产生一个独立的 Shader 二进制文件。 在 .usf 中,排列组合值以 #define 形式暴露。 这就是 UE 如何为"此通道是否使用 MSAA?"或"是否启用阴影?"等情况避免运行时分支的方式。

// === Declaring permutation dimensions ===

// Boolean permutation (generates 2 variants: USE_MSAA=0 and USE_MSAA=1)
class FUseMSAA : public SHADER_PERMUTATION_BOOL("USE_MSAA");

// Integer permutation (generates 4 variants: 0,1,2,3)
class FQualityLevel : public SHADER_PERMUTATION_INT("QUALITY_LEVEL", 4);

// Enum permutation (generates one variant per enum value)
SHADER_PERMUTATION_ENUM_CLASS("BLEND_MODE", EMyBlendMode);

// Combine into a domain
using FPermutationDomain = TShaderPermutationDomain<FUseMSAA, FQualityLevel>;

// Filter out invalid combinations (e.g., QUALITY_LEVEL=3 only on SM6)
static bool ShouldCompilePermutation(...) {
    FPermutationDomain Domain(Parameters.PermutationId);
    if (Domain.Get<FQualityLevel>() == 3 &&
        !IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM6))
        return false;
    return true;
}

// === In C++ dispatch code ===
FPermutationDomain Domain;
Domain.Set<FUseMSAA>(View.bMSAA);
Domain.Set<FQualityLevel>(CVarQuality.GetValueOnRenderThread());
TShaderMapRef<FMyEffectCS> Shader(GetGlobalShaderMap(FeatureLevel), Domain);

// === In .usf ===
// #if USE_MSAA
//   Texture2DMS<float4> InputMS;  // MSAA variant
// #else
//   Texture2D Input;               // regular variant
// #endif
Permutation explosion: 5 boolean permutations = 32 shader variants compiled. 5 integer permutations of 4 values = 4⁵ = 1024 variants. Use ShouldCompilePermutation() aggressively to filter impossible combinations. Each unfiltered permutation adds compile time and PSO cache size. 排列组合爆炸:5 个布尔排列 = 编译 32 个 Shader 变体。5 个值为 4 的整数排列 = 4⁵ = 1024 个变体。积极使用 ShouldCompilePermutation() 过滤不可能的组合。每个未过滤的排列组合都会增加编译时间和 PSO 缓存大小。

Full Compute Shader Example — Blur Effect完整计算 Shader 示例 — 模糊效果

A complete, working example from C++ declaration to .usf implementation, integrated into the RDG pipeline.

从 C++ 声明到 .usf 实现的完整可用示例,集成到 RDG 管线中。

Step 1 — C++ Declaration (MyBlur.h/.cpp)步骤 1 — C++ 声明(MyBlur.h/.cpp)

// MyBlur.h
class FMyGaussianBlurCS : public FGlobalShader
{
    DECLARE_GLOBAL_SHADER(FMyGaussianBlurCS);
    SHADER_USE_PARAMETER_STRUCT(FMyGaussianBlurCS, FGlobalShader);

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_RDG_TEXTURE(Texture2D, InputTexture)
        SHADER_PARAMETER_SAMPLER(SamplerState, InputSampler)
        SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float4>, OutputTexture)
        SHADER_PARAMETER(FVector2f, InvTextureSize)
        SHADER_PARAMETER(FVector2f, BlurDirection)   // (1,0) or (0,1)
        SHADER_PARAMETER(float, BlurRadius)
    END_SHADER_PARAMETER_STRUCT()

    static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& P)
    { return IsFeatureLevelSupported(P.Platform, ERHIFeatureLevel::SM5); }

    static void ModifyCompilationEnvironment(
        const FGlobalShaderPermutationParameters& P, FShaderCompilerEnvironment& Env)
    {
        FGlobalShader::ModifyCompilationEnvironment(P, Env);
        Env.SetDefine(TEXT("THREAD_GROUP_SIZE"), 8);
    }
};
IMPLEMENT_GLOBAL_SHADER(FMyGaussianBlurCS,
    "/Project/Private/MyBlur.usf", "MainCS", SF_Compute);

// MyBlur.cpp — the dispatch function (called from a post-process or scene extension)
FRDGTextureRef AddMyGaussianBlurPass(
    FRDGBuilder& GraphBuilder,
    FRDGTextureRef InputTexture,
    FIntPoint Resolution,
    float BlurRadius)
{
    // Create output texture
    FRDGTextureDesc OutDesc = InputTexture->Desc;
    FRDGTextureRef OutputTexture = GraphBuilder.CreateTexture(OutDesc, TEXT("BlurOutput"));

    TShaderMapRef<FMyGaussianBlurCS> Shader(GetGlobalShaderMap(GMaxRHIFeatureLevel));

    auto* Params = GraphBuilder.AllocParameters<FMyGaussianBlurCS::FParameters>();
    Params->InputTexture   = InputTexture;
    Params->InputSampler   = TStaticSamplerState<SF_Bilinear>::GetRHI();
    Params->OutputTexture  = GraphBuilder.CreateUAV(OutputTexture);
    Params->InvTextureSize = FVector2f(1.0f/Resolution.X, 1.0f/Resolution.Y);
    Params->BlurDirection  = FVector2f(1.0f, 0.0f); // horizontal first
    Params->BlurRadius     = BlurRadius;

    FIntVector GroupCount(
        FMath::DivideAndRoundUp(Resolution.X, 8),
        FMath::DivideAndRoundUp(Resolution.Y, 8), 1);

    FComputeShaderUtils::AddPass(GraphBuilder,
        RDG_EVENT_NAME("MyGaussianBlur"), Shader, Params, GroupCount);

    return OutputTexture;
}

Step 2 — .usf Shader (YourProject/Shaders/Private/MyBlur.usf)步骤 2 — .usf 着色器(YourProject/Shaders/Private/MyBlur.usf)

// MyBlur.usf
#include "/Engine/Private/Common.ush"

// Bound by the parameter struct — names MUST match exactly
Texture2D InputTexture;
SamplerState InputSampler;
RWTexture2D<float4> OutputTexture;
float2 InvTextureSize;
float2 BlurDirection;
float BlurRadius;

// THREAD_GROUP_SIZE injected by ModifyCompilationEnvironment
[numthreads(THREAD_GROUP_SIZE, THREAD_GROUP_SIZE, 1)]
void MainCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
    uint2 Pixel = DispatchThreadId.xy;
    float2 UV = (float2(Pixel) + 0.5f) * InvTextureSize;

    // Gaussian kernel (9 taps)
    static const float Weights[9] = { 0.05, 0.09, 0.12, 0.15, 0.18,
                                        0.15, 0.12, 0.09, 0.05 };
    float4 Result = 0;
    UNROLL // tells the compiler to unroll this loop
    for (int i = -4; i <= 4; ++i)
    {
        float2 SampleUV = UV + BlurDirection * (float(i) * BlurRadius * InvTextureSize);
        Result += InputTexture.SampleLevel(InputSampler, SampleUV, 0) * Weights[i + 4];
    }

    OutputTexture[Pixel] = Result;
}

Full Vertex + Pixel Shader Example — Custom Mesh Pass完整顶点 + 像素 Shader 示例 — 自定义网格通道

For a VS+PS pair that draws actual geometry (not full-screen), you typically use FGlobalShader with a simple vertex layout, or hook into the Mesh Drawing Pipeline with FMeshMaterialShader. Here is the simpler full-screen case using a screen-aligned triangle — the most common pattern for post-process effects:

对于绘制实际几何体(而非全屏)的 VS+PS 对,通常使用带简单顶点布局的 FGlobalShader,或通过 FMeshMaterialShader 挂入网格绘制管线。以下是使用屏幕对齐三角形的更简单全屏情况——后处理效果最常见的模式:

// Full-screen VS + PS pair (FGlobalShader, simplest approach)

// C++ declaration
class FMyPostProcessVS : public FGlobalShader {
    DECLARE_GLOBAL_SHADER(FMyPostProcessVS);
    SHADER_USE_PARAMETER_STRUCT(FMyPostProcessVS, FGlobalShader);
    using FParameters = FEmptyShaderParameters; // no vertex-side params needed
    static bool ShouldCompilePermutation(...) { return true; }
};
class FMyPostProcessPS : public FGlobalShader {
    DECLARE_GLOBAL_SHADER(FMyPostProcessPS);
    SHADER_USE_PARAMETER_STRUCT(FMyPostProcessPS, FGlobalShader);
    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER_RDG_TEXTURE(Texture2D, SceneColor)
        SHADER_PARAMETER_SAMPLER(SamplerState, SceneColorSampler)
        SHADER_PARAMETER(FVector4f, Tint)
        RENDER_TARGET_BINDING_SLOTS()
    END_SHADER_PARAMETER_STRUCT()
    static bool ShouldCompilePermutation(...) { return true; }
};
IMPLEMENT_GLOBAL_SHADER(FMyPostProcessVS, "/Project/Private/MyPostProcess.usf", "MainVS", SF_Vertex);
IMPLEMENT_GLOBAL_SHADER(FMyPostProcessPS, "/Project/Private/MyPostProcess.usf", "MainPS", SF_Pixel);

// C++ dispatch
auto* Params = GraphBuilder.AllocParameters<FMyPostProcessPS::FParameters>();
Params->SceneColor       = SceneColorTexture;
Params->SceneColorSampler= TStaticSamplerState<>::GetRHI();
Params->Tint             = FVector4f(1,0.8f,0.6f,1);
Params->RenderTargets[0] = FRenderTargetBinding(OutputTexture, ERenderTargetLoadAction::ENoAction);

GraphBuilder.AddPass(RDG_EVENT_NAME("MyPostProcess"), Params, ERDGPassFlags::Raster,
    [Params](FRHICommandList& RHICmdList)
    {
        // Use FScreenPassPipelineState for easy VS+PS setup
        TShaderMapRef<FMyPostProcessVS> VS(GetGlobalShaderMap(GMaxRHIFeatureLevel));
        TShaderMapRef<FMyPostProcessPS> PS(GetGlobalShaderMap(GMaxRHIFeatureLevel));
        DrawScreenPass(RHICmdList, VS, PS, *Params, Viewport);
    });
// MyPostProcess.usf — the .usf file
#include "/Engine/Private/Common.ush"
#include "/Engine/Private/ScreenPass.ush"

Texture2D SceneColor;
SamplerState SceneColorSampler;
float4 Tint;

// Vertex shader: generates a full-screen triangle from 3 vertices (no VB needed)
void MainVS(
    in  uint VertexId : SV_VertexID,
    out float4 OutPosition : SV_POSITION,
    out float2 OutUV : TEXCOORD0)
{
    // Full-screen triangle trick: 3 vertices, no index buffer
    OutUV.x = (VertexId == 2) ? 2.0f : 0.0f;
    OutUV.y = (VertexId == 1) ? 2.0f : 0.0f;
    OutPosition = float4(OutUV * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);
}

// Pixel shader: sample + tint
float4 MainPS(float4 SVPosition : SV_POSITION, float2 UV : TEXCOORD0) : SV_Target0
{
    float4 Color = SceneColor.Sample(SceneColorSampler, UV);
    return Color * Tint;
}

Custom Material Expressions — HLSL in the Material Editor自定义材质表达式 — 材质编辑器中的 HLSL

The fastest way to add custom HLSL to a material without touching C++. Use the Custom node in the Material Editor (right-click → Custom). Each input pin becomes a function parameter; the code block is the function body.

无需修改 C++ 向材质添加自定义 HLSL 的最快方法。 在材质编辑器中使用 Custom 节点(右键 → Custom)。 每个输入引脚成为函数参数;代码块是函数体。

Basic Custom Node UsageCustom 节点基本用法

// Custom node Code field — this becomes the function body
// Inputs: "UV" (float2), "Roughness" (float)
// Output Type: CMOT Float3 (set in node properties)

// Simple example: procedural checkerboard
float2 Checker = floor(UV * float2(8, 8));
float IsWhite  = fmod(Checker.x + Checker.y, 2.0f);
return float3(IsWhite, IsWhite, IsWhite) * Roughness;

Including Engine Headers in Custom Nodes在 Custom 节点中包含引擎头文件

// You can include engine ush files in Custom node code:
"#include \"/Engine/Private/Random.ush\""
// (put the include as a string literal in the Code field — it will be prepended)

// Actually: UE5 provides "Additional Includes" field in Custom node properties
// Add: /Engine/Private/Random.ush
// Then in code field, call engine functions directly:
float Hash = Rand3DPCG16(uint3(floor(WorldPos))).x / 65535.0f;
return Hash;

Limitations of Custom NodesCustom 节点的限制

Project / Plugin Shader Directory Setup项目 / 插件 Shader 目录配置

Before your .usf files can be found by UE, you must register the virtual path mapping.

在 UE 找到你的 .usf 文件之前,必须注册虚拟路径映射。

Method 1: Module StartupModule() (Recommended)方法 1:Module StartupModule()(推荐)

// MyModule.cpp
void FMyModule::StartupModule()
{
    // Map the virtual path /Project/  →  YourGame/Shaders/
    FString ShaderDir = FPaths::Combine(
        FPaths::ProjectDir(), TEXT("Shaders"));
    AddShaderSourceDirectoryMapping(TEXT("/Project"), ShaderDir);

    // For a plugin instead:
    // FString PluginDir = IPluginManager::Get().FindPlugin("MyPlugin")->GetBaseDir();
    // AddShaderSourceDirectoryMapping(TEXT("/Plugin/MyPlugin"),
    //     FPaths::Combine(PluginDir, TEXT("Shaders")));
}

Method 2: .uplugin / .uproject (Declarative)方法 2:.uplugin / .uproject(声明式)

// YourPlugin.uplugin — add shader directory declaration
{
  "FileVersion": 3,
  "Modules": [{ "Name": "MyPlugin", "Type": "Runtime" }],
  "ShaderSourceDirectories": [
    { "VirtualRoot": "/Plugin/MyPlugin", "LocalPath": "Shaders" }
  ]
}

Recommended Folder Structure推荐目录结构

YourGame/ Shaders/ Private/ ← your implementation .usf files MyBlur.usf MyPostProcess.usf MyEffect.usf Public/ ← headers that other projects/plugins can include MySharedTypes.ush Then in .usf files: #include "/Project/Private/MyBlur.usf" ← your own files #include "/Engine/Private/Common.ush" ← engine files

Hot-Reload & Debugging热重载与调试

Shader Development ModeShader 开发模式

-- In Engine.ini or DefaultEngine.ini --
[DevOptions.Shaders]
bAllowUniqueShaderJobId=True    ; required for shader development

-- Console commands (in-editor or in-game) --
r.ShaderDevelopmentMode=1        ; enable hot-reload on .usf file save
RecompileShaders all             ; recompile all shaders
RecompileShaders /Project/Private/MyBlur.usf   ; recompile one file
RecompileShaders changed         ; recompile only modified files

r.Shaders.Optimize=0             ; disable optimization (for debugger)
r.Shaders.KeepDebugInfo=1        ; keep HLSL source in binary (for RenderDoc/PIX)
r.Shaders.Symbols=1              ; write .pdb shader symbols to disk

-- View compiled HLSL source (useful for material debug) --
r.DumpShaderDebugInfo=1          ; writes HLSL source for every compiled shader
r.DumpShaderDebugShortNames=1    ; shorter filenames
-- Output location: Saved/ShaderDebugInfo/ --

Debugging with RenderDoc / PIX使用 RenderDoc / PIX 调试

  1. Set r.Shaders.Optimize=0 and r.Shaders.KeepDebugInfo=1 before the shader is compiled (in .ini or before startup)
  2. 在 Shader 编译之前设置 r.Shaders.Optimize=0r.Shaders.KeepDebugInfo=1(在 .ini 中或启动前)
  3. Capture a frame in RenderDoc. Find your RDG event name (e.g., "MyGaussianBlur") in the event list.
  4. 在 RenderDoc 中捕获一帧。在事件列表中找到你的 RDG 事件名称(例如 "MyGaussianBlur")。
  5. In RenderDoc: Pipeline State → Compute Shader → "Edit" → opens HLSL source with live edit
  6. 在 RenderDoc 中:Pipeline State → Compute Shader → "Edit" → 打开带实时编辑的 HLSL 源码
  7. Use Mesh Viewer → Source to see the input geometry for a draw call
  8. 使用 Mesh Viewer → Source 查看绘制调用的输入几何体

Shader Printf Debugging (SHADER_PRINT)Shader Printf 调试(SHADER_PRINT)

// In your .usf file — print from GPU to console (UE 5.1+)
#include "/Engine/Private/ShaderPrint.ush"

[numthreads(8,8,1)]
void MainCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
    if (all(DispatchThreadId.xy == uint2(0,0)))  // print once
    {
        FShaderPrintContext Ctx = InitShaderPrintContext(true, float2(0.1,0.5));
        Print(Ctx, TEXT("MyValue="));
        Print(Ctx, MyValue);  // works for float, int, float2/3/4
        Newline(Ctx);
    }
}

// Enable from console: r.ShaderPrint=1

Common USF Pitfalls常见 USF 坑点

Name mismatch crash: The C++ parameter struct member name and the HLSL variable name MUST be identical (case-sensitive). If your struct has SHADER_PARAMETER(float, BlurRadius), the HLSL must declare float BlurRadius;. A name mismatch causes a silent bind failure — the GPU reads garbage. 名称不匹配崩溃:C++ 参数结构体成员名和 HLSL 变量名必须完全相同(区分大小写)。如果结构体有 SHADER_PARAMETER(float, BlurRadius),HLSL 必须声明 float BlurRadius;。名称不匹配会导致静默绑定失败——GPU 读取垃圾数据。
Pitfall坑点Solution解决方法
Shader not found (ensure failure)Shader 找不到(ensure 失败)Check: virtual path registered? File exists at that path? IMPLEMENT_GLOBAL_SHADER called in a .cpp linked by the module?检查:虚拟路径已注册?文件在该路径存在?IMPLEMENT_GLOBAL_SHADER 在模块链接的 .cpp 中调用?
float3 vs float4 mismatchfloat3 与 float4 不匹配C++ FVector3f → HLSL float3, C++ FVector4f → HLSL float4. Always match exactly.C++ FVector3f → HLSL float3,C++ FVector4f → HLSL float4。始终精确匹配。
Texture reads wrong data纹理读取错误数据RDG textures: use SHADER_PARAMETER_RDG_TEXTURE (not plain SHADER_PARAMETER_TEXTURE) for RDG-managed resources.RDG 纹理:对 RDG 管理的资源使用 SHADER_PARAMETER_RDG_TEXTURE(而非普通 SHADER_PARAMETER_TEXTURE)。
Compile error "not found in scope"编译错误"未在作用域中找到"Missing include. Add #include "/Engine/Private/Common.ush" at top.缺少 include。在顶部添加 #include "/Engine/Private/Common.ush"
Permutation count explodes build time排列组合数量爆炸增加构建时间Filter with ShouldCompilePermutation(). Use r.ShaderDevelopmentMode=1 to only compile the current permutation.ShouldCompilePermutation() 过滤。使用 r.ShaderDevelopmentMode=1 只编译当前排列组合。
Changes not picked up after save保存后更改未生效Must have r.ShaderDevelopmentMode=1. Then run RecompileShaders changed in console.必须设置 r.ShaderDevelopmentMode=1。然后在控制台运行 RecompileShaders changed

Source Navigation源码导航

Unreal Engine 5.6 · Engine/Shaders/Private/ · Engine/Source/Runtime/RenderCore/Public/
Source analysis · For learning purposes