[RDG] Transfer Result of Compute Shader to Material (RenderTarget?)

Hello everyone,

I spend my Sunday trying to understand how to get custom Compute Shaders to work in UE4.
I found some tutorials that I followed and while it worked for the first few tries, I ran into issues with resources not resetting properly.
After asking for help on Unreal Slackers, I was told that one should use RDG nowadays.

Now,… there isn’t much documentation about this, despite some Crash Course and a Blog Post that uses the same Course to explain things a little bit more.

What I have by now:

  • A simple TestShader.utf, which tries to map a pseudo random number to each pixel (so basically generate noise), which has one parameter for the output RWTexture2D<float> OutputTexture;
  • An Actor that, on Tick (?) utilizes ENQUEUE_RENDER_COMMAND to create a FRDGBuilder (because I don’t know where else I would get the RHICmdList from).

Now in that Render Command, I create a FRDGTextureDesc, a FRDGTexture* and a FRDGTextureUAV*. I then grab my TestShader, call GraphBuilder.AddPass, in which I call FComputeShaderUtils::Dispatch. In the end, I call GraphBuilder.Execute(). So far so good, all of this compiles and doesn’t crash when called.

I will add the actual code further down so you can have a look.

My main questions currently are:

  1. How do I update the Params that I pass into the Shader? I was able to do that with the non-RDG version. Do I just call this on Tick and pass in the updated variables via the ENQUEUE_RENDER_COMMAND?
  2. How do I actually use the result of this Compute Shader? In the non-RDG version, the example copied the Shader Output into a RenderTarget that is used in a Material on a StaticMeshComponent. How do I do this with RDG? I tried using AddCopyTexturePass, but this expects two FRDGTexture* inputs and I only have one. The other is a simple UTextureRenderTarget2D pointer. I know that they are sort of communicating with the RHI resource, but I have no idea how to convert between them.

Here is the actual code:


#include "/Engine/Public/Platform.ush"

RWTexture2D<float> OutputTexture;

uint hash(uint state)
    state ^= 2747636419u;
    state *= 2654435769u;
    state ^= state >> 16;
    state *= 2654435769u;
    state ^= state >> 16;
    state *= 2654435769u;
    return state;

float scaleToRange01(uint number)
    return (float)number / 4294967295.0;

void MainComputeShader(uint3 id : SV_DispatchThreadID)
    const float2 pixel = id.xy;

    const float randomNumber = scaleToRange01(hash(pixel));

    OutputTexture[id.xy] = randomNumber;


#pragma once

#include "CoreMinimal.h"
#include "GlobalShader.h"
#include "RenderGraphUtils.h"
#include "ShaderParameterStruct.h"


class SHADERDECLARATIONS_API FTestShader : public FGlobalShader

    SHADER_USE_PARAMETER_STRUCT(FTestShader, FGlobalShader);

    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);

        OutEnvironment.SetDefine(TEXT("THREADGROUPSIZE_Z"), 1);

        SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture2D<float>, OutputTexture)


#include "TestShader.h"

IMPLEMENT_GLOBAL_SHADER(FTestShader, "/CustomShaders/TestShader.usf", "MainComputeShader", SF_Compute);

And the AShaderTestActor::Tick function in which I call the ENQUEUE_RENDER_COMMAND (not sure this should be on tick).

void AShaderTestActor::Tick(float DeltaTime)

        [this] (FRHICommandListImmediate& RHICmdList)
            FRDGBuilder GraphBuilder(RHICmdList);

            FRDGTextureDesc OutputTextureDesc = FRDGTextureDesc::Create2DDesc(
                // Render Target Size for now hardcoded
                FIntPoint(800, 800),
                TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_UAV,

            FRDGTextureRef OutputTexture = GraphBuilder.CreateTexture(OutputTextureDesc, TEXT("OutputTexture"));
            FRDGTextureUAVRef OutputTextureUAV = GraphBuilder.CreateUAV(OutputTexture);

            FTestShader::FParameters* PassParams = GraphBuilder.AllocParameters<FTestShader::FParameters>();
            PassParams->OutputTexture = OutputTextureUAV;

            TShaderMapRef<FTestShader> TestShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));

                RDG_EVENT_NAME("TestShader 800x800"),
                [PassParams, TestShader](FRHICommandList& RHICmdList)
                    FComputeShaderUtils::Dispatch(RHICmdList, TestShader, *PassParams, FIntVector(800, 800, 1));


I’ve tried searching for Text and Video Tutorials, I tried looking for usage in the Engine Source Code. I’m a bit at a loss.

If anyone could explain to me how I get my created noise visibly into a material and how I can update the params per frame, that would be great.
Will probably also help others in the future.

Kind regards,