Unable to get the raytraceacceleration structure in a compute shader

I’m attempting to create a compute shader which uses raytracing to return the point where each ray hits a piece of geometry in the scene. However, I am having an issue where I am unable to pass the raytraceacceleration structure into the shader when it is run. I have done some research however, all of the examples I can find are out of date since 5.6 as RayTracingScene→GetLayerView needs two inputs now.

Any help is appreciated!

You can obtain the necessary information from ViewInfo.

FScene* Scene = View.Family->Scene->GetRenderScene();
const FRayTracingScene& RayTracingScene = Scene->RayTracingScene;
const FViewInfo& ViewInfo = static_cast<const FViewInfo&>(View);
PassParameters->TLAS = RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base, ViewInfo.GetRayTracingSceneViewHandle());

This is an example of code that works in UE5.7. It only includes a pixel shader and a ray tracing shader, but I think it will be helpful as a reference.

Clarify this if you can: so, you’re using a compute shader to query an RT shader that does the acceleration structure traversal in hardware, or you’re using a compute shader to implement a software triangle RT solution? And is this for lighting, for sound, for physics?

So I want to essentially use a compute shader to compute a line trace and return the hit point but using RT since I want to be able to hit objects which have no collision in the scene but are still visible such as nanite trees if that’s possible. I’m trying to recreate similar functionality to a lidar sensor.

How am I able to get the View variable here in my code?

Here is the header file for my shader which I created using a tutorial on how to create a compute shader in ue5. I have attempted to get the accelleration structure here but it gives an error that update has not been run.

The .h file

#pragma once

#include "CoreMinimal.h"
#include "GenericPlatform/GenericPlatformMisc.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "SceneInterface.h"
#include "RenderGraphUtils.h"
#include "SceneView.h"
#include "PixelShaderUtils.h"
#include "MeshPassProcessor.inl"
#include "StaticMeshResources.h"
#include "DynamicMeshBuilder.h"
#include "RenderGraphResources.h"
#include "GlobalShader.h"
#include "UnifiedBuffer.h"
#include "CanvasTypes.h"
#include "MaterialShader.h"
#include "RHIGPUReadback.h"
#include "ShaderParameterStruct.h"
#include "RenderTargetPool.h"

#include "RHI.h"
#include "Modules/ModuleManager.h"
#include "C:\Program Files\Epic Games\UE_5.7\Engine\Shaders\Shared\RayTracingDefinitions.h"
#include "C:\Program Files\Epic Games\UE_5.7\Engine\Shaders\Shared\RayTracingPayloadType.h"
#include "C:\\Program Files\\Epic Games\\UE_5.7\\Engine\\Source\\Runtime\\Renderer\\Private\\RayTracing\RayTracingScene.h"
#include "C:\Program Files\Epic Games\UE_5.7\Engine\Source\Runtime\Renderer\Private\SceneRendering.h"
#include "C:\Program Files\Epic Games\UE_5.7\Engine\Source\Runtime\Renderer\Private\ScenePrivate.h"
#include "SceneViewExtension.h"
#include "MySimpleComputeShader.generated.h"

class FScene;
class FSceneView;

struct MYSHADERS_API FMySimpleComputeShaderDispatchParams
{
	int X;
	int Y;
	int Z;


	FVector3f Origin;
	FVector3f Direction;
	float Distance;
	FRDGBufferSRVRef LayerView;
	FSceneInterface* RenderScene;
	const FSceneView* SceneView;
	FVector3f Output;



	FMySimpleComputeShaderDispatchParams(int x, int y, int z)
		: X(x)
		, Y(y)
		, Z(z)
	{
	}
};

// This is a public interface that we define so outside code can invoke our compute shader.
class MYSHADERS_API FMySimpleComputeShaderInterface : public FSceneViewExtensionBase {
public:

	// Executes this shader on the render thread
	static void DispatchRenderThread(
		FRHICommandListImmediate& RHICmdList,
		FMySimpleComputeShaderDispatchParams Params,
		TFunction<void(FVector3f OutputVal)> AsyncCallback
	);


	// Executes this shader on the render thread from the game thread via EnqueueRenderThreadCommand
	static void DispatchGameThread(
		FMySimpleComputeShaderDispatchParams Params,
		TFunction<void(FVector3f OutputVal)> AsyncCallback
	)
	{
		ENQUEUE_RENDER_COMMAND(SceneDrawCompletion)(
			[Params, AsyncCallback](FRHICommandListImmediate& RHICmdList)
			{
				DispatchRenderThread(RHICmdList, Params, AsyncCallback);
			});
	}

