After adding a basic Nvidia RTX ray tracing example last week, I spent some more time with Vulkan and the VK_NV_ray_tracing
extension. The result are two new, more advanced examples that I just uploaded to my Vulkan C++ example repository. As with the basic example I tried to keep them as straight forward as possible with all the relevant code parts put into one source file, so that following and building upon is as easy as possible.
Unlike most of the other advanced Vulkan examples, the most interesting parts can be found inside the ray tracing shaders. Most of the C++ code is just setup to get the shaders up and running and passing data to the ray tracing hardware.
Ray traced shadows
This example adds an additional miss shader named shadow.rmiss
to the pipeline thats sets the shadowed flag payload to false if the ray does not hit any of the scene geometry:
#version 460
#extension GL_NV_ray_tracing : require
layout(location = 2) rayPayloadInNV bool shadowed;
void main()
{
shadowed = false;
}
The actual shadow casting is triggered at the end of the closest hit shader closesthit.rchit
:
|
|
Note that we need to pass the proper offsets (line 24 and 26) to traceNV
to make it use the correct shaders that have were passed to vkCreateRayTracingPipelinesNV
. Since our shadow miss shader is the second shader of that given type, we need to offset by 1.
With this setup, if the ray misses our geometry, the shadowed
ray payload will be set to false by the shadow miss shader and can be used to darken the hit color to darken shadowed parts of the scene.
Ray traced reflections
While doing real time reflections with rasterization is hard and involves lots of tricks, esp. if you want recursion and multi-directional reflections, they’re a perfect match for ray tracing. This example renders an old-school looking scene with multiple reflective objects and multiple levels of recursion for creating perfect reflections.
Reflections are traced within a loop inside the ray generation shaders rgen.rgen
:
|
|
If the ray hits a reflective surface (line 31), we reflect it’s direction using the normal of the current hit point, so the next loop iteration traces in the reflected direction.
For this shader we need some additional info on the ray hit, so instead of a single hitValue
like in the previous sample, the ray payload (line 6-10) has been expanded to include some additional data that is filled in the closes hit shaders closeshit.rchit
:
|
|
Note the use of the gl_RayTmaxNV
GLSL built-in. In a closest hit shader, this value stores the closest distance to the intersected primitive. This value is used in the above ray generation shader. Details on the VK_NV_ray_tracing
built-ins can be found in the official extension spec.
To keep things simple, we treat vertices with full white color as reflectors. In a more complex example you’d pass that info e.g. via a material buffer storing material IDs.
And this is how it looks in motion: