DXR Raytracing Effect from Scratch

Hi,
I am trying to simulate a simple lidar scanner with DXR. I am running this in a custom pass volume. The problem I have is that the closestHit shader never gets triggered. I am only getting misses. I am guessing my accelerationStructure is somehow messed up. Can somebody give me a hint what I am doing wrong? I am stuck on this for 3 hours now.
To build the AS I just loop through some tagged objects and add their MeshRenderers. I then call build and pass the AS to the shader.

Custom Pass Execute Function:

protected override void Execute(ScriptableRenderContext renderContext, CommandBuffer cmd, HDCamera camera, CullingResults cullingResult)
        {
            if (scanner != null)
            {
                if (EditorApplication.isPlaying)
                {           
                    RayTracingAccelerationStructure accelerationStructure = new RayTracingAccelerationStructure();
                    GameObject[] obstacles = GameObject.FindGameObjectsWithTag("ObstacleRaytraced");

                    foreach(GameObject obj in obstacles) {
                        MeshRenderer ren = obj.GetComponent<MeshRenderer>();
                        accelerationStructure.AddInstance(ren);
                    }

                    //accelerationStructure.Update();
                    accelerationStructure.Build();

                    //cmd.BuildRayTracingAccelerationStructure(accelerationStructure);
                    cmd.SetRayTracingAccelerationStructure(shader, "_RaytracingAccelerationStructure", accelerationStructure);
                    cmd.SetRayTracingShaderPass(shader, "LidarPass");

                    cmd.SetRayTracingTextureParam(shader, Shader.PropertyToID("RenderTarget"), m_renderTex);
                    Vector4 position = scanner.RayOrigin.position;
                    Vector4 dir  = scanner.RayOrigin.forward;

                    cmd.SetRayTracingVectorParam(shader, Shader.PropertyToID("originPos"), position);
                    cmd.SetRayTracingVectorParam(shader, Shader.PropertyToID("originDir"), dir);

                    cmd.DispatchRays(shader, "MyRaygenShader", scanner.SamplesPerScan, 1, 1);

                    RenderTexture.active = m_renderTex;

                    m_resultTex.ReadPixels(new Rect(0, 0, scanner.SamplesPerScan, 1), 0, 0);
                    var data = m_resultTex.GetPixels();

                    RenderTexture.active = null;
                    if (debug)
                    {
                        //Debug.Log("Size: " + m_resultTex.width);
                        for (int x = 0; x < m_resultTex.width; x++)
                        {
                            if (data[x] != new Color(0, 0, 0, 0))
                                Debug.Log(data[x]);
                        }
                    }
                }
            }
        }

Raytrace Shader:

RWTexture2D<float4> RenderTarget;
float4 originPos;
float4 originDir;

#pragma max_recursion_depth 1

//Includes omitted
//...

struct RayPayload { float4 color; uint2 launchIdx; float3 position;};

[shader("raygeneration")]
void MyRaygenShader(){

    uint2 launchIdx = DispatchRaysIndex().xy;
    //uint2 launchDim = DispatchRaysDimensions().xy;

    RayDesc ray;
    ray.Origin = originPos;
    ray.Direction = normalize(originDir);
    ray.TMin = 0;  
    ray.TMax = _RaytracingRayMaxLength;

    RayPayload payload;
    payload.color = float4(0, 0, 0, 0);
    payload.position = float3(0,0,0);
    TraceRay(_RaytracingAccelerationStructure, RAY_FLAG_NONE, 0xFF, 0, 1, 0, ray, payload);
    RenderTarget[launchIdx] = payload.color;

}

[shader("miss")]
void Miss(inout RayPayload payload : SV_RayPayload){
    //rayDirection = WorldRayDirection();
    payload.color = float4(0,0,0,0);
}


[shader("closesthit")]
void ClosestHit(inout RayPayload payload : SV_RayPayload, AttributeData attributeData : SV_IntersectionAttributes)
{
    payload.color= float4(1, 1, 1, 1);
}

Thanks a lot for your help!

Okay, small success: I managed to validate that my AS is correct. I can see that the Miss Shader does NOT get called when the ray intersects with geometry, but the closestHitShader does not get called either. The payload is unmodified in this case. Does the ClosestHitShader have to enabled somewhere? I can only see the flag to disable it, which I assume means its enabled by default…

@GeneralLee Yes, closest hit is enabled by default. The problem is probably because you are using _RaytracingRayMaxLength but never setting its value

Acceleration structure build is a GPU operation meaning that it has to be done in the same command buffer where this is actually used. Could you try replacing accelerationStructure.Build(); with cmd.BuildRayTracingAccelerationStructure(accelerationStructure).

ClosestHit shaders in the same raytrace file don’t work currently. It needs to be a pass named “LidarPass” in your case in the actual *.shader file that your materials use.

Is there any documentation that explains how to do what OP asked about? I am looking for similar information. It seems I am even unable to define a Miss shader in my .raytrace file underneath my Ray Generation shader. An example project that demonstrates how to include your own custom raygen shader, hit shader, anyhit shader, intersection shader, and miss shader would be incredibly helpful! I am working on a project where I need to use nonlinear raytracing where the ray follows a parametric curve, so I need explicit control over the raytracing process through these different shaders. Unfortunately, I have not found documentation that has helped me do this in Unity yet!

Also, this presentation :

https://on-demand.gputechconf.com/siggraph/2019/pdf/sig940-getting-started-with-directx-ray-tracing-in-unity.pdf

seems to have some conflicting info. Their closesthit shader does not use “LidarPass,” so I’m confused on what info is up to date / valid and what isn’t. I think this all can be cleared up with a simple demo project that implements these different shaders. I’d be happy to help put that demo project together with the help of this community.

Hi, @TheLazyEngineer

I attached a very simple project that I use for testing. In Game View it generates a ray tracing image. You can click on Play to make the teapot rotate and use WASD + hold right mouse button to navigate. Scene View doesn’t look great because the shaders used in normal rendering don’t do anything. I tested the project in Unity 2020.1.2f1.

The scene should look like this:

  • ray tracing setup and execution is in RayTracingTest.cs.
  • check the main camera in the hierarchy where the script above is specified + the raytrace shader.
  • the raygeneration shader is in RayTracingTest.raytrace
  • there are different materials each with its own ray tracing shader.
  • there is also a procedural intersection material (using an intersection shader) - the red sphere.

All the ray tracing shaders use a ShaderPass called Test. That name is specified in RayTracingTest.cs before ray tracing execution with SetShaderPass.

Have fun!

6266142–691716–BuiltInRP.zip (8.24 MB)

3 Likes

@INedelcu this is exactly what I had in mind. Thank you for sharing this, it will be very helpful to me and others.

I have another question that I’d love to get your insights on:

I want to implement a non linear ray tracing shader. Basically, I want to ray trace along a parametric curve. So, I want to generate a ray with a small fixed length (TMax - TMin is small), and incrementally advance the ray along the curve until the total distance traveled by the ray reaches some threshold. At each step, the ray must check for intersections, and if there is a Miss, then the ray should calculate its new origin and direction and try again. So, kind of like ray marching along a curve.

I suppose I could implement this in an intersection shader where use the ray from the generation shader is used and updated in a loop, and checked for intersection at each iteration. However, I think this would mean I need to re-implement the optimized DX built in Triangle intersection routine because the intersection checks will now be in my own custom intersection shader which is now also responsible for curving the ray. I’d rather not do this because I want to use the optimized DX built in triangle intersection shader. Is this a callable shader? I could see this working if I am able to call the built in triangle intersection shader from my custom intersection shader at each iteration of the loop.

I could also recursively call TraceRay() in a miss shader. The ray generation shader would produce a small ray, and if the miss shader is called, it will recast the next ray with the updated direction and origin. However, it looks like there is a recursion depth limit of 31, and I do not think it will be enough, so I don’t think this will work.

