I’m trying to create a Compute shader that can run line traces using GPU raytracing. Whilst I created a single compute shader, I am having issues with this one as it appears that I need to have a hit and miss shader defined in the .usf file with the shaders declared in a .cpp rather than a .h file followed by an implementation macro. Whilst I can get this all to compile, I get exceptions in the editor when I add in anything but the base shader declaration. Any guidance here on how to resolve this would be most appreciated. The only guidance I can find that gets us nearly there is this one, however that’s for a proper shader as a pass, rather than one I could run from an Async a compute shader call from BP
See FLineTraceGPU.h and .cpp which is in my plugin’s source/public and private dirs respectively
FLineTraceGPU.h
#pragma once
#include "CoreMinimal.h"
#include "ShaderParameterStruct.h"
#include "GenericPlatform/GenericPlatformMisc.h"
//Full shader implementation is in the .cpp
could you provide some more details on the exceptions you are seeing or alternatively could you provide a minimal repro project?
Regarding the tutorial you linked to, I’m not sure if you have seen this thread on the Unreal dev forum, which contains some interesting discussions and might be useful to make the tutorial work.
You may be interested in looking at inline ray tracing , which was made available in DXR 1.1. This should simplify shader development as it doesn’t use separate shaders.
it’s hard to tell what is causing the mismatch without having the full code. Do the types of OriginSRV, RaySRV, OutputUAV and TLAS_RHI_SRV match the ones in the header (StructuredBuffer<float3>, StructuredBuffer<float3>, RWStructuredBuffer<float4> and RaytracingAccelerationStructure)?
There may also be a memory alignment issue when using StructuredBuffer<float3>. As far as I can tell, the engine code only uses StructuredBuffer<float4> for that reason. Can you test by converting the structs holding the ray origins and directions to float4?
// we want a slight delay before we start, otherwise some resources such as the accelerated structure will not be ready
if(RenderTarget != nullptr && TranscurredTime>1.0f)
I’m not sure if a delay of one second is required, the duration probably depends on the complexity of the scene and its accompanying acceleration structure that needs to be build. There might also be a better way to know when the TLAS is ready to be used as SceneRendering.cpp has a few helper methods for this:
bool FViewInfo::HasRayTracingScene() const
{
check(Family);
FScene* Scene = Family->Scene ? Family->Scene->GetRenderScene() : nullptr;
if (Scene)
{
return Scene->RayTracingScene.IsCreated();
}
return false;
}
FRHIRayTracingScene* FViewInfo::GetRayTracingSceneChecked(ERayTracingSceneLayer Layer) const
{
check(Family);
if (Family->Scene)
{
if (FScene* Scene = Family->Scene->GetRenderScene())
{
FRHIRayTracingScene* Result = Scene->RayTracingScene.GetRHIRayTracingScene(Layer);
checkf(Result, TEXT("Ray tracing scene is expected to be created at this point."));
return Result;
}
}
return nullptr;
}
FRDGBufferSRVRef FViewInfo::GetRayTracingSceneLayerViewChecked(ERayTracingSceneLayer Layer) const
{
FRDGBufferSRVRef Result = nullptr;
check(Family);
if (Family->Scene)
{
if (FScene* Scene = Family->Scene->GetRenderScene())
{
Result = Scene->RayTracingScene.GetLayerView(Layer);
}
}
checkf(Result, TEXT("Ray tracing scene SRV is expected to be created at this point."));
return Result;
}
I hope this helps, but let me know if you have more questions.
the TLAS is rebuilt asynchronously every frame on the GPU during the Render Thread’s rendering phase later in the pipeline closer to the point where ray traced features such as Lumen or needed (the render thread only submits the commands to build it on the GPU). This may explain why the TLAS handle is sometimes invalid.
The best place to reliably get a handle to the TLAS is on the Render Thread, immediately before the ray tracing passes that need it (to ensure that the TLAS is available). For that, it’s best to add the compute pass to the Render Dependency Graph (RDG) system (with FRDGBuilder::AddPass()), and make the TLAS a dependency of the compute pass, which should guarantee that the compute shader is dispatched after the TLAS build pass has finished. You can declare TLAS as a parameter to your RDG pass in your shader parameters, that way the render graph knows your pass depends on TLAS. The RDG builder then inserts the compute pass after the TLAS build pass in the graph and handles the necessary synchronisation and resource state transitions. The engine manages then executes the pass at the safe guaranteed point after the TLAS build command has executed on the GPU.
I hope that helps, but let me know if you have further questions.
I think the fact you might be seeing intermittent hits and different hit results on consecutive runs is because this example is meant for tracing shadows, where the ray returns at any hit (RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH).
I’ve made a few slight modifications to your code to mimic a closest hit shader instead of any hit (shadow) shader by removing the above flag and continue the trace in a while loop. I also replaced TMin with a small positive value to avoid self intersections resulting from limitied precision and changed the miss shader by explicitly comparing the status to COMMITTED_NOTHING. The change from ray.Origin to q.WorldRayOrigin() and ray.Direction to q.WorldRayDirection() are probably redundant, but it’s based on an example found in the DXR documentatio:
Many thanks - so it looks like the reason my shaders were not compiling were due to a class referencing a usf that wasn’t there.
Thanks for the links - I’ve been going through these - it appears that the API has changed quite a bit since 5.2 which these examples relate to. 5.6 it seems a little different.
So I’ve got a my line trace shader MVP compiling and accessible through an interface class. As I’d want to implement this as a compute shader, I need to be able to pass the TLAS data through to the usf shader file.
Currently whilst my code is compiling, I’m getting an exception when Unreal Editor starts up - pointing to a mismatch between the data binding between the usf and the shader.
Many thanks - I did find an issue with this data mapping which I’ve resolved and I’ve been able to verify that my structured buffers float3 and float4 now working fine having tested a version of the shader commenting out TLAS/RaytracingAccelerationStructure from the shader file, shader wrapper and interface call and simply returning one of the input vectors instead to verify shader data i/o
The problem I’m having appears to be in getting the TLAS FRDGBufferSRV for the TLAS - having looking in RaytracingDebug.cpp in the engine, it appears that I should be getting a handle to it using:
However when I try to in my code, I get an invalid object which throws an exception. I’ve attached my interface code as I believe the issue may be to do with the context in which I’m getting this? I’ve made a helper function and verified that World and Scene are valid handles. Helper function for ref:
bool ILineTrace_CS_Interface::GetTLAS(FRDGBufferSRVRef& TLAS)
{
TLAS = nullptr;
//World
const UWorld* World = GEngine ? GEngine->GetWorldContexts()[0].World() : nullptr;
if (!World)
{
UE_LOG(LogLineTrace_CS, Error, TEXT("Unable to get world context"));
return false;
}
UE_LOG(LogLineTrace_CS, Log, TEXT("World is valid"));
TLAS = World->Scene->GetRenderScene()->RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base);
if (!TLAS)
{
UE_LOG(LogLineTrace_CS, Error, TEXT("Invalid TLAS"));
return false;
}
return true;
}
I’ve also had to include include “../Private/ScenePrivate.h” and added Path.Combine(GetModuleDirectory(“Renderer”), “Internal”) to build.cs so that I can build against these.
To help, I’ve attached my the corresponding shader, wrapper and interface files
Thanks - that’s helpful - looks like TLAS is not always present - which I’ve verified with a test actor which logs when it finds a valid TLAS on tick in PIE. It appears to be intermittently generated - in a way that I can’t quite repeat reliably. Is there a way to request a TLAS update and/or under what conditions is it updated?
test code:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "TLASTestActor.generated.h"
UCLASS()
class My_API ATLASTestActor : public AActor
{
GENERATED_BODY()
public:
// Sets default values for this actor's properties
ATLASTestActor();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick(float DeltaTime) override;
private:
bool TryGetTLAS(FRHIShaderResourceView*& OutTLAS) const;
void ForceTLASRebuild() const;
bool bHasPokedTLS;
};
and
#include "LineTraceGPU/TLASTestActor.h"
#include "EngineUtils.h"
#include "Engine/StaticMeshActor.h"
#include "Runtime/Renderer/Private/ScenePrivate.h"
// Sets default values
ATLASTestActor::ATLASTestActor()
{
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
}
// Called when the game starts or when spawned
void ATLASTestActor::BeginPlay()
{
Super::BeginPlay();
UE_LOG(LogTemp, Log, TEXT("TLASTestActor: BeginPlay"));
}
// Called every frame
void ATLASTestActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
FRHIShaderResourceView* TLAS_RHI = nullptr;
if (TryGetTLAS(TLAS_RHI))
{
UE_LOG(LogTemp, Log, TEXT("✅ TLAS is ready! Safe to dispatch GPU work."));
// You could now trigger DispatchRenderThread() here
SetActorTickEnabled(false); // stop checking
}
else
{
UE_LOG(LogTemp, Warning, TEXT("TLAS not ready yet — waiting..."));
if (!bHasPokedTLS)
{
UE_LOG(LogTemp, Warning, TEXT("Poking TLAS..."));
ForceTLASRebuild();
bHasPokedTLS = true;
}
}
}
bool ATLASTestActor::TryGetTLAS(FRHIShaderResourceView*& OutTLAS) const
{
OutTLAS = nullptr;
UWorld* World = GetWorld();
if (!World)
{
return false;
}
FSceneInterface* SceneInterface = World->Scene;
if (!SceneInterface)
{
return false;
}
FScene* RenderScene = SceneInterface->GetRenderScene();
if (!RenderScene)
{
return false;
}
if (!RenderScene->RayTracingScene.IsCreated())
{
return false;
}
FRDGBufferSRVRef LayerView = RenderScene->RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base);
if (!LayerView)
{
return false;
}
OutTLAS = LayerView->GetRHI();
return (OutTLAS != nullptr);
}
// ==== Helper to poke TLAS rebuild (game thread) ====
void ATLASTestActor::ForceTLASRebuild() const
{
UWorld* World = GetWorld();
if (!World) return;
for (TActorIterator<AStaticMeshActor> It(World); It; ++It)
{
if (UStaticMeshComponent* Comp = It->GetStaticMeshComponent())
{
// Smallest possible "poke" — doesn't change anything visually
Comp->MarkRenderStateDirty();
}
}
}
>> It appears to be intermittently generated - in a way that I can’t quite repeat reliably.
There might be a race condition. You can try to disable r.RayTracing.AsyncBuild to see if that makes a difference.
>> Is there a way to request a TLAS update and/or under what conditions is it updated?
Looking at the engine code, it seems the ray tracing scene is created in FDeferredShadingSceneRenderer::DispatchRayTracingWorldUpdates() in DeferredShadingRenderer.cpp with RayTracingScene.Create() and RayTracingScene.Build()
Many thanks - I’ve been struggling to get a reliable handle on TLAS from Game Thread, Render Thread and have tried via a FSceneViewExtension class hooking onto PostRenderViewFamily_RenderThread as it appears that it might be safer hooking in here for TLAS which appears to be a pretty shortlived object per frame. - In fact that sceneViewExtension NEVER gets a valid TLAS rather checking on GT and RT results in intermittent values.
Does TLAS persist or is it created/destroyed on a per frame basis with a particular lifespan in the frame? If so, what would be the best events to hook into in order to get a valid handle on this and from what thread etc (although from what I’ve read, it appears that TLAS may be generated async on GPU so understandably outside of the other threads as they’re running on CPU).
I’m thinking that simply polling from a CPU thread isn’t the best way to get TLAS and get my compute shader to process?
Thanks - I’m just about there now with my understanding of how to implement this within rendergraph. The mists are parting somewhat!
My understanding now:
A TLAS object is created each frame at a specific moment
In order to bind to the TLAS the pointer for this is generated prior to the TLAS RHI ref and the RenderModule has a delegate that can be hooked onto called FPostOpaqueRenderDelegate
At this point, the pointer to TLAS is valid and can be set using… “World->Scene->GetRenderScene()->RayTracingScene.GetLayerView(ERayTracingSceneLayer::Base)))”
After which this needs to be added to RDG using GraphBuilder->AddPass
I’ve manged to hook everything up and verify that my structured buffers are coming in fine and given I’m now calling a function that calls the TLAS, and there are no exceptions, I assume that this is coming in fine now too.
Strangely, I am getting strange results for the same data input data across multiple calls with this compute shader - am I missing something here?
Occasional hit results returned in same direction of line trace, and then misses - this happens despite not having changed anything in the shader or objects calling it (have even confirmed this behaviour hardcoding origin and direction vectors which again returns different results across multiple calls - either a hit in direction of the ray - or a miss).
any hit results do not appear to hit geometry in level - rather are returning a point a specific distance from the origin along the direction of the ray
Given that the Origin and Direction is in WorldSpace, I’d expect this to work?
[mention removed] - Many thanks for your code here - having implemented it, I’m getting similar inconsistent results so I’ve uploaded a MVP project with the relevant code in a GPUCompute plugin to help.
Incorrect hit points - The shader is returning a valid hit however the hit point is incorrect and doesn’t appear to correlate with the geometry in the scene.
Inconsistent Results - I’m getting inconsistent hit results which appear to be view dependent (eg same inputs for line trace produces different results depending upon the view in editor).
I’ve attached a screenshot illustrating this (thin red trace lines = miss) as well as the sample project. Map has a simple editor utility actor with a BP function which can be called in editor through the actors properties bar.