Community Tutorial: How to Create a Custom Ray Tracing Shader as a Plugin

Hi! I’m trying to port this example to UE5.5.1 under Linux, it seems almost done but I still have one issue, about passing to the shader the TLAS:
unfortunately GetLayerSRVChecked is not available anymore (or at least not on UE 5.5 Vulkan), any suggestion how to get it alternaatively?
Thanks!

ok, some steps forward
replaced

PassParameters->TLAS = CachedParams.Scene->GetLayerSRVChecked(ERayTracingSceneLayer::Base);

with

PassParameters->TLAS = CachedParams.Scene->GetLayerView(ERayTracingSceneLayer::Base)->GetRHI();

It compiles, but I get this error at runtime, and I amnot sure how to proceed…

Ray tracing scene SRV was not created. Perhaps Create() was not called.

Hi all, another small step forward:
I moved the TLAS init inside the graph builder pass and now seems valid, but unfrotunately I am getting an error on this line

	RHICmdList.SetRayTracingMissShader(
                   RHIScene,               
                   RAY_TRACING_MISS_SHADER_SLOT_DEFAULT, 
                   PipeLine, 
                   0 /* ShaderIndexInPipeline */, 
                   0, nullptr, 0);

Looking inside I get that in the SetRayTracingBindings the

			Scene->GetInitializer().PerInstanceGeometries

is empty… so when it tries to fill the bindings it fails

			Bindings[i].Geometry = Scene>GetInitializer().PerInstanceGeometries[Bindings[i].InstanceIndex];

FRayTracingScene is correctly initialized(lot of stuff inside, and definitely the perInstanceGeometry object looks populated) but FRHIRayTracingScene is not, so I am wondering who (or how to) init this one too

Any idea?

Thanks

I have some new Findings!

There is a good chance that a Full Ray Tracer might be Possible using this Plugin Setup.

The Reason why all the other Payload Variables arent present is beacuse the Tutorial shows Using Only FMinimalPayload

(from RayTracingCommon.ush Ln.250)

struct FMinimalPayload
{
float HitT; // Distance from ray origin to the intersection point in the ray direction. Negative on miss.

bool IsMiss() { return HitT < 0; }
bool IsHit() { return !IsMiss(); }

void SetMiss() { HitT = -1; }
};

As we can see, FMinimalPayload outputs only HitT, and nothing else.

If we want stuff Like Base Color, Normal or Metallic at Hit Location, we need to set The Shader to use a more advanced Payload Structure

(from RayTracingCommon.ush Ln.675)

struct FMaterialClosestHitPayload : FMinimalPayload
{
// Unpacked Packed
// offset bytes
// float FMinimalPayload::HitT // 0 4 32bits
FRayCone RayCone; // 4 8 64bits
float3 Radiance; // 8 6 48bits
float3 WorldNormal; // 24 6 48bits
float3 BaseColor; // 36 6 48bits
float3 DiffuseColor; // 48 0 (derived)
float3 SpecularColor; // 60 0 (derived)
float Opacity; // 72 2 16bits
float Metallic; // 76 1 8bits
float Specular; // 80 1 8bits
float Roughness; // 84 2 16bits
float Ior; // 88 2 16bits
uint ShadingModelID; // 92 1 4bits
uint BlendingMode; // 96 0 3bits (packed with ShadingModelID)
uint PrimitiveLightingChannelMask; // 100 0 3bits (packed with ShadingModelID)
float4 CustomData; // 104 4 32bits
float GBufferAO; // 120 0 (removed)
float3 IndirectIrradiance; // 124 0 48bits – gbuffer only has float payload and there are truncation HLSL warnings

// Quite some code assume FRayHitInfo has a WorldPos
// So keep it here and force to re-construct it in Unpack call using ray information.
// It is not packed in FRayHitInfoPacked
float3 TranslatedWorldPos; // 136 0 (derived)
uint Flags; // 148 0 6bits (packed with ShadingModelID)
float3 WorldTangent; // 152 6 48bits
float Anisotropy; // 164 2 16bits (packed with WorldTangent)
// 166 total

void SetFrontFace() { Flags |= RAY_TRACING_PAYLOAD_OUTPUT_FLAG_FRONT_FACE; }
bool IsFrontFace() { return (Flags & RAY_TRACING_PAYLOAD_OUTPUT_FLAG_FRONT_FACE) != 0; }

void SetTwoSided() { Flags |= RAY_TRACING_PAYLOAD_OUTPUT_FLAG_TWO_SIDED; }
bool IsTwoSided() { return (Flags & RAY_TRACING_PAYLOAD_OUTPUT_FLAG_TWO_SIDED) != 0; }

FRayCone GetRayCone() { return RayCone; }
void SetRayCone(FRayCone NewRayCone) { RayCone = NewRayCone; }

bool HasAnisotropy() { return abs(Anisotropy) >= 0.001f; }

float3 GetRayDirection() { return IndirectIrradiance; /* Alias IndirectIrradiance/RayDirection */ }
};

(Note: As long as im reading it correctly, there are Two (or more?) versions of This Payload Structure, One of them is Intended for Strata. Im not sure which one of them is the one Getting used if i set my Shader to Use FMaterialClosestHitPayload)

Maybe it will work, maybe not.

FMaterialClosestHitPayload is an Extended version of FMinimalPayload, which has all the Stuff That a Typical Full Raytracer Would need.

