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 totalvoid 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…
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!
thanks: very helpful comment
Indeed I have reached the same conclusions:
RayTCurrent is always 0, so there’s definitely something wrong in this shader setup
I have also followed your suggestion: I modifed RTdebug shader, changing the Instance view to print this (normalized) value and it works, it’s available and consistent
So now I try to dig into RayTracingDebug.usf and related files to understand what are the differences
New update on raytracer shader, with some itneresting finds
RayGen shader is now like this
// use material closest hit pay load
FMinimalPayload Payload = (FMinimalPayload) 0;
Payload.HitT = -999.0f; // Distinctive initial value
TraceRay(
TLAS, // AccelerationStructure
RayFlags,
RAY_TRACING_MASK_OPAQUE, // InstanceInclusionMask, defined by UE4
RAY_TRACING_SHADER_SLOT_MATERIAL, // RayContributionToHitGroupIndex, defined by UE4
RAY_TRACING_NUM_SHADER_SLOTS, // MultiplierForGeometryContributionToShaderIndex, defined by UE4
0, // MissShaderIndex
Ray, // RayDesc
Payload // Payload
);
// Debug output based on initial payload state
if (Payload.HitT == -999.0f)
{
// No update occurred at all
outTex[PixelCoord] = float4(1,0,0,1); // Red - no payload update
}
else if (Payload.HitT == 0)
{
outTex[PixelCoord] = float4(0,1,0,1); // Green - zero hit
}
else if (Payload.HitT > 0)
{
outTex[PixelCoord] = float4(0,0,1,1); // Blue - positive hit
}
else if (Payload.HitT == -1)
{
outTex[PixelCoord] = float4(1,1,0,1); // Yellow - miss
}
else
{
outTex[PixelCoord] = float4(1,0,1,1); // Purple - unexpected state
}
and I get this
This should mean that the payload is not updated when there’s an hit, but only in the miss case…Quite weird actually
edit: I think I miss to call SetRayTracingHitGroup properly…any hint on what to pass using SBT?
Im currently writing my Raytracer Inside RayTracingPrimaryRays.usf, and There The Material Payload is Working Like a Charm! (Here is my progress if anybody is Curious: https://www.reddit.com/r/raytracing/comments/1jklscs/after_i_finally_got_an_rtx_card_i_realized_i_dont/)
Checking its Parent C++ File (RayTracingPrimaryRays.cpp), I found this Possibly Super Important Bit of Code on Line 75:
void FDeferredShadingSceneRenderer::PrepareRayTracingTranslucency(const FViewInfo& View, TArray<FRHIRayTracingShader*>& OutRayGenShaders)
{
// Translucency and primary ray tracing requires the full ray tracing pipeline with material bindings.
if (!ShouldRenderRayTracingEffect(ERayTracingPipelineCompatibilityFlags::FullPipeline))
{
return;
}
// Declare all RayGen shaders that require material closest hit shaders to be bound.
// NOTE: Translucency shader may be used for primary ray debug view mode.
if (GetRayTracingTranslucencyOptions(View).bEnabled || View.Family->EngineShowFlags.RayTracingDebug)
{
FRayTracingPrimaryRaysRGS::FPermutationDomain PermutationVector;
PermutationVector.Set<FRayTracingPrimaryRaysRGS::FEnableTwoSidedGeometryForShadowDim>(EnableRayTracingShadowTwoSidedGeometry());
auto RayGenShader = View.ShaderMap->GetShader<FRayTracingPrimaryRaysRGS>(PermutationVector);
OutRayGenShaders.Add(RayGenShader.GetRayTracingShader());
}
}
-Seems to be Related to Binding Unreals Material Closest Hit Shader, which does what We need
Im not sure if this Will make it Work, but With High Probability It will Definitely Get us Closer to Making the Material Payload Work Properly
I would Recommend Getting rid of The Tutorial Closest Hit Shader, and Replace it With The Unreal One to get the Material Payload Working. Finding The Unreal CHS and copying it Into the Plugin is a Possibility, but if Even possible, it Would be Probably Multiple times Longer than the RayGen Shader with no real benefit at least from my Point of View
If for any reason, I can provide my Raytracing Code I have in PrimaryRays.ush, if needed
thanks for the reply and the additional info
unfortunately for me modifying an internal shader it’s a no go (at least for now): I must have a separate independent shader plugin
So, one additional thing I m wondering is:
do we actually set, with this setup, some real geometry to the shader? cause another possible explanation for this strange behavior is that the hit shader is just never called due to an empty scene, thus we get all misses…
Great exploration! I’m currently facing an issue where the material payload isn’t being filled correctly. Could you please let me know which Unreal Closest Hit Shader should be used to replace the custom Closest Hit Shader? I’ve been studying the source code for several days but still haven’t figured out which one to use. Thank you very much!
I havent actually found the Closest Hit Used by Unreal, and It is a Mystery how the Payload is getting populated. Maybe try following this?
-from RayTracingBuiltInShaders.usf, seems to be the "Default" Closest Hit shader
RAY_TRACING_ENTRY_CLOSEST_HIT(DefaultMainCHS, FDefaultPayload, Payload, FRayTracingIntersectionAttributes, Attributes)
{
Payload.Barycentrics = Attributes.GetBarycentrics();
Payload.InstanceID = InstanceID();
Payload.InstanceIndex = InstanceIndex();
Payload.PrimitiveIndex = PrimitiveIndex();
Payload.HitT = RayTCurrent();
}
-from RayTracingCommon.ush, Whatever this is
struct FDefaultPayload : FIntersectionPayload
{
uint InstanceID; // Value of FRayTracingGeometryInstance::UserData. Undefined on miss.
};
#if IS_PAYLOAD_ENABLED(RT_PAYLOAD_TYPE_DEFAULT)
CHECK_RT_PAYLOAD_SIZE(FDefaultPayload)
#endif
Also Here you can have a look how a Generic Closest Hit Looks like, maybe it will help where to search: DirectX Raytracing (DXR) Functional Spec | DirectX-Specs
EDIT: This guy (UE4.27: Ray Traced Light Diffraction – The Graphic Guy Squall) made a Functional Closest Hit Shader where he does get the Material Data Into the Payload struct. I havent done much Research Trying to Understad his implementation as I already have a Raytracing shader with Working Payloads
Thank you for your reply!
I noticed that there are some ClosestHit shaders in RayTracingMaterialDefaultHitShaders.usf
, but none of them seem to read material information.
After exploring the UE source code, I believe the key to enabling a custom ClosestHit shader to read material data might lie in RayTracingMaterialHitShaders.usf
and RayTracingMaterialHitShaders.cpp
, particularly in the MaterialCHS
function:
RAY_TRACING_ENTRY_CLOSEST_HIT(MaterialCHS,
FPackedMaterialClosestHitPayload, PackedPayload,
FRayTracingIntersectionAttributes, Attributes)
{
PackedPayload.HitT = RayTCurrent();
......
// Fill payload with material data
GetMaterialPayload(PixelMaterialInputs, MaterialParameters, Interpolants, PackedPayload.IsEnableSkyLightContribution(), Payload);
......
}
However, I’m not quite sure how to proceed from here. I appreciate the link you shared—it seems like it involves modifications and customization of this part as well. I’ll take a closer look and study the code carefully.
Ok, I think I figured out what is missing in the tutorial
As I thought, it totally misses the geometry binds to the hit shaders and to do that we need
- bind geometry from the current view to the shader
- set it to the hit shader group using RHICmdList.SetRayTracingHitGroups()
in Engine/Plugins/FX/Niagara/Source/NiagaraShader/Private/NiagaraAsyncGpuTraceProviderHwrt.cpp there is a nice piece of code for doing that
static void BindNiagaraRayTracingMeshCommands(...)
{
FConcurrentLinearBulkObjectAllocator Allocator;
FRayTracingLocalShaderBindings* Bindings = nullptr;
FRHIUniformBuffer** UniformBufferArray = nullptr;
if (RHICmdList.Bypass())
{
Bindings = reinterpret_cast<FRayTracingLocalShaderBindings*>(Allocator.Malloc(MergedBindingsSize, alignof(FRayTracingLocalShaderBindings)));
UniformBufferArray = reinterpret_cast<FRHIUniformBuffer**>(Allocator.Malloc(UniformBufferArraySize, alignof(FRHIUniformBuffer*)));
}
else
{
Bindings = reinterpret_cast<FRayTracingLocalShaderBindings*>(RHICmdList.Alloc(MergedBindingsSize, alignof(FRayTracingLocalShaderBindings)));
UniformBufferArray = reinterpret_cast<FRHIUniformBuffer**>(RHICmdList.Alloc(UniformBufferArraySize, alignof(FRHIUniformBuffer*)));
}
...
for (const FRayTracingShaderBindingData DirtyShaderBinding : DirtyShaderBindings)
{
const FRayTracingMeshCommand& MeshCommand = *DirtyShaderBinding.RayTracingMeshCommand;
FRayTracingLocalShaderBindings Binding = {};
Binding.RecordIndex = DirtyShaderBinding.SBTRecordIndex;
Binding.Geometry = DirtyShaderBinding.RayTracingGeometry;
Binding.SegmentIndex = MeshCommand.GeometrySegmentIndex;
Binding.UserData = PackUserData(MeshCommand);
Binding.UniformBuffers = UniformBufferArray;
Binding.NumUniformBuffers = NumUniformBuffers;
Bindings[BindingIndex] = Binding;
BindingIndex++;
}
RHICmdList.SetRayTracingHitGroups(
SBT,
Pipeline,
NumTotalBindings,
Bindings,
bCopyDataToInlineStorage);
RHICmdList.SetRayTracingMissShader(SBT, 0, Pipeline, 0 /* ShaderIndexInPipeline */, 0, nullptr, 0);
RHICmdList.CommitShaderBindingTable(SBT);
}
After implementing this part (just copy this function to RayGenTest class and call it after setting the pipeline), and all works as expected
This is my custom hw RT shader that set depth (aka normalized RayCurrentT value) as output color
After doing that, I guess you’re free to pick and use any payload you want
one more thing: the cool thing is that code seems working also on Linux and Vulkan SM6
Still getting sometimes failed assert on retrieving the RT scene, but not always and always with the same code, so I guess some sync issue in the GraphBuild calls… I definitely need to go deeper there, cause I dont like this lack of determinism and also the delay in the testrunner