	// Dispatches this shader. Can be called from any thread
	static void Dispatch(
		FMySimpleComputeShaderDispatchParams Params,
		TFunction<void(FVector3f OutputVal)> AsyncCallback
	)
	{
		if (IsInRenderingThread()) {
			DispatchRenderThread(GetImmediateCommandList_ForRenderCommand(), Params, AsyncCallback);
		}
		else {
			DispatchGameThread(Params, AsyncCallback);
		}
	}
};



DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnMySimpleComputeShaderLibrary_AsyncExecutionCompleted, const FVector3f, Value);


UCLASS() // Change the _API to match your project
class MYSHADERS_API UMySimpleComputeShaderLibrary_AsyncExecution : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

public:

	// Execute the actual load
	virtual void Activate() override {
		// Create a dispatch parameters struct and fill it the input array with our args
		FMySimpleComputeShaderDispatchParams Params(1, 1, 1);
		Params.Origin = Arg1;
		Params.Direction = Arg2;
		Params.Distance = Arg3;


		// Dispatch the compute shader and wait until it completes
		FMySimpleComputeShaderInterface::Dispatch(Params, [this](FVector3f OutputVal) {
			this->Completed.Broadcast(OutputVal);
			});
	}



	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", Category = "ComputeShader", WorldContext = "WorldContextObject"))
	static UMySimpleComputeShaderLibrary_AsyncExecution* ExecuteBaseComputeShader(UObject* WorldContextObject, FVector3f Arg1, FVector3f Arg2, float Arg3) {
		UMySimpleComputeShaderLibrary_AsyncExecution* Action = NewObject<UMySimpleComputeShaderLibrary_AsyncExecution>();
		Action->Arg1 = Arg1;
		Action->Arg2 = Arg2;
		Action->Arg3 = Arg3;
		APlayerController* PC = WorldContextObject->GetWorld()->GetFirstPlayerController();
		ULocalPlayer* ULP = PC->GetLocalPlayer();
		check(IsValid(ULP));
		FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(ULP->ViewportClient->Viewport,WorldContextObject->GetWorld()->Scene,ULP->ViewportClient->EngineShowFlags));
		FVector ViewLocation;
		FRotator ViewRotation;
		const FSceneView* SceneView = ULP->CalcSceneView(&ViewFamily, ViewLocation, ViewRotation, ULP->ViewportClient->Viewport);
		const FViewInfo& ViewInfo = static_cast<const FViewInfo&>(*SceneView);
		FRayTracingScene::FViewHandle ViewHandle = ViewInfo.GetRayTracingSceneViewHandle();
		check(ViewHandle.IsValid())
		Action->Arg4 = WorldContextObject->GetWorld()->Scene->GetRenderScene()->RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base,ViewHandle);
		Action->Arg5 = WorldContextObject->GetWorld()->Scene;
		Action->Arg6 = SceneView;
		Action->RegisterWithGameInstance(WorldContextObject);
		return Action;
	}

	UPROPERTY(BlueprintAssignable)
	FOnMySimpleComputeShaderLibrary_AsyncExecutionCompleted Completed;


	FVector3f Arg1;
	FVector3f Arg2;
	float Arg3;
	FRDGBufferSRVRef Arg4;
	FSceneInterface* Arg5;
	const FSceneView* Arg6;

};

#define NUM_THREADS_ExampleComputeShader_X 1
#define NUM_THREADS_ExampleComputeShader_Y 1
#define NUM_THREADS_ExampleComputeShader_Z 1

The cpp file:

#include "MySimpleComputeShader.h"


DECLARE_STATS_GROUP(TEXT("MySimpleComputeShader"), STATGROUP_MySimpleComputeShader, STATCAT_Advanced);
DECLARE_CYCLE_STAT(TEXT("MySimpleComputeShader Execute"), STAT_MySimpleComputeShader_Execute, STATGROUP_MySimpleComputeShader);

// This class carries our parameter declarations and acts as the bridge between cpp and HLSL.
class MYSHADERS_API FMySimpleComputeShader : public FGlobalShader
{
public:

	DECLARE_GLOBAL_SHADER(FMySimpleComputeShader);
	SHADER_USE_PARAMETER_STRUCT(FMySimpleComputeShader, FGlobalShader);


	class FMySimpleComputeShader_Perm_TEST : SHADER_PERMUTATION_INT("TEST", 1);
	using FPermutationDomain = TShaderPermutationDomain<
		FMySimpleComputeShader_Perm_TEST
	>;

	BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
		/*
		* Here's where you define one or more of the input parameters for your shader.
		* Some examples:
		*/
		// SHADER_PARAMETER(uint32, MyUint32) // On the shader side: uint32 MyUint32;
		SHADER_PARAMETER(FVector3f, Origin) // On the shader side: float3 MyVector;
		SHADER_PARAMETER(FVector3f, Direction)
		SHADER_PARAMETER(float, Distance)
		SHADER_PARAMETER_SRV(RayTracingAccelerationStructure, TLAS)
		// SHADER_PARAMETER_TEXTURE(Texture2D, MyTexture) // On the shader side: Texture2D<float4> MyTexture; (float4 should be whatever you expect each pixel in the texture to be, in this case float4(R,G,B,A) for 4 channels)
		// SHADER_PARAMETER_SAMPLER(SamplerState, MyTextureSampler) // On the shader side: SamplerState MySampler; // CPP side: TStaticSamplerState<ESamplerFilter::SF_Bilinear>::GetRHI();

		// SHADER_PARAMETER_ARRAY(float, MyFloatArray, [3]) // On the shader side: float MyFloatArray[3];

		// SHADER_PARAMETER_UAV(RWTexture2D<FVector4f>, MyTextureUAV) // On the shader side: RWTexture2D<float4> MyTextureUAV;
		// SHADER_PARAMETER_UAV(RWStructuredBuffer<FMyCustomStruct>, MyCustomStructs) // On the shader side: RWStructuredBuffer<FMyCustomStruct> MyCustomStructs;
		// SHADER_PARAMETER_UAV(RWBuffer<FMyCustomStruct>, MyCustomStructs) // On the shader side: RWBuffer<FMyCustomStruct> MyCustomStructs;

		// SHADER_PARAMETER_SRV(StructuredBuffer<FMyCustomStruct>, MyCustomStructs) // On the shader side: StructuredBuffer<FMyCustomStruct> MyCustomStructs;
		// SHADER_PARAMETER_SRV(Buffer<FMyCustomStruct>, MyCustomStructs) // On the shader side: Buffer<FMyCustomStruct> MyCustomStructs;
		// SHADER_PARAMETER_SRV(Texture2D<FVector4f>, MyReadOnlyTexture) // On the shader side: Texture2D<float4> MyReadOnlyTexture;

		// SHADER_PARAMETER_STRUCT_REF(FMyCustomStruct, MyCustomStruct)


		//SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer<int>, Input)
		SHADER_PARAMETER_RDG_BUFFER_UAV(Buffer<FVector3f>, Output)


	END_SHADER_PARAMETER_STRUCT()

public:
	static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
	{
		const FPermutationDomain PermutationVector(Parameters.PermutationId);

		return true;
	}


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

		const FPermutationDomain PermutationVector(Parameters.PermutationId);

		/*
		* Here you define constants that can be used statically in the shader code.
		* Example:
		*/
		// OutEnvironment.SetDefine(TEXT("MY_CUSTOM_CONST"), TEXT("1"));

		/*
		* These defines are used in the thread count section of our shader
		*/
		OutEnvironment.SetDefine(TEXT("THREADS_X"), NUM_THREADS_ExampleComputeShader_X);
		OutEnvironment.SetDefine(TEXT("THREADS_Y"), NUM_THREADS_ExampleComputeShader_Y);
		OutEnvironment.SetDefine(TEXT("THREADS_Z"), NUM_THREADS_ExampleComputeShader_Z);

		// This shader must support typed UAV load and we are testing if it is supported at runtime using RHIIsTypedUAVLoadSupported
		OutEnvironment.CompilerFlags.Add(CFLAG_AllowTypedUAVLoads);

		//FForwardLightingParameters::ModifyCompilationEnvironment(Parameters.Platform, OutEnvironment);
	}
private:
};

// This will tell the engine to create the shader and where the shader entry point is.
//                            ShaderType                            ShaderPath                     Shader function name    Type
IMPLEMENT_GLOBAL_SHADER(FMySimpleComputeShader, "/MyShadersShaders/MySimpleComputeShader.usf", "MySimpleComputeShader", SF_Compute);