I tried Using FMaterialClosestHitPayload in my Shader, but D3D Crashes due to a Structure Missmatch. Im unable to Initialize it in My shader correctly, and this is where im Stuck now

I hope this info will be Helpfull…

1 Like

Update 2:

I managed to get the FPackedMaterialClosestHitPayload Struct along with its Unpacked Variant (FMaterialClosestHitPayload) working.

To do that we need to set this Function in RayGenTest.cpp:

static ERayTracingPayloadType GetRayTracingPayloadType(const int32 /PermutationId/)
{
return ERayTracingPayloadType::RayTracingMaterial;
}

To use ERayTracingPayloadType::RayTracingMaterial. You can find all possible Settings inside RayTracingPayloadType.h

That However only allows The Packed Payload to work. To get the Unpacked Variant, we need to First Run TraceMaterialRayPacked() with the Packed Payload as input, Then Run

UnpackedPayload = UnpackRayTracingPayload(PackedPayload, Ray);

To Convert the Payload into FMaterialClosestHitPayload.

But even After that, All Material data is still Zero. This is where my Progress ended.
The problem is that all the Payload data Must be Retrieved and set inside the Closest Hit Shader. We cannot do this with This setup as the Closest Hit Shader is using a Manual Definition, meaning Unreal doesnt assing all the Required Code.

We can fix that by using a Macro instead:

//Replace [shader(“closesthit”)] with this inside Our Shader file:

RAY_TRACING_ENTRY_CLOSEST_HIT(RayTraceTestCHS,
FPackedMaterialClosestHitPayload, Payload,
FRayTracingIntersectionAttributes, Attributes)
{
Payload.HitT = RayTCurrent();
}

Here are 2 Questions: Does it Work? What is RayTCurrent()?

-Answer 1: No it still doesnt (more on that later)
-Answer 2: When you Trigger TraceRay() in any way, D3D automatically Generates these Values:

float3 WorldRayOrigin();
float3 WorldRayDirection();
float RayTMin();
float RayTCurrent()

(More here: DirectX Raytracing (DXR) Functional Spec | DirectX-Specs)
You can use these values instantly with no Complex setup.

-Now About why the Payload is still empty - We are still missing the Code that gets the Material Data where the Ray Intersected the Mesh

I have spent Days looking into The Engine source and what i have found?

Unreal uses JesusAPI to pray that the Payload gets Magically Filled with the right Values

-Now jokes aside, I was unable to find how Unreal actually does that no matter what, in fact, I havent found any information Regarding Generaly how to Populate a User Defined Payload with data in the Closest Hit Shader In DirectX12 Raytracing.

I suspect TraceRay() also returns Data about which Object ID it hit in the Scene, The Triangle ID, and the UV position, and then based on that Unreal must retrieve The Mesh Texture data at The Triangle and UV Position.

The best place to search is probably RayTracingDebug.usf. This shader gets Used to preview Raytracing Payload data when Using Ray Tracing Debug View Mode in the Editor. If we can Reverse Engineer what Closest Hit Shader it uses and How it works, it would finally allow us to Finish the Raytracing Plugin.

So it is still Not Possible to make A Custom RayTracer in Unreal?.. Actually…

If you take a deep look at RayTracingDebug.usf, it has a Suspiciously similliar layout to Our Raytracing Shader. If you really just need a custom RayTracer for Your Unreal project, you can Rewrite the Debug Shader to Output your Custom Ray Traced Result instead of Debug data using the Working Payload Outputs.

This even Works in Packaged builds by just using “Execute Console Command” Blueprint Node at Begin Play when the Level Loads to trigger your custom RayTracer inside Debug Preview. And you dont need to worry about performance as the Debug shader already uses Official Code with Advanced Optimizations and Safety Checks.

If Anybody Needs, here is my current MyRayTraceTest.usf: #include "/Engine/Private/Common.ush"#include "/Engine/Private/RayTracing/RayT - Pastebin.com
EDIT: I Have a typo here on line 50 - There is an extra ) in the line - Remove it to make the Shader Compile

TLDR: I made some improvements, but i still cant get the Payload Outputs working. However i have a found a Good workaround so you can Write a Custom Working Raytracer without all this mess

If Anybody has any questions, just ask and i will do my best to respond.

The same error occurred with UE 5.5.3 VULKAN_SM6. And the error Ray tracing scene SRV was not created. Perhaps Create() was not called. also may happen even after TLAS initting has moved into the GraphBuilder pass.
Do you have more findings now?

@FluffyBunnyO
First off thank you so much for taking the time to make this tutorial. It’s an incredible service to the community!

Do you know if FRayTracingScene can be used to create a separately managed scene within a plugin? I want to do it for the same reasons that the Heterogenous Volume system does, I’m trying to use raytracing for a hybrid raytrace/raymarch system, and I don’t want the overhead of the rest of the scene geometry during the TLAS generation/traversal.
So far it seems like declaring FRayTracingScene within my class isn’t possible because the class isn’t exported, I’m sure you ran into this issue because you had to do a forward declaration just to have a pointer to the Scene’s instance. Do you think there’s a way around this?

Hi! May i ask why you are using SHADER_PARAMETER_UAV/SRV and not SHADER_PARAMETER_RDG_TEXTURE_UAV/SRV? In the ShaderParameterMacros.h is standing that these commands will add Views for a render graph tracked texture. I dont know the difference between the normal and the tracked texture and what are the benefits from not using the tracked texture.
Thank you very much for your tutorial!