[Tutorial] Pixel and Compute Shaders in UE4

As i saw engine source codes, all main nodes shader code written as strings in C++ per node.
And as i understand nodes flow will produce shader file, which compiled as fact.
Any info about it in official documentation?

Afaik, .usf files are compiled for specific platform ex. GLSL

Yay!
But take in to the account, that default nodes will produce the best optimized shader code for a specified platform/API etc.
As i remember all dat stuff are covered in documentation.
Good luck with experiments!

Thanks for the resource! Hard to get concrete explanations of how everything works as a whole.
@zameran I would gladly use the default Material nodes as I love that system but sadly I need a for loop which they have not yet made possible. :frowning:

Hello, i’m not sure if that can help but you can use render target that’s what i’ve done to render a buddhabrot

Hey Guys, so when I’m in PIE the editor freezes for a while and then I get the error “Unreal Engine is exiting due to D3D device being lost.” :frowning: Has anyone ran into problem before? My OS is Windows 10 x64 and my GPU is nVidia Geforce GTX 970 with driver version 382.53. Any suggestion, solutions are much appreciated. Thanks:)

hey,

first of all: extraordinary work, ! :slight_smile:
The github project works just fine for me when I used UE4.16; for UE4.17 however I get some errors, like e.g.
“SetBlendState is not a member of FRHICommandListImmediate” in PixelShaderUsageExample.cpp

Can someone update the project for UE4.17 or give some hints how to do it (I couldn’t find any messages about a deprecated SetBlendState function or which function replaces it)?

okay, wasn’t that easy but did it myself. You can find the upgraded version for UE4.17 here: GitHub - ValentinKraft/UE4ShaderPluginDemo: A tutorial project that shows how to implement HLSL Pixel and Compute shaders in UE4
I created a pull request as well for 's original plugin.

Thank you , I was working on it and after I updated the engine to 4.18.3, I got compiling error HICmdList

Hi, I’m so puzzled with compute shader recently, And I don’t know how to copy the result calculated from compute_shader. I try to copy the TextureRHI resource to a render target. But operation is always failed. could you give me some help? thank you very much.

static void UseComputeShader_RenderThread(
FRHICommandListImmediate& RHICmdList,
FTextureRenderTargetResource* OutputRenderTargetResource,
ERHIFeatureLevel::Type FeatureLevel
)
{
check(IsInRenderingThread());

TShaderMapRef<FMyComputeShader> ComputeShader(GetGlobalShaderMap(FeatureLevel));
RHICmdList.SetComputeShader(ComputeShader->GetComputeShader());

//ComputeShader->SetSurfaces(RHICmdList,)
int32 SizeX = OutputRenderTargetResource->GetSizeX();
int32 SizeY = OutputRenderTargetResource->GetSizeY();
FRHIResourceCreateInfo CreateInfo;

FTexture2DRHIRef Texture = RHICreateTexture2D(SizeX, SizeY, PF_A32B32G32R32F, 1, 1, TexCreate_ShaderResource | TexCreate_UAV, CreateInfo);
FUnorderedAccessViewRHIRef TextureUAV = RHICreateUnorderedAccessView(Texture);
ComputeShader->SetSurfaces(RHICmdList, TextureUAV);

DispatchComputeShader(RHICmdList, *ComputeShader, SizeX/32, SizeY/32, 1);
ComputeShader->UnbindBuffers(RHICmdList);

// Resolve render target.
RHICmdList.CopyToResolveTarget(
Texture,
OutputRenderTargetResource->TextureRHI,
true, FResolveParams());

//create a bitmap
TArray<FColor> Bitmap;

//To access our resource we do a custom read using lockrect
uint32 LolStride = 0;
char* TextureDataPtr = (char*)RHICmdList.LockTexture2D(Texture, 0, EResourceLockMode::RLM_ReadOnly, LolStride, false);

for (uint32 Row = 0; Row < Texture->GetSizeY(); ++Row)
{
uint32* PixelPtr = (uint32*)TextureDataPtr;

//Since we are using our custom UINT format, we need to unpack it here to access the actual colors
for (uint32 Col = 0; Col < Texture->GetSizeX(); ++Col)
{
uint32 EncodedPixel = *PixelPtr;
uint8 r = (EncodedPixel & 0x000000FF);
uint8 g = (EncodedPixel & 0x0000FF00) >> 8;
uint8 b = (EncodedPixel & 0x00FF0000) >> 16;
uint8 a = (EncodedPixel & 0xFF000000) >> 24;
Bitmap.Add(FColor(r, g, b, a));

PixelPtr++;
}

// move to next row:
TextureDataPtr += LolStride;
}

RHICmdList.UnlockTexture2D(Texture, 0, false);

// if the format and texture type is supported
if (Bitmap.Num())
{
// Create screenshot folder if not already present.
IFileManager::Get().MakeDirectory(*FPaths::ScreenShotDir(), true);

const FString ScreenFileName(FPaths::ScreenShotDir() / TEXT(“VisualizeTexture”));

uint32 ExtendXWithMSAA = Bitmap.Num() / Texture->GetSizeY();

// Save the contents of the array to a bitmap file. (24bit only so alpha channel is dropped)
FFileHelper::CreateBitmap(*ScreenFileName, ExtendXWithMSAA, Texture->GetSizeY(), Bitmap.GetData());

UE_LOG(LogConsoleResponse, Display, TEXT(“Content was saved to “%s””), *FPaths::ScreenShotDir());
}
else
{
UE_LOG(LogConsoleResponse, Error, TEXT(“Failed to save BMP, format or texture type is not supported”));
}

}

