418 lines
13 KiB
HTML
418 lines
13 KiB
HTML
<meta charset="utf-8" lang="en">
|
|
**NVIDIA Vulkan Ray Tracing Tutorial**
|
|
**Anyhit Shaders**
|
|
|
|
<small>Authors: [Martin-Karl Lefrançois](https://devblogs.nvidia.com/author/mlefrancois/), Neil Bickford </small>
|
|
|
|
|
|

|
|
|
|
This is an extension of the Vulkan ray tracing [tutorial](vkrt_tutorial.md.html).
|
|
|
|
Like closest hit shaders, any hit shaders operate on intersections between rays and geometry. However, the any hit
|
|
shader will be executed for all hits along the ray. The closest hit shader will then be invoked on the closest accepted
|
|
intersection.
|
|
|
|
The any hit shader can be useful for discarding intersections, such as for alpha cutouts for example, but can also be
|
|
used for simple transparency. In this example we will show what is needed to do to add this shader type and to create a
|
|
transparency effect.
|
|
|
|
!!! Note Note
|
|
This example is based on many elements from the [Antialiasing Tutorial](vkrt_tuto_jitter_cam.md.html).
|
|
|
|
(insert setup.md.html here)
|
|
|
|
# Any Hit
|
|
|
|
## `raytrace.rahit`
|
|
|
|
Create a new shader file `raytrace.rahit` and rerun CMake to have it added to the solution.
|
|
|
|
This shader starts like `raytrace.chit`, but uses less information.
|
|
~~~~ C++
|
|
#version 460
|
|
#extension GL_EXT_ray_tracing : require
|
|
#extension GL_EXT_nonuniform_qualifier : enable
|
|
#extension GL_EXT_scalar_block_layout : enable
|
|
#extension GL_GOOGLE_include_directive : enable
|
|
|
|
#include "random.glsl"
|
|
#include "raycommon.glsl"
|
|
#include "wavefront.glsl"
|
|
|
|
// clang-format off
|
|
layout(location = 0) rayPayloadInEXT hitPayload prd;
|
|
|
|
layout(binding = 2, set = 1, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc;
|
|
layout(binding = 4, set = 1) buffer MatIndexColorBuffer { int i[]; } matIndex[];
|
|
layout(binding = 5, set = 1, scalar) buffer Vertices { Vertex v[]; } vertices[];
|
|
layout(binding = 6, set = 1) buffer Indices { uint i[]; } indices[];
|
|
layout(binding = 1, set = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[];
|
|
// clang-format on
|
|
~~~~
|
|
|
|
!!! Note
|
|
You can find the source of `random.glsl` in the Antialiasing Tutorial [here](../ray_tracing_jitter_cam/README.md#toc1.1).
|
|
|
|
|
|
For the any hit shader, we need to know which material we hit, and whether that material supports transparency. If it is
|
|
opaque, we simply return, which means that the hit will be accepted.
|
|
|
|
~~~~ C++
|
|
void main()
|
|
{
|
|
// Object of this instance
|
|
uint objId = scnDesc.i[gl_InstanceCustomIndexEXT].objId;
|
|
// Indices of the triangle
|
|
uint ind = indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 0];
|
|
// Vertex of the triangle
|
|
Vertex v0 = vertices[nonuniformEXT(objId)].v[ind.x];
|
|
|
|
// Material of the object
|
|
int matIdx = matIndex[nonuniformEXT(objId)].i[gl_PrimitiveID];
|
|
WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIdx];
|
|
|
|
if (mat.illum != 4)
|
|
return;
|
|
~~~~
|
|
|
|
Now we will apply transparency:
|
|
|
|
~~~~ C++
|
|
if (mat.dissolve == 0.0)
|
|
ignoreIntersectionEXT();
|
|
else if(rnd(prd.seed) > mat.dissolve)
|
|
ignoreIntersectionEXT();
|
|
}
|
|
~~~~
|
|
|
|
As you can see, we are using a random number generator to determine if the ray hits or ignores the object. If we
|
|
accumulate enough rays, the final result will converge to what we want.
|
|
|
|
## `raycommon.glsl`
|
|
|
|
The random `seed` also needs to be passed in the ray payload.
|
|
|
|
In `raycommon.glsl`, add the seed:
|
|
|
|
~~~~ C++
|
|
struct hitPayload
|
|
{
|
|
vec3 hitValue;
|
|
uint seed;
|
|
};
|
|
~~~~
|
|
|
|
## Adding Any Hit to `createRtPipeline`
|
|
|
|
The any hit shader will be part of the hit shader group. Currently, the hit shader group only contains the closest hit shader.
|
|
|
|
In `createRtPipeline()`, after loading `raytrace.rchit.spv`, load `raytrace.rahit.spv`
|
|
|
|
~~~~ C++
|
|
vk::ShaderModule ahitSM =
|
|
nvvk::createShaderModule(m_device, //
|
|
nvh::loadFile("shaders/raytrace.rahit.spv", true, paths));
|
|
~~~~
|
|
|
|
add the any hit shader to the hit group
|
|
|
|
~~~~ C++
|
|
hg.setClosestHitShader(static_cast<uint32_t>(stages.size()));
|
|
stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, chitSM, "main"});
|
|
hg.setAnyHitShader(static_cast<uint32_t>(stages.size()));
|
|
stages.push_back({{}, vk::ShaderStageFlagBits::eAnyHitKHR, ahitSM, "main"});
|
|
m_rtShaderGroups.push_back(hg);
|
|
~~~~
|
|
|
|
and at the end, delete it:
|
|
|
|
~~~~ C++
|
|
m_device.destroy(ahitSM);
|
|
~~~~
|
|
|
|
## Give access of the buffers to the Any Hit shader
|
|
|
|
In `createDescriptorSetLayout()`, we need to allow the Any Hit shader to access some buffers.
|
|
|
|
This is the case for the material and scene description buffers
|
|
|
|
~~~~ C++
|
|
// Materials (binding = 1)
|
|
m_descSetLayoutBind.emplace_back(
|
|
vkDS(1, vkDT::eStorageBuffer, nbObj,
|
|
vkSS::eVertex | vkSS::eFragment | vkSS::eClosestHitKHR | vkSS::eAnyHitKHR));
|
|
// Scene description (binding = 2)
|
|
m_descSetLayoutBind.emplace_back( //
|
|
vkDS(2, vkDT::eStorageBuffer, 1,
|
|
vkSS::eVertex | vkSS::eFragment | vkSS::eClosestHitKHR | vkSS::eAnyHitKHR));
|
|
~~~~
|
|
|
|
and also for the vertex, index and material index buffers:
|
|
|
|
~~~~ C++
|
|
// Materials (binding = 4)
|
|
m_descSetLayoutBind.emplace_back( //
|
|
vkDS(4, vkDT::eStorageBuffer, nbObj,
|
|
vkSS::eFragment | vkSS::eClosestHitKHR | vkSS::eAnyHitKHR));
|
|
// Storing vertices (binding = 5)
|
|
m_descSetLayoutBind.emplace_back( //
|
|
vkDS(5, vkDT::eStorageBuffer, nbObj, vkSS::eClosestHitKHR | vkSS::eAnyHitKHR));
|
|
// Storing indices (binding = 6)
|
|
m_descSetLayoutBind.emplace_back( //
|
|
vkDS(6, vkDT::eStorageBuffer, nbObj, vkSS::eClosestHitKHR | vkSS::eAnyHitKHR));
|
|
~~~~
|
|
|
|
## Opaque Flag
|
|
|
|
In the example, when creating `VkAccelerationStructureGeometryKHR` objects, we set their flags to `vk::GeometryFlagBitsKHR::eOpaque`. However, this avoided invoking the any hit shader.
|
|
|
|
We could remove all of the flags, but another issue could happen: the any hit shader could be called multiple times for the same triangle. To have the any hit shader process only one hit per triangle, set the `eNoDuplicateAnyHitInvocation` flag:
|
|
|
|
~~~~ C++
|
|
geometry.setFlags(vk::GeometryFlagBitsKHR::eNoDuplicateAnyHitInvocation);
|
|
~~~~
|
|
|
|
## `raytrace.rgen`
|
|
|
|
If you have done the previous [Jitter Camera/Antialiasing](../ray_tracing_jitter_cam) tutorial,
|
|
you will need just a few changes.
|
|
|
|
First, `seed` will need to be available in the any hit shader, which is the reason we have added it to the hitPayload structure.
|
|
|
|
Change the local `seed` to `prd.seed` everywhere.
|
|
|
|
~~~~ C++
|
|
prd.seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pushC.frame);
|
|
~~~~
|
|
|
|
For optimization, the `TraceRayEXT` call was using the `gl_RayFlagsOpaqueEXT` flag. But
|
|
this will skip the any hit shader, so change it to
|
|
|
|
~~~~ C++
|
|
uint rayFlags = gl_RayFlagsNoneEXT;
|
|
~~~~
|
|
|
|
## `raytrace.rchit`
|
|
|
|
Similarly, in the closest hit shader, change the flag to `gl_RayFlagsSkipClosestHitShaderEXT`, as we want to enable the any hit and miss shaders, but we still don't care
|
|
about the closest hit shader for shadow rays. This will enable transparent shadows.
|
|
|
|
~~~~ C++
|
|
uint flags = gl_RayFlagsSkipClosestHitShaderEXT;
|
|
~~~~
|
|
|
|
# Scene and Model
|
|
|
|
For a more interesting scene, you can replace the `helloVk.loadModel` calls in `main()` with the following scene:
|
|
|
|
~~~~ C++
|
|
helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths));
|
|
helloVk.loadModel(nvh::findFile("media/scenes/sphere.obj", defaultSearchPaths),
|
|
nvmath::scale_mat4(nvmath::vec3f(1.5f))
|
|
* nvmath::translation_mat4(nvmath::vec3f(0.0f, 1.0f, 0.0f)));
|
|
helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths));
|
|
~~~~
|
|
|
|
## OBJ Materials
|
|
|
|
By default, all objects are opaque, you will need to change the material description.
|
|
|
|
Edit the first few lines of `media/scenes/wuson.mtl` and `media/scenes/sphere.mtl` to use a new illumination model (4) with a dissolve value of 0.5:
|
|
|
|
~~~~ C++
|
|
newmtl default
|
|
illum 4
|
|
d 0.5
|
|
...
|
|
~~~~
|
|
|
|
# Accumulation
|
|
|
|
As mentioned earlier, for the effect to work, we need to accumulate frames over time. Please implement the following from [Jitter Camera/Antialiasing](vkrt_tuto_jitter_cam.md):
|
|
|
|
* [Frame Number](vkrt_tuto_jitter_cam.md.htm#toc1.2)
|
|
* [Storing or Updating](vkrt_tuto_jitter_cam.md.htm#toc1.4)
|
|
* [Application Frame Update](vkrt_tuto_jitter_cam.md.htm#toc2)
|
|
|
|
|
|
# Fixing Pipeline
|
|
|
|
The above code works, but might not work in the future. The reason is, the shadow ray `traceRayEXT` call in the Closest Hit shader, uses payload 1
|
|
and when intersecting the object, the any hit shader will be executed using payload 0. In the time of writing those lines, the driver add
|
|
padding and there are no side effect, but this is not how thing should be done.
|
|
|
|
Each `traceRayEXT` invocation should have as many Hit Groups as there are trace calls with different payload. For the other examples, it is still fine,
|
|
because we are using the `gl_RayFlagsSkipClosestHitShaderNV` flag and the closest hit shader (payload 0) will not be called and there were not
|
|
any hit or intersection shaders in the Hit Group. But in this example, the closest hit will be skiped, but not the any hit.
|
|
|
|
**To fix this**, we need to add another hit group.
|
|
|
|
This is how the current SBT looks like.
|
|
|
|

|
|
|
|
And we need to add the following to the ray tracing pipeline, a copy of the previous Hit Group, with a new AnyHit using the proper payload.
|
|
|
|

|
|
|
|
|
|
## New shaders
|
|
|
|
Create two new files `raytrace_0.ahit` and `raytrace_1.ahit`, and rename `raytrace.ahit` to `raytrace_ahit.glsl`
|
|
|
|
!!! WARNING CMake
|
|
Cmake need to be re-run to add the new files to the project.
|
|
|
|
In `raytrace_0.ahit` add the following code
|
|
|
|
~~~~ C
|
|
#version 460
|
|
#extension GL_GOOGLE_include_directive : enable
|
|
|
|
#define PAYLOAD_0
|
|
#include "raytrace_rahit.glsl"
|
|
~~~~
|
|
|
|
and in `raytrace_1.ahit`, replace `PAYLOAD_0` by `PAYLOAD_1`
|
|
|
|
Then in `raytrace_ahit.glsl` remove the `#version 460` and add the following code, so that we have the right layout.
|
|
|
|
~~~~ C
|
|
#ifdef PAYLOAD_0
|
|
layout(location = 0) rayPayloadInNV hitPayload prd;
|
|
#elif defined(PAYLOAD_1)
|
|
layout(location = 1) rayPayloadInNV shadowPayload prd;
|
|
#endif
|
|
~~~~
|
|
|
|
## New Payload
|
|
|
|
We cannot simply have a bool for our shadow ray payload. We also need the `seed` for the random function.
|
|
|
|
In the `raycommon.glsl` file, add the following structure
|
|
|
|
~~~~ C
|
|
struct shadowPayload
|
|
{
|
|
bool isHit;
|
|
uint seed;
|
|
};
|
|
~~~~
|
|
|
|
The usage of the shadow payload is done in the closest hit and shadow miss shader. First, let's modify `raytraceShadow.rmiss` to look like this
|
|
|
|
~~~~ C
|
|
#version 460
|
|
#extension GL_NV_ray_tracing : require
|
|
#extension GL_GOOGLE_include_directive : enable
|
|
|
|
#include "raycommon.glsl"
|
|
|
|
layout(location = 1) rayPayloadInNV shadowPayload prd;
|
|
|
|
void main()
|
|
{
|
|
prd.isHit = false;
|
|
}
|
|
~~~~
|
|
|
|
The the change in the closest hit shader `raytrace.rchit`, need to change the usage of the payload, but also the call to `traceRayEXT`
|
|
|
|
Replace the payload to
|
|
|
|
~~~~ C
|
|
layout(location = 1) rayPayloadNV shadowPayload prdShadow;
|
|
~~~~
|
|
|
|
Then just before the call to `traceRayEXT`, initialize the values to
|
|
|
|
~~~~ C
|
|
prdShadow.isHit = true;
|
|
prdShadow.seed = prd.seed;
|
|
~~~~
|
|
|
|
and after the trace, set the seed value back to the main payload
|
|
|
|
~~~~ C
|
|
prd.seed = prdShadow.seed;
|
|
~~~~
|
|
|
|
And check if the trace shadow hit an object of not
|
|
|
|
~~~~ C
|
|
if(prdShadow.isHit)
|
|
~~~~
|
|
|
|
### traceRayEXT
|
|
|
|
When we call `traceRayEXT`, since we are using the payload 1 (last argument), we also
|
|
need the trace to hit the alternative hit group, the one using the payload 1.
|
|
To do this, we need to set the sbtRecordOffset to 1
|
|
|
|
~~~~ C
|
|
traceRayEXT(topLevelAS, // acceleration structure
|
|
flags, // rayFlags
|
|
0xFF, // cullMask
|
|
1, // sbtRecordOffset
|
|
0, // sbtRecordStride
|
|
1, // missIndex
|
|
origin, // ray origin
|
|
tMin, // ray min range
|
|
rayDir, // ray direction
|
|
tMax, // ray max range
|
|
1 // payload (location = 1)
|
|
);
|
|
~~~~
|
|
|
|
|
|
|
|
|
|
## Ray tracing Pipeline
|
|
|
|
The final step is to add the new Hit Group. This is a change in `HelloVulkan::createRtPipeline()`.
|
|
We need to load the new any hit shader and create a new Hit Group.
|
|
|
|
Replace the `"shaders/raytrace.rahit.spv"` for `"shaders/raytrace_0.rahit.spv"`
|
|
|
|
Then, after the creating of the first Hit Group, create a new one, where only the any hit using payload 1
|
|
is added. We are skipping the closest hit shader in the trace call, so we can ignore it in the Hit Group.
|
|
|
|
~~~~ C
|
|
// Payload 1
|
|
vk::ShaderModule ahit1SM =
|
|
nvvk::createShaderModule(m_device, //
|
|
nvh::loadFile("shaders/raytrace_1.rahit.spv", true, paths));
|
|
hg.setClosestHitShader(VK_SHADER_UNUSED_NV); // Not used by shadow (skipped)
|
|
hg.setAnyHitShader(static_cast<uint32_t>(stages.size()));
|
|
stages.push_back({{}, vk::ShaderStageFlagBits::eAnyHitNV, ahit1SM, "main"});
|
|
m_rtShaderGroups.push_back(hg);
|
|
~~~~
|
|
|
|
At the end of the function, delete the shader module `ahit1SM`.
|
|
|
|
|
|
!!! NOTE Re-Run
|
|
Everything should work as before, but now it does it right.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Final Code
|
|
|
|
You can find the final code in the folder [ray_tracing_anyhit](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_anyhit)
|
|
|
|
|
|
<!-- Markdeep: -->
|
|
<link rel="stylesheet" href="vkrt_tutorial.css?">
|
|
<script> window.markdeepOptions = { tocStyle: "medium" };</script>
|
|
<script src="markdeep.min.js" charset="utf-8"></script>
|
|
<script src="https://developer.nvidia.com/sites/default/files/akamai/gameworks/whitepapers/markdeep.min.js" charset="utf-8"></script>
|
|
<script>
|
|
window.alreadyProcessedMarkdeep || (document.body.style.visibility = "visible")
|
|
</script>
|