Shaders#

Unreal Shading language is HLSL.

  • .ush: Unreal Shader Headers

    • included by other USH or USF files

  • .usf: Unreal Shader Format

    • should be private data only

    • should contain shader entry points i.e custom shaders.

Write HLSL functions for your materials#

To write shader code directly, you can add a custom shader path in your project module.

// Maps ``/Gamekit`` to ``/path/to/project/Gamekit/Shaders``
void FGamekitModule::StartupModule() {
    // GamekitShaders
    FString ShaderDirectory = FPaths::Combine(FPaths::ProjectPluginsDir(), TEXT("<ProjectFolderName>"), TEXT("<ShaderFolder>>"));

    // Make sure the mapping does not exist before adding it
    if (!AllShaderSourceDirectoryMappings().Contains("/<ShaderFolderShortcut>")){
        AddShaderSourceDirectoryMapping("/<ShaderFolderShortcut>", ShaderDirectory);
    }
}

void FGamekitModule::ShutdownModule() {
        ResetAllShaderSourceDirectoryMappings();
}

To include a Gamekit shader file simply add /<ShaderFolderShortcut>/<ShaderFile: fire>.ush

Examples#

Gaussian Blur#

SamplerState TexSampler;

//! Simple 3x3 Gaussian Kernel
float3 GaussianBlur(Texture2D Tex, float2 UV, float Distance) {
    float3 newSample =
         Texture2DSample(Tex, TexSampler, UV + float2(-1,  1) * Distance) * 1.f +
         Texture2DSample(Tex, TexSampler, UV + float2( 0,  1) * Distance) * 2.f +
         Texture2DSample(Tex, TexSampler, UV + float2( 1,  1) * Distance) * 1.f +
         Texture2DSample(Tex, TexSampler, UV + float2(-1,  0) * Distance) * 2.f +
         Texture2DSample(Tex, TexSampler, UV + float2( 0,  0) * Distance) * 4.f +
         Texture2DSample(Tex, TexSampler, UV + float2( 1,  0) * Distance) * 2.f +
         Texture2DSample(Tex, TexSampler, UV + float2(-1, -1) * Distance) * 1.f +
         Texture2DSample(Tex, TexSampler, UV + float2( 0, -1) * Distance) * 2.f +
         Texture2DSample(Tex, TexSampler, UV + float2( 1, -1) * Distance) * 1.f;

    return newSample / 16.f;
}

Custom Shaders#

To follow this part you will need to do the steps described in the previous section.

Warning

Global Shader compile error will make the editor crash during load up

Warning

Module using global shaders needs to be configured to load during the PostConfigInit phase. If not the loading will crash with a criptic error message about the OS not being able to load your library.

"Modules": [
    {
        "Name": "Gamekit",
        "Type": "Runtime",
        "LoadingPhase": "PostConfigInit",
        "WhitelistPlatforms": [ "Win64" ]
    }
],
 class FUpscalingShader : public FGlobalShader
 {
 public:
     DECLARE_GLOBAL_SHADER(FUpscalingShader);
     SHADER_USE_PARAMETER_STRUCT(FUpscalingShader, FGlobalShader);

     BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
         SHADER_PARAMETER_TEXTURE(Texture2D<uint>, InputTexture)  // Read Only Texture
         SHADER_PARAMETER_UAV(RWTexture2D<uint>, OutputTexture)   // Read Write Texture
         SHADER_PARAMETER(FIntPoint, Dimensions)
         SHADER_PARAMETER(UINT, TimeStamp)
         SHADER_PARAMETER(UINT, Multiplier)
     END_SHADER_PARAMETER_STRUCT()

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

     static inline void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
     {
         FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);

         // Preprocessor defines
         OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_X"), NUM_THREADS_PER_GROUP_DIMENSION);
         OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Y"), NUM_THREADS_PER_GROUP_DIMENSION);
         OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Z"), 1);
     }
 };

IMPLEMENT_GLOBAL_SHADER(
    FUpscalingShader,            // Shader Type
    "/Gamekit/Upscaling.usf",    // Shader File
    "MainComputeShader",         // Shader Entry point
    SF_Compute                   // Shader Kind (Vertex, Hull, Domain, Pixel, Geometry, Compute, RayGen, RayMiss, RayHitGroup, RayCallable)
 );
ReserveRenderTargets(RHICmdList);
CopyInputTextureToInputTarget(RHICmdList);

FUpscalingShader::FParameters PassParameters;
PassParameters.OutputTexture = ComputeShaderOutput->GetRenderTargetItem().UAV;
PassParameters.InputTexture  = ComputeShaderInput->GetRenderTargetItem().ShaderResourceTexture;
PassParameters.Dimensions    = CachedParams.OriginalSize;
PassParameters.TimeStamp     = CachedParams.TimeStamp;
PassParameters.Multiplier    = CachedParams.Multiplier;

TShaderMapRef<FUpscalingShader> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));

FComputeShaderUtils::Dispatch(
    RHICmdList,
    ComputeShader,
    PassParameters,
    FIntVector(
        FMath::DivideAndRoundUp(CachedParams.OriginalSize.X, NUM_THREADS_PER_GROUP_DIMENSION),
        FMath::DivideAndRoundUp(CachedParams.OriginalSize.Y, NUM_THREADS_PER_GROUP_DIMENSION),
        1
    )
);