Also hacked something together with global shaders in a plugin. It’s a fun project on how to get raymarching with true Volume Textures (before engine version 4.20, which supports them natively now). Tested with versions 4.19 & 4.20.

I’ve got an where i’m referring to a global constant parameter in a shared file, and it doesn’t recognise it. Is there a way to force registration of those buffer types before shader compilation?

may be of interest:

https://forums.unrealengine.com/community/community-content-tools-and-tutorials/1560965-shadersplus-infrastructure-for-custom-shaders

Anyone managed to get it to work on 4.21 ? Wanted to update my project but my working 4.20 plugin isn’t working anymore.

hello from the future, running my projects on 4.22 and 4.23. Looking to use compute shaders to lighten the load of my AI horde / crowd sim. Has anyone update to recent engine version or found something new to use?

I am fairly certain that the existing UE crowd manager uses GPU flocking.

Chris Murphy showed us a demo of something else recently though that I think was compute accelerated for large crowd skeletal animation. I think it’s used in fortnite single player to help with animating large numbers of enemies, it’ll dynamically switch from compute to CPU as zombies break from the proscribed animation or something. That is probably 4.23 at least. Might be worth asking him on Twitter about it: https://twitter.com/HighlySpammable

I’ve been using Shaderplus as a foundation to run clean compute shader code for almost a year, totally underrated and works like a charm.

https://forums.unrealengine.com/comm…custom-shaders

I wrote another tutorial, based on Temarans work: Compute Shader in Unreal [Tutorial] – Valentin Kraft's Portfolio

Bit late to the compute shader party but I’m trying to use plugin as a base for my own implementation. Using the 4.24 version of plugin.

The main difference for me is that I do not wish to have a texture as the output of my shader but a data array and using a texture as input to the shader. And currently stuck in figuring out where I correctly can read the data back out on the CPU game thread after the renderthread has completed it’s work. I believe the input texture is working as intended but I’m sure I’m both making mistakes on the output buffer side of things as well as having no clue how to correctly read out the data. Anyone that can put me back on the right track?

So the main differences I’ve made so far are from the original plugin:

ComputeShaderExample.cpp:


...

	BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
		SHADER_PARAMETER_TEXTURE(Texture2D<FVector4>, InputTexture)
                ...
		SHADER_PARAMETER_UAV(RWStructuredBuffer<FVector4>, OutputBuffer)
	END_SHADER_PARAMETER_STRUCT()

...

void FComputeShaderExample::RunComputeShader_RenderThread(FRHICommandListImmediate& RHICmdList, const FShaderUsageExampleParameters& DrawParameters, FTextureRHIRef inputTexture, FUnorderedAccessViewRHIRef outputBuffer_UAV)
{
	QUICK_SCOPE_CYCLE_COUNTER(STAT_ShaderPlugin_ComputeShader); // Used to gather CPU profiling data for the UE4 session frontend
	SCOPED_DRAW_EVENT(RHICmdList, ShaderPlugin_Compute); // Used to profile GPU activity and add metadata to be consumed by for example RenderDoc

	UnbindRenderTargets(RHICmdList);
	
	FComputeShaderExampleCS::FParameters PassParameters;
	PassParameters.InputTexture = inputTexture;
        ...
	PassParameters.OutputBuffer = outputBuffer_UAV;

	TShaderMapRef<FComputeShaderExampleCS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel));
	FComputeShaderUtils::Dispatch(RHICmdList, *ComputeShader, PassParameters, 
								FIntVector(FMath::DivideAndRoundUp(DrawParameters.GetRenderTargetSize().X, NUM_THREADS_PER_GROUP_DIMENSION),
										   FMath::DivideAndRoundUp(DrawParameters.GetRenderTargetSize().Y, NUM_THREADS_PER_GROUP_DIMENSION), 1));
}

ShaderDeclarationDemoModule.cpp:

void FShaderDeclarationDemoModule::Draw_RenderThread(const FShaderUsageExampleParameters& DrawParameters)
{
	check(IsInRenderingThread());

	if (!DrawParameters.RenderTarget)
	{
		return;
	}

	FRHICommandListImmediate& RHICmdList = GRHICommandList.GetImmediateCommandList();

	QUICK_SCOPE_CYCLE_COUNTER(STAT_ShaderPlugin_Render); // Used to gather CPU profiling data for the UE4 session frontend
	SCOPED_DRAW_EVENT(RHICmdList, ShaderPlugin_Render); // Used to profile GPU activity and add metadata to be consumed by for example RenderDoc

	OutputBuffer_array_.SetNum(32);
	OutputBuffer_resource_.ResourceArray = &OutputBuffer_array_;
	OutputBuffer_buffer_ = RHICreateStructuredBuffer(sizeof(float), sizeof(FVector4) * 32, BUF_ShaderResource | BUF_UnorderedAccess, OutputBuffer_resource_);
	OutputBuffer_UAV_ = RHICreateUnorderedAccessView(OutputBuffer_buffer_, /* bool bUseUAVCounter */ false, /* bool bAppendBuffer */ false);


	FComputeShaderExample::RunComputeShader_RenderThread(RHICmdList, DrawParameters, DrawParameters.RenderTarget->GetRenderTargetResource()->TextureRHI, OutputBuffer_UAV_);
}

ShaderDeclarationDemoModule.h:


struct FShaderUsageExampleParameters
{
	UTextureRenderTarget2D* RenderTarget;
        ...
	TArray<FVector4> OutputBuffer;

	FIntPoint GetRenderTargetSize() const
	{
		return CachedRenderTargetSize;
	}

	FShaderUsageExampleParameters()	{ }
	FShaderUsageExampleParameters(UTextureRenderTarget2D* InRenderTarget, 
		TArray<FVector4> OutputBuffer)
		: RenderTarget(InRenderTarget),
		  OutputBuffer(OutputBuffer),
		  ...
	{
		CachedRenderTargetSize = InRenderTargetDepth ? FIntPoint(InRenderTarget->SizeX, InRenderTarget->SizeY) : FIntPoint::ZeroValue;
	}

...

private:
	TResourceArray<FVector4> OutputBuffer_array_;
	FRHIResourceCreateInfo OutputBuffer_resource_;
	FStructuredBufferRHIRef OutputBuffer_buffer_;
	FUnorderedAccessViewRHIRef OutputBuffer_UAV_;

ShaderUsageDemoCharacter.cpp


AShaderUsageDemoCharacter::AShaderUsageDemoCharacter()
{
	...
	capture_2D_ = CreateDefaultSubobject<USceneCaptureComponent2D>(TEXT("CaptureComp"));
}

void AShaderUsageDemoCharacter::PostInitializeComponents()
{
	Super::PostInitializeComponents();
	render_target_2D_ = NewObject<UTextureRenderTarget2D>();
	capture_2D_->SetRelativeRotation(FRotator(0, 0, 0));
	capture_2D_->SetRelativeLocation(FVector(0, 0, 0));
	capture_2D_->AttachTo(->RootComponent);
	capture_2D_->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
	render_target_2D_->InitCustomFormat(1024, 1024, PF_B8G8R8A8, true);
	capture_2D_->TextureTarget = render_target_2D_;
	capture_2D_->bAlwaysPersistRenderingState = true;
	capture_2D_->bCaptureEveryFrame = false;
	capture_2D_->bCaptureOnMovement = false;
	capture_2D_->bUseCustomProjectionMatrix = false;
}
...
void AShaderUsageDemoCharacter::Tick(float DeltaSeconds)
	...
	capture_2D_->CaptureScene();
	FShaderUsageExampleParameters DrawParameters(render_target_2D_,  output_buffer_);
	{
		...
	}
	FShaderDeclarationDemoModule::Get().UpdateParameters(DrawParameters);

	// HERE I WOULD LIKE TO READ OUT THE DATA BUFFER OF THE SHADER

ShaderUsageDemoCharacter.h

...
public:
	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	class USceneCaptureComponent2D* capture_2D_;

	UPROPERTY(BlueprintReadWrite, EditAnywhere)
	class UTextureRenderTarget2D* render_target_2D_;

	TArray<FVector4> output_buffer_;

Hi Temaran, is it possible to use your plugin for multi-passes like in Unity’s HLSL shaders? I.e suppose I have SomeShaderA that does most of the pixel shading but then SomeShaderB that does an Outline using extruded normals; does this work to additively blend them? I.e the Vertex information is merely copied between the two shaders such that I won’t need to use the Duplicated Mesh solution or a Post Process shader as I see? It seems like in Unity shaders you can just do two shader passes and preserve the original mesh geometry.