**NVIDIA Vulkan Ray Tracing Tutorial** **Instances** Author: [Martin-Karl Lefrançois](https://devblogs.nvidia.com/author/mlefrancois/) ![](Images/callable.png) This is an extension of the Vulkan ray tracing [tutorial](vkrt_tutorial.md.htm). Ray tracing allow to use [callable shaders](https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/chap8.html#shaders-callable) in ray-generation, closest-hit, miss or another callable shader stage. It is similar to an indirect function call, whitout having to link those shaders with the executable program. (insert setup.md.html here) # Data Storage Data can only access data passed in to the callable from parent stage. There will be only one structure pass at a time and should be declared like for payload. In the parent stage, using the `callableDataEXT` storage qualifier, it could be declared like: ~~~~ C++ layout(location = 0) callableDataEXT rayLight cLight; ~~~~ where `rayLight` struct is defined in a shared file. ~~~~ C++ struct rayLight { vec3 inHitPosition; float outLightDistance; vec3 outLightDir; float outIntensity; }; ~~~~ And in the incoming callable shader, you must use the `callableDataInEXT` storage qualifier. ~~~~ C++ layout(location = 0) callableDataInEXT rayLight cLight; ~~~~ # Execution To execute one of the callable shader, the parent stage need to call `executeCallableEXT`. The first parameter is the SBT record index, the second one correspond to the 'location' index. Example of how it is called. ~~~~ C++ executeCallableEXT(pushC.lightType, 0); ~~~~ # Adding Callable Shaders to the SBT ## Create Shader Modules In `HelloVulkan::createRtPipeline()`, immediately after adding the closest-hit shader, we will add 3 callable shaders, for each type of light. ~~~~ C++ // Callable shaders vk::RayTracingShaderGroupCreateInfoKHR callGroup{vk::RayTracingShaderGroupTypeKHR::eGeneral, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; vk::ShaderModule call0 = nvvk::createShaderModule(m_device, nvh::loadFile("shaders/light_point.rcall.spv", true, paths)); vk::ShaderModule call1 = nvvk::createShaderModule(m_device, nvh::loadFile("shaders/light_spot.rcall.spv", true, paths)); vk::ShaderModule call2 = nvvk::createShaderModule(m_device, nvh::loadFile("shaders/light_inf.rcall.spv", true, paths)); callGroup.setGeneralShader(static_cast(stages.size())); stages.push_back({{}, vk::ShaderStageFlagBits::eCallableKHR, call0, "main"}); m_rtShaderGroups.push_back(callGroup); callGroup.setGeneralShader(static_cast(stages.size())); stages.push_back({{}, vk::ShaderStageFlagBits::eCallableKHR, call1, "main"}); m_rtShaderGroups.push_back(callGroup); callGroup.setGeneralShader(static_cast(stages.size())); stages.push_back({{}, vk::ShaderStageFlagBits::eCallableKHR, call2, "main"}); m_rtShaderGroups.push_back(callGroup); ~~~~ And at the end of the function, delete the shaders. ~~~~ C++ m_device.destroy(call0); m_device.destroy(call1); m_device.destroy(call2); ~~~~ ### Shaders Here are the source of all shaders * [light_point.rcall](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/blob/master/ray_tracing_callable/shaders/light_point.rcall) * [light_spot.rcall](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/blob/master/ray_tracing_callable/shaders/light_spot.rcall) * [light_inf.rcall](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/blob/master/ray_tracing_callable/shaders/light_inf.rcall) ## Passing Callable to traceRaysKHR In `HelloVulkan::raytrace()`, we have to tell where the callable shader starts. Since they were added after the hit shader, we have in the SBT the following. ******************** * +---------+ * | ray-gen | * +---------+ * | miss0 | * | miss1 | * +---------+ * | hit0 | * +---------+ * | call0 | * | call1 | * | call2 | * +---------+ ******************** Therefore, the callable starts at `4 * progSize` ~~~~ C++ vk::DeviceSize callableGroupOffset = 4u * progSize; // Jump over the previous shaders vk::DeviceSize callableGroupStride = progSize; ~~~~ Then we can call `traceRaysKHR` ~~~~ C++ const vk::StridedBufferRegionKHR callableShaderBindingTable = { m_rtSBTBuffer.buffer, callableGroupOffset, progSize, sbtSize}; cmdBuf.traceRaysKHR(&raygenShaderBindingTable, &missShaderBindingTable, &hitShaderBindingTable, &callableShaderBindingTable, // m_size.width, m_size.height, 1); // ~~~~ # Calling the Callable Shaders In the closest-hit shader, instead of having a if-else case, we can now call directly the right shader base on the type of light. ~~~~ C++ cLight.inHitPosition = worldPos; //#define DONT_USE_CALLABLE #if defined(DONT_USE_CALLABLE) // Point light if(pushC.lightType == 0) { vec3 lDir = pushC.lightPosition - cLight.inHitPosition; float lightDistance = length(lDir); cLight.outIntensity = pushC.lightIntensity / (lightDistance * lightDistance); cLight.outLightDir = normalize(lDir); cLight.outLightDistance = lightDistance; } else if(pushC.lightType == 1) { vec3 lDir = pushC.lightPosition - cLight.inHitPosition; cLight.outLightDistance = length(lDir); cLight.outIntensity = pushC.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); cLight.outLightDir = normalize(lDir); float theta = dot(cLight.outLightDir, normalize(-pushC.lightDirection)); float epsilon = pushC.lightSpotCutoff - pushC.lightSpotOuterCutoff; float spotIntensity = clamp((theta - pushC.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); cLight.outIntensity *= spotIntensity; } else // Directional light { cLight.outLightDir = normalize(-pushC.lightDirection); cLight.outIntensity = 1.0; cLight.outLightDistance = 10000000; } #else executeCallableEXT(pushC.lightType, 0); #endif ~~~~ # Final Code You can find the final code in the folder [ray_tracing_callable](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_callable)