void FMySimpleComputeShaderInterface::DispatchRenderThread(FRHICommandListImmediate& RHICmdList, FMySimpleComputeShaderDispatchParams Params, TFunction<void(FVector3f OutputVal)> AsyncCallback) {
	FRDGBuilder GraphBuilder(RHICmdList);

	{

		SCOPE_CYCLE_COUNTER(STAT_MySimpleComputeShader_Execute);
		DECLARE_GPU_STAT(MySimpleComputeShader);
		RDG_EVENT_SCOPE(GraphBuilder, "MySimpleComputeShader");
		RDG_GPU_STAT_SCOPE(GraphBuilder, MySimpleComputeShader);

		typename FMySimpleComputeShader::FPermutationDomain PermutationVector;
		// Add any static permutation options here
		// PermutationVector.Set<FMySimpleComputeShader::FMyPermutationName>(12345);

		TShaderMapRef<FMySimpleComputeShader> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector);


		bool bIsShaderValid = ComputeShader.IsValid();

		if (bIsShaderValid) {
			FMySimpleComputeShader::FParameters* PassParameters = GraphBuilder.AllocParameters<FMySimpleComputeShader::FParameters>();

			//const void* RawData = (void*)Params.Origin;
			//int NumInputs = 2;
			//int InputSize = sizeof(FVector3f);
		//	FRDGBufferRef InputBuffer1 = CreateUploadBuffer(GraphBuilder, TEXT("InputBuffer"), InputSize, 1, Params.Origin, InputSize);

			//PassParameters->Input = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(InputBuffer, PF_R32_SINT));
			const FSceneView& View = *Params.SceneView;
			TRDGUniformBufferRef<FSceneUniformParameters> Buffer = GetSceneUniformBufferRef(GraphBuilder, View);
			


			PassParameters->Origin = Params.Origin;
			PassParameters->Direction = Params.Direction;
			PassParameters->Distance = Params.Distance;


			FRDGBufferRef OutputBuffer = GraphBuilder.CreateBuffer(
				FRDGBufferDesc::CreateBufferDesc(sizeof(FVector3f), 1),
				TEXT("OutputBuffer"));
			PassParameters->Output = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OutputBuffer, PF_R32_SINT));

			auto GroupCount = FComputeShaderUtils::GetGroupCount(FIntVector(Params.X, Params.Y, Params.Z), FComputeShaderUtils::kGolden2DGroupSize);


			FRDGBufferSRVRef layerView = Params.LayerView;


			GraphBuilder.AddPass(
				RDG_EVENT_NAME("ExecuteMySimpleComputeShader"),
				PassParameters,
				ERDGPassFlags::AsyncCompute,
				[&PassParameters,layerView, ComputeShader, GroupCount](FRHIComputeCommandList& RHICmdList)
				{
					PassParameters->TLAS = layerView->GetRHI();
					FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParameters, GroupCount);
				});


			FRHIGPUBufferReadback* GPUBufferReadback = new FRHIGPUBufferReadback(TEXT("ExecuteMySimpleComputeShaderOutput"));

			AddEnqueueCopyPass(GraphBuilder, GPUBufferReadback, OutputBuffer, 0u);
			auto RunnerFunc = [GPUBufferReadback, AsyncCallback](auto&& RunnerFunc) -> void {
				if (GPUBufferReadback->IsReady()) {

					FVector3f* Buffer = (FVector3f*)GPUBufferReadback->Lock(1);
					FVector3f OutVal = Buffer[0];

					GPUBufferReadback->Unlock();

					AsyncTask(ENamedThreads::GameThread, [AsyncCallback, OutVal]() {
						AsyncCallback(OutVal);
						});

					delete GPUBufferReadback;
				}
				else {
					AsyncTask(ENamedThreads::ActualRenderingThread, [RunnerFunc]() {
						RunnerFunc(RunnerFunc);
						});
				}
				};

			AsyncTask(ENamedThreads::ActualRenderingThread, [RunnerFunc]() {
				RunnerFunc(RunnerFunc);
				});

		}
		else {
			// We silently exit here as we don't want to crash the game if the shader is not found or has an error.

		}
	}

	GraphBuilder.Execute();
}


The Shader.usf, not implemented anything yet of course except a simple vector return which I have checked works

#include "/Engine/Public/Platform.ush"
#include "/Engine/Private/Common.ush"
#include "/Engine/Private/RayTracing/RayTracingCommon.ush"
#include "/Engine/Private/RayTracing/RayTracingHitGroupCommon.ush"

const float3 Origin;
const float3 Direction;
const float3 Distance;
const RaytracingAccelerationStructure TLAS;
//RWBuffer<float3> Output;

[numthreads(THREADS_X, THREADS_Y, THREADS_Z)]
void MySimpleComputeShader(
	uint3 DispatchThreadId : SV_DispatchThreadID,
	uint GroupIndex : SV_GroupIndex)
{
    //Output[0] = Origin;
}

When it comes to views, I think it’s better to use the functions provided by SceneViewExtension. For example, if you use PostTLASBuild_RenderThread, you can be sure it will be called after TLAS has been built. However, I believe you’ll need to use polling or similar methods to call it at any specific time.

Engine/Source/Runtime/Engine/Public/SceneViewExtension.h:200

Additionally, if any ray-tracing features such as ray-traced shadows are not enabled in the project, TLAS will not be generated and an error will occur.

Sorry this is mostly new to me, what is polling?

For example, it’s a mechanism where you set a call flag, check it every time, and execute the code only once when it becomes active.