Maybe there’s a more appropriate way of achieving this effect? Any ideas on the best way to proceed?

Thanks!

If I understood correctly what you are trying to achieve, I would suggest you to write this algorithm in the raygeneration shader. One benefit is that you can have as many iterations as you want. Do you wish to continue the curved ray after it intersects a geometry or it will stop?

Using a for loop to trace along the curve would look like this (I didn’t try this in Unity!!!)

struct RayMarchPayload
{
     bool missed;
};

[shader("miss")]
void MissShader0(inout RayMarchPayload payload : SV_RayPayload)
{
    payload.missed = true;
}

struct RayShadingPayload
{
     float3 color;
};

void MissShader1(inout RayShadingPayload payload : SV_RayPayload)
{
    payload.color = float3(0, 0, 0);   // or some sky color.
}

[shader("raygeneration")]
void MainRayGenShader()
{
 
    ...
 
    const uint steps = 128;

    RayDesc ray;
    ray.TMin = 0;
    ray.TMax = maxRayLength / steps;     // the incremental step along the curved ray.
    ray.Origin = curvedRayOrigin;
    // ray.Direction is computed inside the loop;
    
    RayMarchPayload payload;
    payload.missed = false;

    // This loop detects which ray segment will intersect the geometry.
    for (uint i = 1; i < steps; i++)
    {
          float t = (float)i / (float)(steps - 1);   // you can use this t in [0, 1] to evaluate the curve.

          float3 pointOnCurve = EvaluateCurve(..., t, ...)

          ray.Direction = normalize(pointOnCurve - ray.Origin);

          payload.missed = false;

          const uint missShaderIndex = 0;
          TraceRay(g_SceneAccelStruct, RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH | RAY_FLAG_SKIP_CLOSEST_HIT_SHADER, 0xFF, 0, 1, missShaderIndex,  ray, payload);

         // if payload.missed is still false after TraceRay it means it actually hit some geometry inside the current segment. Note that MissShader0 is setting it to true.
         if (payload.missed == false)
              break;

          ray.Origin = pointOnCurve; // new origin for the next iteration;
    }

    // Now you can use ray again in a new TraceRay to do the actual shading. If you wish to execute a closest hit shader then it will look like this:
   if (payload.missed == false)
   {
           RayShadingPayload payloadShading;
           payloadShading.color = float3(0, 0, 0);

           const uint missShaderIndex = 1;
           TraceRay(g_SceneAccelStruct, 0, 0xFF, 0, 1, missShaderIndex,  ray, payloadShading);

           // write payloadShading.color to a texture
   }   
}

@INedelcu , yes this will work. Thank you for the help.

1 Like

I have a simple path tracing demo here GitHub - INedelcu/PathTracingDemo: A path tracing demo using hardware accelerated ray tracing in Unity

3 Likes

I wrote a technical document about vertex attributes sampling and interpolation in closest hit and any hit shaders -

3 Likes

I have one questions?
if mtiuple AS was bind. how I can get mesh data for different AS.

Binding multiple RayTracingAccelerationStructures to the same DispatchRays command is not supported and will probably never be supported.

1 Like

hello, I have tried using Ray Tracing in built-in pipline, it works,but when I use TraceRay in closesthit shader, the unity crashed, I tried in many versions from 2019.4 to 2021,everytime I use the TraceRay func in closesthit shader, unity crashed, is this a bug?

Hi @Sunday14 !

Unfortunately with ray tracing is very easy to make the GPU crash and Unity will crash with it.

You can paste your closest hit shader here to have a look.

You should be aware of a few important things when calling TraceRay in a closest hit shader. One is that you can’t exceed the maximum recursion depth that is declared in the raytrace shader - #pragma max_recursion_depth 10. So your shader logic has to ensure that. In that example I use that payload.bounceIndex. You can use it. You also have to use exactly the same ray payload structure that the raytrace shader used (the primary TraceRay). If you already do these then it might be a bug and you can fill a bug report and attach your project to it.