USF / HLSL Guide — Writing Custom Shaders in UE5USF / HLSL 编写指南 — 在 UE5 中编写自定义 Shader
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 中从不使用绝对路径——始终使用虚拟形式。
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.ush | Platform/compiler macros, HLSL version detection平台/编译器宏,HLSL 版本检测 |
| /Engine/Private/Common.ush | Start here. Math utilities, PI, saturate, lerp, rcp, pow2/4/5, BRANCH, UNROLL, LOOP从这里开始。数学工具函数、常量、控制流提示宏 |
| /Engine/Private/DeferredShadingCommon.ush | FGBufferData struct, GetGBufferData(), DecodeGBuffer() — essential for deferred passesGBuffer 数据结构和解码函数——延迟通道必须 |
| /Engine/Private/GBufferCommon.ush | GBuffer channel layout constants, encode/decode helpersGBuffer 通道布局常量,编码/解码辅助函数 |
| /Engine/Private/ShadingModels.ush | All BRDF functions: DefaultLitBxDF, HairBxDF, etc.所有 BRDF 函数 |
| /Engine/Private/SceneTexturesCommon.ush | SceneDepthTexture, SceneColorTexture, velocity, GBuffer SRVs场景深度/颜色/速度/GBuffer 纹理绑定 |
| /Engine/Private/Random.ush | Hash functions: RandFast(), Rand3DPCG16(), blue noise哈希/随机函数 |
| /Engine/Private/BRDF.ush | Low-level BRDF building blocks: D_GGX, Vis_SmithJoint, F_Schlick底层 BRDF 构建块 |
| /Engine/Private/ScreenPass.ush | Full-screen pass helpers: FScreenTransform, UV transforms for post-process全屏通道辅助函数,后处理 UV 变换 |
| /Engine/Private/ComputeShaderUtils.ush | Compute 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可用数据 |
|---|---|---|
| FGlobalShader | Engine-wide effects: post-process, RDG compute, custom passes. No material, no mesh.引擎级效果:后处理、RDG 计算、自定义通道。无材质,无网格。 | View UB, custom parameters onlyView UB、自定义参数 |
| FMaterialShader | Shaders that need material parameters (BaseColor, Roughness etc.) but not mesh vertex data.需要材质参数但不需要网格顶点数据的 Shader。 | +Material parameters, Material UB+材质参数、Material UB |
| FMeshMaterialShader | Vertex/Pixel shaders in the mesh drawing pipeline. Full access to vertex factory, material, and view.网格绘制管线中的 VS/PS。完整访问顶点工厂、材质和视图。 | +Vertex factory, local-to-world matrix+顶点工厂、局部到世界矩阵 |
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);
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
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 节点的限制
- Cannot write to UAVs (no RWTextures, no RW buffers) — read-only
- 不能写入 UAV(没有 RWTexture,没有 RW 缓冲区)——只读
- Cannot declare new textures — only use textures passed as inputs
- 不能声明新纹理——只能使用作为输入传入的纹理
- No control over the render pass or shader stage — always runs in the material's normal stage
- 无法控制渲染通道或 Shader 阶段——始终在材质的正常阶段运行
- Permutation-incompatible — the custom HLSL is compiled into every permutation of the material
- 与排列组合不兼容——自定义 HLSL 被编译进材质的每个排列组合
- For anything more complex, write a proper FGlobalShader + RDG pass
- 对于更复杂的情况,编写适当的 FGlobalShader + RDG 通道
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推荐目录结构
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 调试
- Set
r.Shaders.Optimize=0andr.Shaders.KeepDebugInfo=1before the shader is compiled (in .ini or before startup) - 在 Shader 编译之前设置
r.Shaders.Optimize=0和r.Shaders.KeepDebugInfo=1(在 .ini 中或启动前) - Capture a frame in RenderDoc. Find your RDG event name (e.g., "MyGaussianBlur") in the event list.
- 在 RenderDoc 中捕获一帧。在事件列表中找到你的 RDG 事件名称(例如 "MyGaussianBlur")。
- In RenderDoc: Pipeline State → Compute Shader → "Edit" → opens HLSL source with live edit
- 在 RenderDoc 中:Pipeline State → Compute Shader → "Edit" → 打开带实时编辑的 HLSL 源码
- Use
Mesh Viewer → Sourceto see the input geometry for a draw call - 使用
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 坑点
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源码导航
- /Engine/Shaders/Private/Common.ushStart every .usf here. Math, BRANCH/UNROLL macros, platform abstractions.每个 .usf 的起点。数学函数、控制流宏、平台抽象。
- /Engine/Shaders/Private/BRDF.ush
D_GGX,Vis_SmithJointApprox,F_Schlick,Diffuse_Lambert底层 BRDF 函数实现 - /Engine/Shaders/Private/DeferredShadingCommon.ush
FGBufferData,GetGBufferData()— GBuffer read helpersGBuffer 读取辅助函数 - /Engine/Shaders/Private/Random.ushHashing:
Rand3DPCG16,RandFast, blue noise LUT哈希/随机函数,蓝噪声 LUT - /Engine/Shaders/Private/ScreenPass.ushPost-process UV transforms,
FScreenTransform后处理 UV 变换 - /Engine/Shaders/Private/ShaderPrint.ushGPU printf:
Print(),PrintNewline()— enable with r.ShaderPrint=1GPU printf:Print()、PrintNewline()——用 r.ShaderPrint=1 启用 - Runtime/RenderCore/Public/GlobalShader.h
FGlobalShaderbase class,DECLARE_GLOBAL_SHADER,IMPLEMENT_GLOBAL_SHADER全局 Shader 基类和注册宏 - Runtime/RenderCore/Public/ShaderParameterMacros.hAll
SHADER_PARAMETER_*macros — parameter struct binding system所有SHADER_PARAMETER_*宏——参数结构体绑定系统 - Runtime/RenderCore/Public/ShaderPermutation.h
SHADER_PERMUTATION_BOOL,SHADER_PERMUTATION_INT,TShaderPermutationDomain排列组合域系统 - Runtime/RenderCore/Public/RenderGraphUtils.h
FComputeShaderUtils::AddPass(),AddFullscreenPass(),AddCopyTexturePass()RDG 集成工具函数