CopyOutputTargetToOutputTexture(RHICmdList);

See UnrealEngine\Engine\Source\Runtime\RHI\Public\RHIDefinitions.h for the ETextureCreateFlags enum.

FRHICommandListImmediate& RHICmdList = FRHICommandListExecutor::GetImmediateCommandList();

// Create 2D texture description for reading
FPooledRenderTargetDesc InputTargetDesc = FPooledRenderTargetDesc::Create2DDesc(
    FIntPoint(TileSize, TileSize), // FIntPoint           InExtent
    PF_G16,                        // EPixelFormat        InFormat
    FClearValueBinding::None,      // FCLearValueBinding  InClearValue
    TexCreate_None,                // ETextureCreateFlags InFlags
    TexCreate_ShaderResource,      // ETextureCreateFlags InTargetableFlags
    false                          // bool                bInForceSeparateTargetAndShaderResource
                                   // uint16              InNumMips             = 1
                                   // bool                InAutowritable        = true
                                   // bool                InCreateRTWriteMask   = false
                                   // bool                InCreateFmask         = false
);

// Get the Texture
TRefCountPtr<IPooledRenderTarget> InputTarget;
GRenderTargetPool.FindFreeElement(
    RHICmdList,           // FRHICommandList&                   RHICmdList
    InputTargetDesc,      // const FPooledRenderTargetDesc&     InputDesc
    InputTarget,          // TRefCountPtr<IPooledRenderTarget>& Out
    TEXT("InputTarget")   // const TCHAR*                       InDebugName
                          // ERenderTargetTransience            TransienceHint          = ERenderTargetTransience::Transient
                          // bool                               bDeferTextureAllocation = false
);

Initialize Input Target#

// Copy your input texture to the target texture
RHICmdList.CopyTexture(
    GetTextureRHI(CachedParams.OriginalTexture),
    ComputeShaderInput->GetRenderTargetItem().ShaderResourceTexture,
    FRHICopyTextureInfo()
);
// Create 2D texture description for writing
FPooledRenderTargetDesc OutputTargetDesc = FPooledRenderTargetDesc::Create2DDesc(
    FIntPoint(NumTilesX, NumTilesY),
    PF_R8G8B8A8,
    FClearValueBinding::None,
    TexCreate_None,
    TexCreate_UAV,
    false,
);

TRefCountPtr<IPooledRenderTarget> OutputTarget;
GRenderTargetPool.FindFreeElement(
    RHICmdList,
    OutputTargetDesc,
    OutputTarget,
    TEXT("OutputTarget")
);

Retrive Output#

// Copy the output target to the output texture
RHICmdList.CopyTexture(
    ComputeShaderOutput->GetRenderTargetItem().ShaderResourceTexture,
     GetTextureRHI(CachedParams.UpscaledTexture),
    FRHICopyTextureInfo()
);

Use Custom Shaders as materials#

Shader Parameters#

Common abbreviation:

  • RDG: render graph

  • UAV: unordered access view

  • SRV: Shader Resource View

Parameters:

  • SHADER_PARAMETER_ARRAY(float, MyScalarArray, [8])

  • SHADER_PARAMETER_TEXTURE(Texture2D, MyTexture)

  • SHADER_PARAMETER_SRV(Texture2D, MySRV)

  • SHADER_PARAMETER_UAV(Texture2D, MyUAV)

  • SHADER_PARAMETER_SAMPLER(SamplerState, MySampler)

  • SHADER_PARAMETER_RDG_TEXTURE(Texture2D, MyTexture)

  • SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, MySRV)

  • SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D, MyUAV)

  • SHADER_PARAMETER_RDG_BUFFER(Buffer<float4>, MyBuffer)

  • SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<float4>, MySRV)

  • SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer<float4>, MyUAV)

  • SHADER_PARAMETER_RDG_UNIFORM_BUFFER(FMyStruct, MemberName)

  • SHADER_PARAMETER_RDG_BUFFER_UPLOAD

  • RDG_BUFFER_ACCESS(MyBuffer)

  • RDG_BUFFER_ACCESS_DYNAMIC

  • RDG_TEXTURE_ACCESS

  • RDG_TEXTURE_ACCESS_DYNAMIC

Common Errors#

[2022.02.19-18.32.59:344][522]LogWindows: Windows GetLastError: The operation completed successfully. (0)
[2022.02.19-18.32.59:345][522]LogWindows: Error: === Critical error: ===
[2022.02.19-18.32.59:345][522]LogWindows: Error:
[2022.02.19-18.32.59:345][522]LogWindows: Error: Fatal error: [File:C:/opt/UnrealEngine/Engine/Source/Runtime/RenderCore/Private/RenderingThread.cpp] [Line: 902]
[2022.02.19-18.32.59:345][522]LogWindows: Error: Rendering thread exception:
[2022.02.19-18.32.59:345][522]LogWindows: Error: Assertion failed: !GRDGInExecutePassScope [File:C:/opt/UnrealEngine/Engine/Source/Runtime/RenderCore/Private/RenderGraphValidation.cpp] [Line: 512]
[2022.02.19-18.32.59:345][522]LogWindows: Error: Render graph is being executed recursively. This usually means a separate FRDGBuilder instance was created inside of an executing pass.

Reference#