www.xbdev.net
xbdev - software development
Friday May 9, 2025
Home | Contact | Support | Vulkan.. Vulkan is doing all the hard work ... | Ray-Tracing Vulkan API Going Beyond Rasterization...
     
 

Ray-Tracing Vulkan API

Going Beyond Rasterization...

 


Little discussion on some of the practical aspects of getting started with ray-tracing (and path-tracing) using the Vulkan ray-tracing extension. The article shifts primarily towards moving beyond triangles towards ray-tracing with implict shapes (procedural stuff).

I often think that ray-tracing without implict shapes is a loss! You can generate fractals and volumetric shapes - all sorts of juicy things - but most of all - it's fun and easy with ray-tracers.

Ray-tracing vs path-tracing - just to note - path-tracing is just another form of ray-tracing - but the journey of the ray through the scene uses probablity - usually over multiple frames and lots and lots of rays. When we say 'ray-tracing' all the discussion and concepts here work with both ray-tracing and path-tracing.


Path-Tracer example output from the Vulkan Ray-Tracing Extension (intersection shader).
Path-Tracer example output from the Vulkan Ray-Tracing Extension (intersection shader).


Ray|Path-Tracing (Ray-Marching)


Working with the Vulkan API (Ray-Tracing Extension)

1. Working with Triangle Meshes
2. Working with Procecural Geometry


Triangle Geomtry


Load the triangle mesh into the acceleration buffers (top and bottom) - and most of the work is done for you - you get the interection point and the triangle corners (vertices) - and you have everything you need.

The procedural ray-tracing/path-tracing is a little less documented and requires a bit more work - so we'll delve into that more.

Focus on the 'Procedural' Geometry Part


You'll use the 'aabb' geometry - and the 'intersect' shader.

Adding the intersect shader is a bit tricky - as it's not one of the required shaders - so you it takes more work to add it into the pipeline compared to the required (rmiss/rhit/rgen).


Word to the wiser - while developing your Top/Bottom acceleration structure - the flags can be a bit tricky - as the wrong flags/grouping can cause a bit of pain - often it won't crash - but your output won't work.

So it's worth switching on the 'layer' feature for the errors/warning.

Let me give you one example - this is one error I got while trying to mix both procedural and triangle geometries:

VUID_Undefined (ERROR SPEC): msgNum2044605652 Validation Error: [ VUID_Undefined ] | 
MessageID 0x79de34d4 vkGetAccelerationStructureBuildSizesKHR(): pBuildInfo[0].pGeometries[0].geometry.aabbs.sType must be VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR.

VUID-VkAccelerationStructureGeometryAabbsDataKHR-sType-sType(ERROR SPEC): msgNum1637708447 Validation Error: [ VUID-VkAccelerationStructureGeometryAabbsDataKHR-sType-sType ] | 
MessageID 0x619d729f vkGetAccelerationStructureBuildSizesKHR(): pBuildInfo[0].pGeometries[0].geometry.aabbs.sType must be VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR.
The Vulkan spec statessType must be VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR 


These extra tips in the error window can save you hours of pain and debugging - especially - when the bug is nothing shows up in the output (those are the worse errors).


'Common' includes


I found that because you're working across multiple ray-tracing shader files - you'll often have a set of common structures/defines - which is easier to manage if you use 'includes'.

Of course, 'includes' don't just work out of the gate! Instead you've got to got to 'enable' the feature with a macro at the top of each shader file you want to use includes.

After you've enabled it you can use the 'include'.

#extension GL_ARB_shading_language_include : enable

#include "common.glsl"


A real pain though - is the 'include' files aren't by default tracked for changes in the visual studio custom builds - it only tracks the 'file' that you're doing the custom build on.

If you do use 'includes' - and you're using a custom build in visual studio - modify the 'Additional Dependencies' in the custom build so it tracks changes in the include files as well (so you don't need to do a force rebuild each time you change an include file).

Ensure that a file is rebuilt when other associated files change. Here's how you can do it:

1. Open your project in Visual Studio.
2. Select the file for which you want to set up dependencies in the Solution Explorer.
3. Open the Property Pages for the selected file. You can do this by right-clicking on the file and selecting Properties.
4. In the Property Pages dialog, go to Configuration Properties > General.
5. In the Item Type property, select Custom Build Tool.
6. Click Apply. This will add a Custom Build Tool node under the Configuration Properties.
7. In the Custom Build Tool node, go to the General folder.
8. In the Additional Dependencies field, specify the files that your custom build tool depends on. Separate multiple files with semicolons.

When any of the specified files change, Visual Studio will rebuild the file associated with the custom build tool.

When I first setup the 'intersect' shader - I had a few problems - as it didn't seem to trigger - as I added it to the 'shader index table' (SBT) - with its own stage grouping in the pipeline.

However, I found it only works if you include it with the 'hit' table shader stage group, e.g.

    // Closest hit group
    
{
        
shaderStages.push_back(loadShader".//closesthit.rchit.spv"VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR));
        
shaderStages.push_back(loadShader(".//intersect.rint.spv"VK_SHADER_STAGE_INTERSECTION_BIT_KHR));

        
VkRayTracingShaderGroupCreateInfoKHR shaderGroup{};
        
shaderGroup.sType              VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR;
        
shaderGroup.type               VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR;// VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR;
        
shaderGroup.generalShader      VK_SHADER_UNUSED_KHR;
        
shaderGroup.closestHitShader   static_cast<uint32_t>(shaderStages.size()) - 2;
        
shaderGroup.anyHitShader       VK_SHADER_UNUSED_KHR;
        
shaderGroup.intersectionShader static_cast<uint32_t>(shaderStages.size()) - 1;
        
shaderGroups.push_back(shaderGroup);
    }



Intersect Shader - Procedural Checks


For the procedural geometry - e.g., implicit shapes and scenes - the intersection calculation was done in the 'intersection' shader (.rint).

The 'triggering' of the intersection (hit something) - is easy - you just call 'reportIntersectionEXT( t, 0 );'.

Which will trigger the 'closesthit' shader - and the result will eventually get back to the place tht generated the ray (e.g., ray-generation shader).

The problem was passing the information back - more than just the 'intersection distance' - but also the normal and material data.

The solution was in the 'intersection' shader - to use the 'hitAttributeEXT' value to pass the result through to the 'closesthit' - which was then copied to the 'Payload' structure to be returned to the ray generation.

To keep things simple the structure for 'hitAttributeEXT' and 'rayPayloadEXT' was kept the same - with rays only being generated in one place (raygen shader).

The common structure for hits and payload data was the same in the end, given by:

// Payload structure
struct Payload {
    
float       t;
    
vec3        normal;
    
Material    material;
};


This was the essential information for all the lighting/effect calculation - no need for information about the shape or geometry.


Path-Tracer Example


A good example to test out the setup is a 'path-tracer' as that requires multiple rays for different interactions (diffusion/refraction/reflection).

One of the tests was to construct a basic test scene of spheres and a plane - and ray-trace it using the Vulkan hardware acceleration (procedural part) - with the 'intersect' shader (.rint) - responsible for calculating the intersection with the implicit geometry and returning back the 'intersection distance', 'normal' and 'material' data.

The full code is given online in the 'vulkanlab.xbdev.net' - available here:
https://vulkanlab.xbdev.net/editor/?id=pathtracertesthardware&

Ray-Marching


Once the 'intersection' shader is up and running for implicity shapes (like spheres and rectangle planes) - it's a piece of cake to swap out the check for a 'ray-marching' loop - the collision detection code is a bit different. Step along the ray until you hit something.

Ray-marching is very important in lots of fields - as it allows you to visualize 'data' - when I say data, I mean 'graphical' data - such as x-rays, land-surveys (using scatter points/dots), LiDAR data, volumetric textures, signed distance functions (sdf) etc.

Resources & Links


VulkanLab Examples/Demos

Procedural Path-Tracer (Ray-Tracer) Code/Working Demo

Official Docs Ray-Tracing Extension

If you're unable to run the Vulkan hardware examples - as you don't have a graphics card that supports ray-tracing - you can also test out the concepts using the WebGL Lab or WebGPU Lab:

WebGLLab Examples
WebGPULab Examples






Other Related Texts You Might Find Interesting

 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2025 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.