DXR-RTX Path Tracer Project HLSL Shader Code
Table of contents
Links to series
- DXR Path Tracer Introduction
- DXR Path Tracer Usage
- DXR Path Tracer Host-Side Explanation
- DXR Path Tracer HLSL Explanation
Just a heads up, I did not work on the main path tracing hlsl code, but I will try my best to explain it. I worked on mainly the host code and linking the hlsl with the host
Here are links to my group members who handled most of the HLSL part, if you wish to contact them:
What is HLSL?
HLSL stands for
have long sleep lie, which is when you sleep for so long that you feel tired.
Haha, jokes aside, HLSL or High Level Shading Language, is the shader language used alongside DirectX.
High level, more like Hard Level
You can skip down to path tracing if you’re more interested in that part. This part answers questions mentioned in the previous host post)
From the last post about the host code, there are some answers answers to be answered:
- Why did each descriptor range have its own space?
- Why did objects have offsets to each resource?
- What are instance ids used for?
Here is the image of the resource layout again:
Why did I use a different space for each descriptor range? This is because HLSL actually allows for dynamic indexing, which is basically a unbounded array
It’s sort of like a pointer with an unbounded amount of indexing
Why does it matter?
The goal is to load as many vertices, indices, textures, materials and objects as possible!
How would this be done with having a defined array size?
It still can be, but what if a new texture is to be added? Oops, the entire root signature and heap descriptor has to be updated!
Let’s say we didn’t defined spaces, where by default, everything starts in space0.
One would still have to manually offset to the next index.
the code had this:
HLSL Compiler yells, stating why is
t0 register exist in both
Indices?. The correct register for
Indices is the end of the number of
Let’s say, we have five vertex elements,
Indices should actually be like this:
Welp, hardcoded array sizes. One can’t arbitrarily load number of vertices/indices.
This is why spaces is used; they allow for arbitrary number of array of resources without messing with offsets.
Here’s what is actually contained in the object structure:
It’s called Info, but it will still be referred as the object structure
As you may recall, each top level instance points to a lower level structure.
Here is the image from Nvidia again:
*image taken from the Nvidia post
How does each top level instance refer to what texture/material it needs?
The instance structure can’t hold that information since it only can contain the transformation, instance id and the bottom level acceleration structure (model), it is referring to.
So, this is where the object structure comes in. It contains the offset to the model, the textures and materials. (Why model again?, because the vertices may contain the normals as well if there isn’t a normal map)
Thus, in the code, we can grab the correct diffuse texture by indexing the diffuse texture array with the info offset:
Wait so, how do we index into the infos structure then?
Remember from the last post about
where i is the object’s index. The
instance id is passed to the hlsl side.
One can access the
instance id by doing the following:
Now, we know which object index the code tracing on, so the
infos array can be accessed as so:
Awesome. If you didn’t fully understand that part, no worries. The whole idea is to access the proper textures from the object’s id.
Now to the more interesting part – path tracing
Path tracing is a graphics technique that produces realistic images by shooting a ray into the scene from the pixel on the screen and randomly bouncing off walls until the ray hits a light. As the ray bounces off the walls, the color from the object that was bounced off of contributes to the ray’s color. Thus, when the ray hits a light, the ray’s color becomes the pixel’s color.
The image below shows a ray being show from the pixel and bouncing until it hits a light source:
Unlike a ray tracer, where rays bounces towards a light source, a path tracer bounces rays in a random direction, making the ray colors more natural, but also can take longer to compute since there may be more bounces.
Show codez pls
The path tracer code begins by calling DispatchRays (A DXR function to generate rays)
Then, we have three special functions in the hlsl shader code
These shaders are explained very well in Nvidia’s videos/post
Ray Generation Shader
The ray generation shader is invoked when a ray ready to be spawned and one can grab the pixel that the ray is spawning with
In this shader, one can invoke
TraceRay(...) to generate a new ray
With path tracing, it needs to accumulate colors to make the image more realistic, so, for each pixel, the code makes
depth calls to
TraceRay for each pixel. This is creates more realistic images as the ray bounces are random, so colors accumulate differently.
Closest Hit Shader
When a ray hits an object, the
closest hit shader is invoked
payload is updated by computing the new color from the object that was hit. (payload is the information the ray is carrying, which in our path tracer, only contains the color)
So, for example, if the object had a diffuse texture, the color could be computed by doing the following:
So, for each bounce, if the ray strikes an object, the color will be sampled from the object and multiplied by the payload’s color
This part is where also the
normals are factored in and the
material properties such as
refractiveness to influence the payload’s color.
The miss shader is invoked when the ray did not hit any object. For example, if there were no objects in the scene, then the miss shader will always be invoked.
The miss shader just sets the
payload color to the background color, which is black.
Working path tracer!
That’s pretty much how the path tracer works!
Let me know if you have any questions!