UE4 Shader Compiler Debug Skill
Handles shader compilation failures in Unreal Engine 4 by providing systematic diagnosis and fixes.
When to Use
When shader compilation errors appear in the form:
error X3000: unrecognized identifiererror X3003: redefinition oferror X3004: undeclared identifiererror X3017: cannot implicitly convert
Core Problem Patterns
Pattern 1: Multiple Include Redefinition
Symptom: error X3003: redefinition of '<identifier>'
Root Cause: A .ush file is included multiple times (via different #if USE_X branches in the parent .usf), but the .ush file lacks a global include guard.
Diagnosis:
# Find all includes of the problematic file
grep -n "#include.*ShadowMomentFiltering.ush" Engine/Shaders/Private/*.usf
Fix: Add a global include guard at the top of the .ush file:
// At very top, after copyright header
#ifndef SHADOW_MOMENT_FILTERING_USH_INCLUDED
#define SHADOW_MOMENT_FILTERING_USH_INCLUDED
// ... all content ...
#endif // SHADOW_MOMENT_FILTERING_USH_INCLUDED
Pattern 2: Conditional Define Inside Guarded Block
Symptom: error X3004: undeclared identifier '<function>'
Root Cause: #ifndef USE_X that sets a default is nested inside #if USE_Y, so when USE_Y=0, USE_X never gets its default value.
Diagnosis: Check structure:
#if USE_Y // Line 100
#ifndef USE_X // Line 101 - nested INSIDE USE_Y block!
#define USE_X 0
#endif
struct X { ... }; // Defined only when USE_Y=1
#endif
Fix: Move #ifndef USE_X before the #if USE_Y block:
#ifndef USE_X
#define USE_X 0
#endif
#if USE_Y
// Now USE_X is guaranteed to be defined regardless of USE_Y
#endif
Pattern 3: Variable Scope Outside Conditional
Symptom: error X3004: undeclared identifier '<variable>'
Root Cause: Variable declared inside #if X block but used outside it.
void MainPS(...) {
#if USE_M4
float Shadow; // Only declared when USE_M4=1
#endif
Shadow = 1.0; // Error when USE_M4=0
}
Fix: Declare variable before the conditional:
void MainPS(...) {
float Shadow = 0; // Always declared
#if USE_M4
Shadow = CalculateM4Shadow(...);
#endif
}
Pattern 4: Type Mismatch
Symptom: error X3017: cannot implicitly convert from 'float2' to 'float3'
Fix: Use correct vector construction:
// Wrong
M4Settings.SvPosition = SVPos.xy;
// Correct
M4Settings.SvPosition = SVPos.xyx;
Pattern 5: Missing C++ Compilation Environment Setup
Symptom: USE_X is always 0 despite shader having #if USE_X blocks
Root Cause: C++ ModifyCompilationEnvironment doesn't set the USE_X define.
Diagnosis: Check ShadowRendering.h or equivalent:
static void ModifyCompilationEnvironment(...) {
OutEnvironment.SetDefine(TEXT("USE_M4_POINT_LIGHT"), 1);
OutEnvironment.SetDefine(TEXT("USE_M4"), 1);
}
Systematic Debugging Process
Step 1: Extract Error Identity
From the log line:
error X3003: redefinition of 'quant1_m'
at ShadowMomentFiltering.ush(334,23-30)
Extract:
- Error type:
X3003= redefinition - Identifier:
quant1_m - File:
ShadowMomentFiltering.ush - Line: 334
Step 2: Find All Definitions
grep -n "quant1_m" Engine/Shaders/Private/ShadowMomentFiltering.ush
Step 3: Check Include Chain
# Which files include this .ush?
grep -rn "#include.*ShadowMomentFiltering.ush" Engine/Shaders/
# Check the including file's conditional structure
grep -n "#if USE_M4" Engine/Shaders/Private/ShadowProjectionPixelShader.usf
Step 4: Verify Include Guards
For each .ush file that's included multiple times:
- Does it have a global
#ifndef X_INCLUDEDguard at top? - Does it have
#endifat bottom? - Are variable/function definitions inside conditional blocks that might not execute?
Step 5: Check Preprocessor Flow
When a file is included twice:
1st include: USE_M4=1, USE_M4_POINT_LIGHT=0
2nd include: USE_M4=1 (already defined!), USE_M4_POINT_LIGHT=1
If the file has no global guard, all code runs twice → redefinition.
Diagnostic Commands
# Find all includes of a problematic .ush
grep -rn "#include.*<filename.ush>" Engine/Shaders/Private/
# Find all #ifndef guards in a file
grep -n "#ifndef\|#define.*_INCLUDED\|#endif" Engine/Shaders/Private/problematic.ush
# Find all definitions of an identifier
grep -rn "static const.*identifier\|identifier =" Engine/Shaders/Private/
# Check C++ defines for shader
grep -rn "SetDefine.*USE_M4" Engine/Source/Runtime/Renderer/Private/
Fix Priority
| Priority | Issue | Fix Complexity |
|---|---|---|
| 1 | Missing global include guard | Low - add #ifndef X_INCLUDED wrapper |
| 2 | Define inside conditional | Low - move #ifndef before outer #if |
| 3 | Variable scope | Low - declare before #if block |
| 4 | Type mismatch | Low - correct vector construction |
| 5 | Missing C++ define | Medium - add to ModifyCompilationEnvironment |
Common Pitfalls
- Using
#if 0to "disable" code - Doesn't work if other includes path to it - Individual
#ifndeffor defaults - These don't prevent re-processing, only set defaults once - Thinking
#if Xis a guard - It's conditional compilation, not include protection - Static const variables - These ARE definitions, need global guard protection
- Nested
#ifndefinside#if- The outer#ifmay skip the inner#ifndef, leaving defines unset
Success Criteria
All shader compilers complete with zero errors:
[0]LogShaderCompilers: Warning: 0 Shader compiler errors
Key Files
| File | Purpose |
|---|---|
ShadowProjectionPixelShader.usf |
Main shadow projection - includes M4 conditionally |
ShadowMomentFiltering.ush |
M4 functions - must have global guard |
ShadowRendering.h |
C++ sets USE_M4_POINT_LIGHT, USE_M4 defines |
ShadowDepthVertexShader.usf |
Depth pass for shadows |
ShadowDepthPixelShader.usf |
Pixel shader for shadow depth |