diff --git a/docs/index.html b/docs/index.html
index 5cfd81c..e7c1755 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -9,4 +9,4 @@
+
\ No newline at end of file
diff --git a/docs/vkrt_tuto_indirect_scissor.md.html b/docs/vkrt_tuto_indirect_scissor.md.html
new file mode 100644
index 0000000..b649eaf
--- /dev/null
+++ b/docs/vkrt_tuto_indirect_scissor.md.html
@@ -0,0 +1,1505 @@
+
+**NVIDIA Vulkan Ray Tracing Tutorial**
+**Trace Rays Indirect**
+
+Authors: David Zhao Akeley
+
+
+
+This is an extension of the [Vulkan ray tracing tutorial](vkrt_tutorial.md.html).
+
+We will discuss the `vkCmdTraceRaysIndirectKHR` command, which allows the
+`width`, `height`, and `depth` of a trace ray command to be specifed by a
+buffer on the device, rather than directly by the host. As a demonstration,
+this example will add colorful lanterns to the scene that add their own light
+and shadows, with a finite radius of effect. A compute shader will calculate
+scissor rectangles for each lantern, and an indirect trace rays command will
+dispatch rays for lanterns only within those scissor rectangles.
+
+# Outline
+
+The basic idea is to split up ray tracing into seperate passes. The first pass
+is similar to the original tutorial: it fills in the entire output image,
+calculating lighting from the main light in the scene. Subsequently, one
+pass is run within a scissor rectangle for each lantern to add its light
+contribution to the output image.
+
+The steps to accomplish this are:
+
+* Add a buffer to store lantern positions, colors, and scissor rectangles.
+ These lanterns are separate from the OBJ geometry loaded in the main
+ tutorial. Run a compute shader each frame to fill in the scissor rectangles.
+
+* Build a BLAS for a lantern, and add lantern instances to the TLAS.
+ As lanterns are self-illuminating, the closest hit shader used to shade
+ ordinary OBJ geometry is inappropriate, so, we will add a new hit group (new
+ closest-hit shader) for lanterns to the SBT, and set `hitGroupId`
+ for lantern instances to `1` so this hit group is used.
+
+* Modify the ray generation shader so that it emulates additive blending,
+ and supports drawing within scissor rectangles (in the main tutorial,
+ the raygen shader assumes the ray trace dispatch covers the whole screen).
+
+* Add shadow rays in lantern passes, cast towards the lantern whose light
+ contribution is being added in the current pass. To detect whether the
+ expected lantern was hit, we add a new miss shader and two new closest
+ hit shaders (one for OBJ instances, one for lanterns) that return the
+ index of the lantern hit (if any).
+
+* Add one `vkCmdTraceRaysIndirectKHR` call in `HelloVulkan::raytrace` for
+ each lantern in the scene.
+
+If everything goes well, we should see something like this (the "lantern debug"
+checkbox enables visualizing the scissor rectangles).
+
+
+
+# Lantern Scissor Rectangles
+
+In this step we set up the buffer storing lantern info, and a compute shader
+for calculating the scissor rectangles.
+
+## Allocate Host Storage
+
+We first need to stage the vector of lanterns on the host in a vector.
+Since this is not an animation example, we won't be concerned with
+keeping track of changes to this vector: changes are forbidden after the
+acceleration structures are built.
+
+In `hello_vulkan.h`, declare a struct for holding information about
+the lanterns on the host.
+
+```` C
+ // Information on each colored lantern illuminating the scene.
+ struct Lantern
+ {
+ nvmath::vec3f position;
+ nvmath::vec3f color;
+ float brightness;
+ float radius; // Max world-space distance that light illuminates.
+ };
+````
+
+Then declare a vector of `Lantern` and add
+a new function for configuring a new lantern in the scene.
+
+```` C
+ // Array of lanterns in scene. Not modifiable after acceleration structure build.
+ std::vector m_lanterns;
+ void addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius);
+````
+
+The `addLantern` function is implemented as
+
+```` C
+// Add a light-emitting colored lantern to the scene. May only be called before TLAS build.
+void HelloVulkan::addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius)
+{
+ assert(m_lanternCount == 0); // Indicates TLAS build has not happened yet.
+
+ m_lanterns.push_back({pos, color, brightness, radius});
+}
+````
+
+In `main.cpp`, we insert calls for adding some lanterns.
+
+```` C
+ // Creation of the example
+ helloVk.loadModel(nvh::findFile("media/scenes/Medieval_building.obj", defaultSearchPaths, true));
+ helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true));
+ helloVk.addLantern({ 8.000f, 1.100f, 3.600f}, {1.0f, 0.0f, 0.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 8.000f, 0.600f, 3.900f}, {0.0f, 1.0f, 0.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 8.000f, 1.100f, 4.400f}, {0.0f, 0.0f, 1.0f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 1.730f, 1.812f, -1.604f}, {0.0f, 0.4f, 0.4f}, 0.4f, 4.0f);
+ helloVk.addLantern({ 1.730f, 1.862f, 1.916f}, {0.0f, 0.2f, 0.4f}, 0.3f, 3.0f);
+ helloVk.addLantern({-2.000f, 1.900f, -0.700f}, {0.8f, 0.8f, 0.6f}, 0.4f, 3.9f);
+ helloVk.addLantern({ 0.100f, 0.080f, -2.392f}, {1.0f, 0.0f, 1.0f}, 0.5f, 5.0f);
+ helloVk.addLantern({ 1.948f, 0.080f, 0.598f}, {1.0f, 1.0f, 1.0f}, 0.6f, 6.0f);
+ helloVk.addLantern({-2.300f, 0.080f, 2.100f}, {0.0f, 0.7f, 0.0f}, 0.6f, 6.0f);
+ helloVk.addLantern({-1.400f, 4.300f, 0.150f}, {1.0f, 1.0f, 0.0f}, 0.7f, 7.0f);
+````
+
+## Lantern Device Storage
+
+In `hello_vulkan.h`, declare a struct for storing lanterns on the device.
+This includes the host information, plus a scissor rectangle.
+```` C
+ // Information on each colored lantern, plus the info needed for dispatching the
+ // indirect ray trace command used to add its brightness effect.
+ // The dispatched ray trace covers pixels (offsetX, offsetY) to
+ // (offsetX + indirectCommand.width - 1, offsetY + indirectCommand.height - 1).
+ struct LanternIndirectEntry
+ {
+ // Filled in by the device using a compute shader.
+ // NOTE: I rely on indirectCommand being the first member.
+ VkTraceRaysIndirectCommandKHR indirectCommand;
+ int32_t offsetX;
+ int32_t offsetY;
+
+ // Filled in by the host.
+ Lantern lantern;
+ };
+````
+
+!!! NOTE
+ `VkTraceRaysIndirectCommandKHR` is just a struct of 3 `int32_t` defining
+ the `width`, `height`, `depth` of a trace ray command.
+
+
+We also declare an equivalent structure for shaders in the file
+`LanternIndirectEntry.glsl`. We avoid using `vec3` due to differences
+in alignment in C++ and GLSL.
+```` C
+struct LanternIndirectEntry
+{
+ // VkTraceRaysIndirectCommandKHR
+ int indirectWidth;
+ int indirectHeight;
+ int indirectDepth;
+
+ // Pixel coordinate of scissor rect upper-left.
+ int offsetX;
+ int offsetY;
+
+ // Lantern starts here:
+ // Can't use vec3 due to alignment.
+ float x, y, z;
+ float red, green, blue;
+ float brightness;
+ float radius;
+};
+````
+
+To store the lanterns on the device, declare the Vulkan buffer of `LanternIndirectEntry`
+in `hello_vulkan.h`
+
+```` C
+ // Buffer to source vkCmdTraceRaysIndirectKHR indirect parameters and lantern color,
+ // position, etc. from when doing lantern lighting passes.
+ nvvk::Buffer m_lanternIndirectBuffer;
+ VkDeviceSize m_lanternCount = 0; // Set to actual lantern count after TLAS build, as
+ // that is the point no more lanterns may be added.
+````
+
+and fill it with a `createLanternIndirectBuffer` function. For performance,
+we allocate a device-local buffer. We need usage flags for
+
+* Storage buffer use, so the compute shader can write to it.
+* Indirect buffer use, so we can source indirect parameters from it when dispatching a ray trace.
+* Device address use, as `vkCmdTraceRaysIndirectKHR` expects a device address.
+* Transfer dst use, so the buffer can be initialized with the lantern colors and positions from `m_lanterns`.
+
+```` C
+// Allocate the buffer used to pass lantern info + ray trace indirect parameters to ray tracer.
+// Fill in the lantern info from m_lanterns (indirect info is filled per-frame on device
+// using a compute shader). Must be called only after TLAS build.
+//
+// The buffer is an array of LanternIndirectEntry, entry i is for m_lanterns[i].
+void HelloVulkan::createLanternIndirectBuffer()
+{
+ assert(m_lanternCount > 0);
+ assert(m_lanternCount == m_lanterns.size());
+
+ // m_alloc behind the scenes uses cmdBuf to transfer data to the buffer.
+ nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex);
+ vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();
+
+ using Usage = vk::BufferUsageFlagBits;
+ m_lanternIndirectBuffer =
+ m_alloc.createBuffer(sizeof(LanternIndirectEntry) * m_lanternCount,
+ Usage::eIndirectBuffer | Usage::eTransferDst
+ | Usage::eShaderDeviceAddress | Usage::eStorageBuffer,
+ vk::MemoryPropertyFlagBits::eDeviceLocal);
+
+ std::vector entries(m_lanternCount);
+ for (size_t i = 0; i < m_lanternCount; ++i) entries[i].lantern = m_lanterns[i];
+ cmdBuf.updateBuffer(m_lanternIndirectBuffer.buffer, 0, entries.size() * sizeof entries[0], entries.data());
+
+ cmdBufGet.submitAndWait(cmdBuf);
+}
+````
+
+Call this function in `main.cpp`, after the AS build (the AS build will be modified later).
+
+```` C
+ helloVk.initRayTracing();
+ helloVk.createBottomLevelAS();
+ helloVk.createTopLevelAS();
+ helloVk.createLanternIndirectBuffer();
+````
+
+## Set up Compute Shader
+
+The compute shader will need the view and projection matrices, plus the Z near plane,
+screen dimensions, and length of the `LanternIndirectBuffer` array, in order to
+compute the scissor rectangles. Feed all of this in with a push constant, declared
+in `hello_vulkan.h`
+
+```` C
+ // Push constant for compute shader filling lantern indirect buffer.
+ // Barely fits in 128-byte push constant limit guaranteed by spec.
+ struct LanternIndirectPushConstants
+ {
+ nvmath::vec4 viewRowX; // First 3 rows of view matrix.
+ nvmath::vec4 viewRowY; // Set w=1 implicitly in shader.
+ nvmath::vec4 viewRowZ;
+
+ nvmath::mat4 proj; // Perspective matrix
+ float nearZ; // Near plane used to create projection matrix.
+
+ // Pixel dimensions of output image (needed to scale NDC to screen coordinates).
+ int32_t screenX;
+ int32_t screenY;
+
+ // Length of the LanternIndirectEntry array.
+ int32_t lanternCount;
+ } m_lanternIndirectPushConstants;
+````
+
+!!! NOTE Push Constant Limit
+ The Vulkan spec only guarantees 128 bytes of push constant, so to make everything
+ fit, we have to chop off the implicit bottom row of the view matrix.
+
+This push constant is consumed in the compute shader `shaders/lanternIndirect.comp`.
+We go through `lanternCount` iterations of a loop that fill in the scissor
+rectangle for each `LanternIndirectEntry` (splitting the work among 128
+invocations of the work group).
+
+```` C
+#version 460
+#extension GL_GOOGLE_include_directive : enable
+
+// Compute shader for filling in raytrace indirect parameters for each lantern
+// based on the current camera position (passed as view and proj matrix in
+// push constant).
+//
+// Designed to be dispatched with only one work group; it alone fills in
+// the entire lantern array (of length lanternCount, in also push constant).
+
+#define LOCAL_SIZE 128
+layout(local_size_x = LOCAL_SIZE, local_size_y = 1, local_size_z = 1) in;
+
+#include "LanternIndirectEntry.glsl"
+
+layout(binding = 0, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+layout(push_constant) uniform Constants
+{
+ vec4 viewRowX;
+ vec4 viewRowY;
+ vec4 viewRowZ;
+ mat4 proj;
+ float nearZ;
+ int screenX;
+ int screenY;
+ int lanternCount;
+}
+pushC;
+
+// Copy the technique of "2D Polyhedral Bounds of a Clipped,
+// Perspective-Projected 3D Sphere" M. Mara M. McGuire
+// http://jcgt.org/published/0002/02/05/paper.pdf
+// to compute a screen-space rectangle covering the given Lantern's
+// light radius-of-effect. Result is in screen (pixel) coordinates.
+void getScreenCoordBox(in LanternIndirectEntry lantern, out ivec2 lower, out ivec2 upper);
+
+// Use the xyz and radius of lanterns[i] plus the transformation matrices
+// in pushC to fill in the offset and indirect parameters of lanterns[i]
+// (defines the screen rectangle that this lantern's light is bounded in).
+void fillIndirectEntry(int i)
+{
+ LanternIndirectEntry lantern = lanterns.lanterns[i];
+ ivec2 lower, upper;
+ getScreenCoordBox(lantern, lower, upper);
+
+ lanterns.lanterns[i].indirectWidth = max(0, upper.x - lower.x);
+ lanterns.lanterns[i].indirectHeight = max(0, upper.y - lower.y);
+ lanterns.lanterns[i].indirectDepth = 1;
+ lanterns.lanterns[i].offsetX = lower.x;
+ lanterns.lanterns[i].offsetY = lower.y;
+}
+
+void main()
+{
+ for (int i = int(gl_LocalInvocationID.x); i < pushC.lanternCount; i += LOCAL_SIZE)
+ {
+ fillIndirectEntry(i);
+ }
+}
+
+/** Center is in camera space */
+void getBoundingBox(
+ in vec3 center,
+ in float radius,
+ in float nearZ,
+ in mat4 projMatrix,
+ out vec2 ndc_low,
+ out vec2 ndc_high) {
+````
+!!! TIP
+ Omitted code for computing scissor rectangles, taken from "2D Polyhedral Bounds of a Clipped,
+ Perspective-Projected 3D Sphere" by Michael Mara and Morgan McGuire.
+ http://jcgt.org/published/0002/02/05/paper.pdf
+```` C
+}
+
+void getScreenCoordBox(in LanternIndirectEntry lantern, out ivec2 lower, out ivec2 upper)
+{
+ vec4 lanternWorldCenter = vec4(lantern.x, lantern.y, lantern.z, 1);
+ vec3 center = vec3(
+ dot(pushC.viewRowX, lanternWorldCenter),
+ dot(pushC.viewRowY, lanternWorldCenter),
+ dot(pushC.viewRowZ, lanternWorldCenter));
+ vec2 ndc_low, ndc_high;
+ float paperNearZ = -abs(pushC.nearZ); // Paper expected negative nearZ, took 2 days to figure out!
+ getBoundingBox(center, lantern.radius, paperNearZ, pushC.proj, ndc_low, ndc_high);
+
+ // Convert NDC [-1,+1]^2 coordinates to screen coordinates, and clamp to stay in bounds.
+
+ lower.x = clamp(int((ndc_low.x * 0.5 + 0.5) * pushC.screenX), 0, pushC.screenX);
+ lower.y = clamp(int((ndc_low.y * 0.5 + 0.5) * pushC.screenY), 0, pushC.screenY);
+ upper.x = clamp(int((ndc_high.x * 0.5 + 0.5) * pushC.screenX), 0, pushC.screenX);
+ upper.y = clamp(int((ndc_high.y * 0.5 + 0.5) * pushC.screenY), 0, pushC.screenY);
+}
+````
+
+Now we just have to fill out the usual boilerplate for setting up the descriptor
+set (passes the `LanternIndirectEntry` array) and compute pipeline. We only have
+to allocate one descriptor as the `LanternIndirectEntry` array never changes.
+
+`hello_vulkan.h`:
+
+```` C
+ nvvk::DescriptorSetBindings m_lanternIndirectDescSetLayoutBind;
+ vk::DescriptorPool m_lanternIndirectDescPool;
+ vk::DescriptorSetLayout m_lanternIndirectDescSetLayout;
+ vk::DescriptorSet m_lanternIndirectDescSet;
+ vk::PipelineLayout m_lanternIndirectCompPipelineLayout;
+ vk::Pipeline m_lanternIndirectCompPipeline;
+````
+
+`hello_vulkan.cpp`:
+
+```` C
+//--------------------------------------------------------------------------------------------------
+// The compute shader just needs read/write access to the buffer of LanternIndirectEntry.
+void HelloVulkan::createLanternIndirectDescriptorSet()
+{
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+ using vkDSLB = vk::DescriptorSetLayoutBinding;
+
+ // Lantern buffer (binding = 0)
+ m_lanternIndirectDescSetLayoutBind.addBinding( //
+ vkDSLB(0, vkDT::eStorageBuffer, 1, vkSS::eCompute));
+
+ m_lanternIndirectDescPool = m_lanternIndirectDescSetLayoutBind.createPool(m_device);
+ m_lanternIndirectDescSetLayout = m_lanternIndirectDescSetLayoutBind.createLayout(m_device);
+ m_lanternIndirectDescSet =
+ m_device.allocateDescriptorSets({m_lanternIndirectDescPool, 1, &m_lanternIndirectDescSetLayout})[0];
+
+ assert(m_lanternIndirectBuffer.buffer);
+ vk::DescriptorBufferInfo lanternBufferInfo{
+ m_lanternIndirectBuffer.buffer, 0, m_lanternCount * sizeof(LanternIndirectEntry)};
+
+ std::vector writes;
+ writes.emplace_back(m_lanternIndirectDescSetLayoutBind.makeWrite(m_lanternIndirectDescSet, 0, &lanternBufferInfo));
+ m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr);
+}
+
+// Create compute pipeline used to fill m_lanternIndirectBuffer with parameters
+// for dispatching the correct number of ray traces.
+void HelloVulkan::createLanternIndirectCompPipeline()
+{
+ // Compile compute shader and package as stage.
+ vk::ShaderModule computeShader =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternIndirect.comp.spv", true, defaultSearchPaths, true));
+ vk::PipelineShaderStageCreateInfo stageInfo;
+ stageInfo.setStage(vk::ShaderStageFlagBits::eCompute);
+ stageInfo.setModule(computeShader);
+ stageInfo.setPName("main");
+
+ // Set up push constant and pipeline layout.
+ constexpr auto pushSize = static_cast(sizeof(m_lanternIndirectPushConstants));
+ vk::PushConstantRange pushCRange = {vk::ShaderStageFlagBits::eCompute, 0, pushSize};
+ static_assert(pushSize <= 128, "Spec guarantees only 128 byte push constant");
+ vk::PipelineLayoutCreateInfo layoutInfo;
+ layoutInfo.setSetLayoutCount(1);
+ layoutInfo.setPSetLayouts(&m_lanternIndirectDescSetLayout);
+ layoutInfo.setPushConstantRangeCount(1);
+ layoutInfo.setPPushConstantRanges(&pushCRange);
+ m_lanternIndirectCompPipelineLayout = m_device.createPipelineLayout(layoutInfo);
+
+ // Create compute pipeline.
+ vk::ComputePipelineCreateInfo pipelineInfo;
+ pipelineInfo.setStage(stageInfo);
+ pipelineInfo.setLayout(m_lanternIndirectCompPipelineLayout);
+ m_lanternIndirectCompPipeline = static_cast(m_device.createComputePipeline({}, pipelineInfo));
+
+ m_device.destroy(computeShader);
+}
+````
+
+`main.cpp` (add after indirect buffer initialization).
+
+```` C
+ // #VKRay
+ helloVk.initRayTracing();
+ helloVk.createBottomLevelAS();
+ helloVk.createTopLevelAS();
+ helloVk.createLanternIndirectBuffer();
+ helloVk.createRtDescriptorSet();
+ helloVk.createRtPipeline();
+ helloVk.createLanternIndirectDescriptorSet();
+ helloVk.createLanternIndirectCompPipeline();
+````
+
+## Call Compute Shader
+
+In `HelloVulkan::raytrace`, we have to fill in the earlier push constant and
+dispatch the compute shader before moving
+on to the actual ray tracing. This is rather verbose due to the need for a
+pipeline barrier synchronizing access to the `LanternIndirectEntry` array
+between the compute shader and indirect draw stages.
+
+```` C
+void HelloVulkan::raytrace(const vk::CommandBuffer& cmdBuf, const nvmath::vec4f& clearColor)
+{
+ // Before tracing rays, we need to dispatch the compute shaders that
+ // fill in the ray trace indirect parameters for each lantern pass.
+
+ // First, barrier before, ensure writes aren't visible to previous frame.
+ vk::BufferMemoryBarrier bufferBarrier;
+ bufferBarrier.setSrcAccessMask(vk::AccessFlagBits::eIndirectCommandRead);
+ bufferBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderWrite);
+ bufferBarrier.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ bufferBarrier.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ bufferBarrier.setBuffer(m_lanternIndirectBuffer.buffer);
+ bufferBarrier.offset = 0;
+ bufferBarrier.size = m_lanternCount * sizeof m_lanterns[0];
+ cmdBuf.pipelineBarrier( //
+ vk::PipelineStageFlagBits::eDrawIndirect, //
+ vk::PipelineStageFlagBits::eComputeShader,//
+ vk::DependencyFlags(0), //
+ {}, {bufferBarrier}, {});
+
+ // Bind compute shader, update push constant and descriptors, dispatch compute.
+ cmdBuf.bindPipeline(vk::PipelineBindPoint::eCompute, m_lanternIndirectCompPipeline);
+ nvmath::mat4 view = getViewMatrix();
+ m_lanternIndirectPushConstants.viewRowX = view.row(0);
+ m_lanternIndirectPushConstants.viewRowY = view.row(1);
+ m_lanternIndirectPushConstants.viewRowZ = view.row(2);
+ m_lanternIndirectPushConstants.proj = getProjMatrix();
+ m_lanternIndirectPushConstants.nearZ = nearZ;
+ m_lanternIndirectPushConstants.screenX = m_size.width;
+ m_lanternIndirectPushConstants.screenY = m_size.height;
+ m_lanternIndirectPushConstants.lanternCount = m_lanternCount;
+ cmdBuf.pushConstants(
+ m_lanternIndirectCompPipelineLayout,
+ vk::ShaderStageFlagBits::eCompute,
+ 0, m_lanternIndirectPushConstants);
+ cmdBuf.bindDescriptorSets(
+ vk::PipelineBindPoint::eCompute, m_lanternIndirectCompPipelineLayout, 0, {m_lanternIndirectDescSet}, {});
+ cmdBuf.dispatch(1, 1, 1);
+
+ // Ensure compute results are visible when doing indirect ray trace.
+ bufferBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
+ bufferBarrier.setDstAccessMask(vk::AccessFlagBits::eIndirectCommandRead);
+ cmdBuf.pipelineBarrier( //
+ vk::PipelineStageFlagBits::eComputeShader, //
+ vk::PipelineStageFlagBits::eDrawIndirect, //
+ vk::DependencyFlags(0), //
+ {}, {bufferBarrier}, {});
+
+
+ // Now move on to the actual ray tracing.
+ m_debug.beginLabel(cmdBuf, "Ray trace");
+````
+
+!!! TIP `eDrawIndirect`
+ `vk::PipelineStageFlagBits::eDrawIndirect` (`VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT`)
+ covers the stage that sources indirect paramaters for compute and ray trace
+ indirect commands, not just graphics draw indirect commands.
+
+Since the near plane and view/projection matrices are used in multiple places now,
+they were factored out to common code in `hello_vulkan.h`.
+
+```` C
+ nvmath::mat4 getViewMatrix()
+ {
+ return CameraManip.getMatrix();
+ }
+
+ static constexpr float nearZ = 0.1f;
+ nvmath::mat4 getProjMatrix()
+ {
+ const float aspectRatio = m_size.width / static_cast(m_size.height);
+ return nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, nearZ, 1000.0f);
+ }
+````
+
+The function for updating the uniform buffer is tweaked to match.
+
+```` C
+void HelloVulkan::updateUniformBuffer(const vk::CommandBuffer& cmdBuf)
+{
+ const float aspectRatio = m_size.width / static_cast(m_size.height);
+
+ CameraMatrices ubo = {};
+ ubo.view = getViewMatrix();
+ ubo.proj = getProjMatrix();
+````
+
+# Lantern Acceleration Structures and Closest Hit Shader
+
+## Bottom-level Acceleration Structure
+
+Lanterns will be drawn as spheres approximated by a triangular mesh. Declare
+in `hello_vulkan.h` functions for generating this mesh, and declare Vulkan
+buffers for storing the mesh's positions and indices, and a `BlasInput`
+for delivering this sphere mesh to the BLAS builder.
+
+```` C
+private:
+ void fillLanternVerts(std::vector& vertices, std::vector& indices);
+ void createLanternModel();
+
+ // Used to store lantern model, generated at runtime.
+ const float m_lanternModelRadius = 0.125;
+ nvvk::Buffer m_lanternVertexBuffer;
+ nvvk::Buffer m_lanternIndexBuffer;
+ nvvk::RaytracingBuilderKHR::BlasInput m_lanternBlasInput{};
+
+ // Index of lantern's BLAS in the BLAS array stored in m_rtBuilder.
+ size_t m_lanternBlasId;
+````
+
+In order to focus on the ray tracing, I omit the code for generating those vertex and index
+buffers. The relevent code in `HelloVulkan::createLanternModel` for creating the `BlasInput` is
+
+```` C
+ // Package vertex and index buffers as BlasInput.
+ vk::DeviceAddress vertexAddress = m_device.getBufferAddress({m_lanternVertexBuffer.buffer});
+ vk::DeviceAddress indexAddress = m_device.getBufferAddress({m_lanternIndexBuffer.buffer});
+
+ uint32_t maxPrimitiveCount = uint32_t(indices.size() / 3);
+
+ // Describe buffer as packed array of float vec3.
+ vk::AccelerationStructureGeometryTrianglesDataKHR triangles;
+ triangles.setVertexFormat(vk::Format::eR32G32B32Sfloat); // vec3 vertex position data.
+ triangles.setVertexData(vertexAddress);
+ triangles.setVertexStride(sizeof(nvmath::vec3f));
+ // Describe index data (32-bit unsigned int)
+ triangles.setIndexType(vk::IndexType::eUint32);
+ triangles.setIndexData(indexAddress);
+ // Indicate identity transform by setting transformData to null device pointer.
+ triangles.setTransformData({});
+ triangles.setMaxVertex(vertices.size());
+
+ // Identify the above data as containing opaque triangles.
+ vk::AccelerationStructureGeometryKHR asGeom;
+ asGeom.setGeometryType(vk::GeometryTypeKHR::eTriangles);
+ asGeom.setFlags(vk::GeometryFlagBitsKHR::eOpaque);
+ asGeom.geometry.setTriangles(triangles);
+
+ // The entire array will be used to build the BLAS.
+ vk::AccelerationStructureBuildRangeInfoKHR offset;
+ offset.setFirstVertex(0);
+ offset.setPrimitiveCount(maxPrimitiveCount);
+ offset.setPrimitiveOffset(0);
+ offset.setTransformOffset(0);
+
+ // Our blas is made from only one geometry, but could be made of many geometries
+ m_lanternBlasInput.asGeometry.emplace_back(asGeom);
+ m_lanternBlasInput.asBuildOffsetInfo.emplace_back(offset);
+````
+
+The principle difference from before is that the vertex array is now a packed array of
+float 3-vectors, hence, we call `triangles.setVertexStride(sizeof(nvmath::vec3f));`.
+
+Then, we add a call to create a lantern model and add the lantern model to the list of
+BLAS to build in `HelloVulkan::createBottomLevelAS`. Since we'll need the index of
+the lantern BLAS later to add lantern instances in the TLAS build, store the
+BLAS index for the lantern in `m_lanternBlasId`.
+
+```` C
+// Build the array of BLAS in m_rtBuilder. There are `m_objModel.size() + 1`-many BLASes.
+// The first `m_objModel.size()` are used for OBJ model BLASes, and the last one
+// is used for the lanterns (model generated at runtime).
+void HelloVulkan::createBottomLevelAS()
+{
+ // BLAS - Storing each primitive in a geometry
+ std::vector allBlas;
+ allBlas.reserve(m_objModel.size() + 1);
+
+ // Add OBJ models.
+ for(const auto& obj : m_objModel)
+ {
+ auto blas = objectToVkGeometryKHR(obj);
+
+ // We could add more geometry in each BLAS, but we add only one for now
+ allBlas.emplace_back(blas);
+ }
+
+ // Add lantern model.
+ createLanternModel();
+ m_lanternBlasId = allBlas.size();
+ allBlas.emplace_back(m_lanternBlasInput);
+
+ m_rtBuilder.buildBlas(allBlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
+}
+````
+
+## Top-level acceleration structure
+
+In the TLAS build function, we add a loop for adding each lantern instance. This is also
+the point that the lanterns are set-in-stone (no more modifications to `m_lanterns`), so
+write `m_lanternCount`.
+
+```` C
+// Build the TLAS in m_rtBuilder. Requires that the BLASes were already built and
+// that all ObjInstance and lanterns have been added. One instance with hitGroupId=0
+// is created for every OBJ instance, and one instance with hitGroupId=1 for each lantern.
+//
+// gl_InstanceCustomIndexEXT will be the index of the instance or lantern in m_objInstance or
+// m_lanterns respectively.
+void HelloVulkan::createTopLevelAS()
+{
+ assert(m_lanternCount == 0);
+ m_lanternCount = m_lanterns.size();
+
+ std::vector tlas;
+ tlas.reserve(m_objInstance.size() + m_lanternCount);
+
+ // Add the OBJ instances.
+ for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++)
+ {
+ nvvk::RaytracingBuilderKHR::Instance rayInst;
+ rayInst.transform = m_objInstance[i].transform; // Position of the instance
+ rayInst.instanceCustomId = i; // gl_InstanceCustomIndexEXT
+ rayInst.blasId = m_objInstance[i].objIndex;
+ rayInst.hitGroupId = 0; // We will use the same hit group for all OBJ
+ rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+ tlas.emplace_back(rayInst);
+ }
+
+ // Add lantern instances.
+ for(int i = 0; i < static_cast(m_lanterns.size()); ++i)
+ {
+ nvvk::RaytracingBuilderKHR::Instance lanternInstance;
+ lanternInstance.transform = nvmath::translation_mat4(m_lanterns[i].position);
+ lanternInstance.instanceCustomId = i;
+ lanternInstance.blasId = m_lanternBlasId;
+ lanternInstance.hitGroupId = 1; // Next hit group is for lanterns.
+ lanternInstance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
+ tlas.emplace_back(lanternInstance);
+ }
+
+ m_rtBuilder.buildTlas(tlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
+}
+````
+
+The principle differences are:
+
+* `instanceCustomId` is set to the index of the lantern in `m_lanternIndirectBuffer`, so we
+ can look up the lantern color in the forthcoming closest hit shader.
+
+* `hitGroupId` is set to `1`, so that lanterns will use a new closest hit shader instead
+ of the old one for OBJs.
+
+!!! TIP Helper Reminders
+ `instanceCustomId` corresponds to `VkAccelerationStructureInstanceKHR::instanceCustomIndex` in host
+ code and `gl_InstanceCustomIndexEXT` in shader code.
+
+ `hitGroupId` corresponds to `VkAccelerationStructureInstanceKHR::instanceShaderBindingTableRecordOffset`.
+
+ `blasId` has no Vulkan equivalent; it is translated to a BLAS device address in the `m_rtBuilder` helper.
+
+## Lantern Primary Ray Closest Hit Shader
+
+We now implement the closest hit shader for lanterns hit by primary rays (rays
+cast starting from the eye). First, we need to do a bit of preparation:
+
+* Add a bool to `hitPayload` to control whether additive blending is enabled or
+ not. The lanterns will be drawn at a constant brightness, so additive blending
+ is enabled for rays hitting OBJ instances and disabled for rays hitting lanterns.
+ The raygen shader will be updated later to take this bool into account.
+
+* Access the GLSL definition of `LanternIndirectEntry` so we can look up the lantern color.
+
+* Add a descriptor set to the raytrace pipeline to deliver the
+
+We do the first two tasks in `raycommon.glsl`.
+
+```` C
+#include "LanternIndirectEntry.glsl"
+
+struct hitPayload
+{
+ vec3 hitValue;
+ bool additiveBlending;
+};
+````
+
+The last task is done in `HelloVulkan::createRtDescriptorSet`
+
+```` C
+// This descriptor set holds the Acceleration structure, output image, and lanterns array buffer.
+//
+void HelloVulkan::createRtDescriptorSet()
+{
+ using vkDT = vk::DescriptorType;
+ using vkSS = vk::ShaderStageFlagBits;
+ using vkDSLB = vk::DescriptorSetLayoutBinding;
+
+ // ...
+
+ // Lantern buffer (binding = 2)
+ m_rtDescSetLayoutBind.addBinding( //
+ vkDSLB(2, vkDT::eStorageBuffer, 1, vkSS::eRaygenKHR | vkSS::eClosestHitKHR));
+ assert(m_lanternCount > 0);
+
+ // ...
+
+ std::vector writes;
+
+ // ...
+
+ writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &lanternBufferInfo));
+ m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr);
+}
+````
+
+Now we can implement the new closest hit shader. Name this shader `lantern.rchit`.
+
+```` 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 "raycommon.glsl"
+
+// Closest hit shader invoked when a primary ray hits a lantern.
+
+// clang-format off
+layout(location = 0) rayPayloadInEXT hitPayload prd;
+
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+// clang-format on
+
+void main()
+{
+ // Just look up this lantern's color. Self-illuminating, so no lighting calculations.
+ LanternIndirectEntry lantern = lanterns.lanterns[nonuniformEXT(gl_InstanceCustomIndexEXT)];
+ prd.hitValue = vec3(lantern.red, lantern.green, lantern.blue);
+ prd.additiveBlending = false;
+}
+````
+
+This shader is fairly simple, we just had to look up the lantern color and return it in the
+payload. Here, we used the fact that in the TLAS instances setup, we set a lantern instance's
+`gl_InstanceCustomIndexEXT` to its position in the lanterns array.
+
+Now we just have to add the new hit group to the pipeline. This is more of the same,
+in the `HelloVulkan::createRtPipeline` function, we add the lantern closest hit
+group after the OBJ hit group, to match the `hitGroupId`s assigned earlier in the
+TLAS build.
+
+```` C
+ // OBJ Primary Ray Hit Group - Closest Hit + AnyHit (not used)
+ vk::ShaderModule chitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/raytrace.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR hg{vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ hg.setClosestHitShader(static_cast(stages.size()));
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, chitSM, "main"});
+ m_rtShaderGroups.push_back(hg);
+
+ // Lantern Primary Ray Hit Group
+ vk::ShaderModule lanternChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lantern.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ lanternHg.setClosestHitShader(static_cast(stages.size()));
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternChitSM, "main"});
+ m_rtShaderGroups.push_back(lanternHg);
+
+ // ...
+
+ m_device.destroy(lanternChitSM);
+````
+
+We don't have to modify `HelloVulkan::createRtShaderBindingTable`. Changes to the number of
+group handles to copy into the SBT are picked up automatically from `m_rtShaderGroups.size()`.
+
+# Ray Generation Shader
+
+## Draw Within Scissor Rectangle
+
+The original ray generation shader assumed that `gl_LaunchSizeEXT` is the size of the entire
+screen. As this is no longer the case for scissor rectangles, we communicate the screen
+size through push constant instead. In addition, we also add to the push constants a number
+indicating which lantern pass is currently being drawn (-1 for the original full screen pass).
+
+Modify `m_rtPushConstants` in `hello_vulkan.h`.
+
+```` C
+ // Push constant for ray trace pipeline.
+ struct RtPushConstant
+ {
+ // Background color
+ nvmath::vec4f clearColor;
+
+ // Information on the light in the sky used when lanternPassNumber = -1.
+ nvmath::vec3f lightPosition;
+ float lightIntensity;
+ int32_t lightType;
+
+ // -1 if this is the full-screen pass. Otherwise, this pass is to add light
+ // from lantern number lanternPassNumber. We use this to lookup trace indirect
+ // parameters in m_lanternIndirectBuffer.
+ int32_t lanternPassNumber;
+
+ // Pixel dimensions of the output image.
+ int32_t screenX;
+ int32_t screenY;
+
+ // See m_lanternDebug.
+ int32_t lanternDebug;
+ } m_rtPushConstants;
+````
+
+We also update the GLSL push constant to match. Since the raygen shader now needs
+access to the push constant, move the push constant definition from `raytrace.rchit`
+to `raycommon.glsl`.
+
+```` C
+layout(push_constant) uniform Constants
+{
+ vec4 clearColor;
+ vec3 lightPosition;
+ float lightIntensity;
+ int lightType; // 0: point, 1: infinite
+ int lanternPassNumber; // -1 if this is the full-screen pass. Otherwise, used to lookup trace indirect parameters.
+ int screenX;
+ int screenY;
+ int lanternDebug;
+}
+pushC;
+````
+
+(`lanternDebug` will be used later to toggle visualising the scissor rectangles)
+
+
+This move also requires us to tweak `raytrace.rmiss`.
+
+````
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+layout(location = 0) rayPayloadInEXT hitPayload prd;
+
+void main()
+{
+ prd.hitValue = pushC.clearColor.xyz * 0.8;
+ prd.additiveBlending = false;
+}
+````
+
+We will cover initializing the new push constants later, when we look at `vkCmdTraceRaysIndirectKHR`.
+
+In `raytrace.rgen`, we have to replace the old code for calculating the pixel center.
+
+```` C
+void main()
+{
+ const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5);
+ const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy);
+````
+
+with
+
+```` C
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+
+void main()
+{
+ // Global light pass is a full screen rectangle (lower corner 0,0), but
+ // lantern passes are only run within rectangles that may be offset.
+ ivec2 pixelOffset = ivec2(0);
+ if (pushC.lanternPassNumber >= 0)
+ {
+ pixelOffset.x = lanterns.lanterns[pushC.lanternPassNumber].offsetX;
+ pixelOffset.y = lanterns.lanterns[pushC.lanternPassNumber].offsetY;
+ }
+
+ const ivec2 pixelIntCoord = ivec2(gl_LaunchIDEXT.xy) + pixelOffset;
+ const vec2 pixelCenter = vec2(pixelIntCoord) + vec2(0.5);
+ const vec2 inUV = pixelCenter / vec2(pushC.screenX, pushC.screenY);
+ vec2 d = inUV * 2.0 - 1.0;
+````
+
+Let's recap why this works. If `pushC.lanternPassNumber` is negative, we're drawing
+the first, full-screen pass, and this code behaves identically as before, except
+that `inUV` performs division by `(pushC.screenX, pushC.screenY)` instead of
+relying on `gl_LaunchSizeEXT` to be the screen size.
+
+Otherwise (`pushC.lanternPassNumber >= 0`), we're drawing a scissor rectangle for
+the given lantern number. Look up that lantern's `LanternIndirectEntry` in the
+array (notice that the descriptor binding for it is added). Its scissor rectangle
+is defined by:
+
+* `LanternIndirectEntry::offsetX`,`offsetY`: the pixel coordinate of the scissor box's
+ upper-left.
+
+* `LanternIndirectEntry::width`,`height`: the dimensions of the scissor box (not
+ directly used here; consumed by `vkCmdTraceRaysIndirectKHR`).
+
+The `gl_LaunchIDEXT` variable ranges from `(0,0)` to `(width-1, height-1)`, so to
+cover the correct pixels within the scissor, we just have to reposition
+`gl_LaunchIDEXT` by the offset `(offsetX, offsetY)`.
+
+## Additive Blending
+
+We also have to emulate additive blending. Instead of always writing to the output
+image:
+
+```` C
+ imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.0));
+````
+
+we do
+
+```` C
+ // Either add to or replace output image color based on prd.additiveBlending.
+ // Global pass always replaces color as it is the first pass.
+ vec3 oldColor = vec3(0);
+ if (prd.additiveBlending && pushC.lanternPassNumber >= 0) {
+ oldColor = imageLoad(image, pixelIntCoord).rgb;
+ }
+ imageStore(image, pixelIntCoord, vec4(prd.hitValue + oldColor, 1.0));
+````
+
+thus adding the ray payload's color to the old image color if `prd.additiveBlending`
+is true and this is not the first, full-screen pass (the first pass must replace the
+output image color as its existing contents are garbage).
+
+# Lantern Shadow Rays
+
+We now have to set up a system for casting shadow rays from the OBJ closest hit
+shader to the lanterns. This requires us to
+
+* Detect in `raycast.rchit` whether we are in a lantern pass, and use this
+ to decide between casting shadow rays to the main light (as in the base
+ tutorial) or casting shadow rays to a lantern.
+
+* Declare a payload for which lantern (if any) was hit, and add a new miss shader
+ and two new closest hit shaders for filling that payload.
+
+* Use the `sbtRecordOffset` parameter of `traceRayEXT` to skip over the earlier
+ hit groups.
+
+## New payload
+
+In `raytrace.rchit` (called when an OBJ instance is hit by a primary ray), declare
+the new payload and the array of lanterns.
+
+```` C
+layout(location = 2) rayPayloadEXT int hitLanternInstance;
+
+layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS;
+layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns;
+````
+
+## New shaders
+
+We need a few simple shaders to report the number of the lantern hit (if any) by the shadow ray.
+First is the miss shader, `lanternShadow.rmiss`.
+
+```` C
+// Miss shader invoked when tracing shadow rays (rays towards lantern)
+// in lantern passes. Misses shouldn't really happen, but if they do,
+// report we did not hit any lantern by setting hitLanternInstance = -1.
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+void main()
+{
+ hitLanternInstance = -1;
+}
+````
+
+Then a closest hit shader for OBJ instances hit by a lantern shadow ray.
+This also returns `-1` for "no lantern". Call this `lanternShadowObj.rchit`.
+
+```` C
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// During a lantern pass, this closest hit shader is invoked when
+// shadow rays (rays towards lantern) hit a regular OBJ. Report back
+// that no lantern was hit (-1).
+
+// clang-format off
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+// clang-format on
+
+void main()
+{
+ hitLanternInstance = -1;
+}
+````
+
+Finally, a closest hit shader for lantern instances, named `lanternShadowLantern.rchit`.
+
+```` C
+#version 460
+#extension GL_EXT_ray_tracing : require
+#extension GL_GOOGLE_include_directive : enable
+#include "raycommon.glsl"
+
+// During a lantern pass, this closest hit shader is invoked when
+// shadow rays (rays towards lantern) hit a lantern. Report back
+// which lantern was hit.
+
+// clang-format off
+layout(location = 2) rayPayloadInEXT int hitLanternInstance;
+
+// clang-format on
+
+void main()
+{
+ hitLanternInstance = gl_InstanceCustomIndexEXT;
+}
+````
+
+Note that we really need to report back the lantern number, and
+not just a boolean "lantern hit" flag. In order to have lanterns cast
+shadows on each other, we must be able to detect that the shadow ray
+hit the "wrong" lantern.
+
+
+
+## Add Shaders to Pipeline
+
+We add the new miss shader as miss shader 2 in the SBT, and the closest hit
+shaders as hit groups 2 and 3 in the SBT, following the earlier 2 hit
+groups for primary rays. Add the following code to `HelloVulkan::createRtPipeline`
+after loading `raytraceShadow.rmiss.spv`.
+
+```` C
+ // Miss shader 2 is invoked when a shadow ray for lantern lighting misses the
+ // lantern. It shouldn't be invoked, but I include it just in case.
+ vk::ShaderModule lanternmissSM = nvvk::createShaderModule(
+ m_device, nvh::loadFile("shaders/lanternShadow.rmiss.spv", true, paths, true));
+````
+
+and add this code for loading the last 2 closest hit shaders after loading
+`lantern.rchit.spv`:
+
+```` C
+ // OBJ Lantern Shadow Ray Hit Group
+ vk::ShaderModule lanternShadowObjChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternShadowObj.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternShadowObjHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ lanternShadowObjHg.setClosestHitShader(static_cast(stages.size()));
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowObjChitSM, "main"});
+ m_rtShaderGroups.push_back(lanternShadowObjHg);
+
+ // Lantern Lantern Shadow Ray Hit Group
+ vk::ShaderModule lanternShadowLanternChitSM =
+ nvvk::createShaderModule(m_device, //
+ nvh::loadFile("shaders/lanternShadowLantern.rchit.spv", true, paths, true));
+
+ vk::RayTracingShaderGroupCreateInfoKHR lanternShadowLanternHg{
+ vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR,
+ VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR};
+ lanternShadowLanternHg.setClosestHitShader(static_cast(stages.size()));
+ stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowLanternChitSM, "main"});
+ m_rtShaderGroups.push_back(lanternShadowLanternHg);
+````
+
+We need to destroy the added shader modules at the end of the function.
+
+```` C
+ m_device.destroy(shadowmissSM);
+ // ...
+ m_device.destroy(lanternShadowObjChitSM);
+ m_device.destroy(lanternShadowLanternChitSM);
+````
+
+Through all this, we still load shader stages in the same order as they will appear
+in the SBT in order to keep things simple (note `stages.size()`). Add a comment
+at the top of this function to help us keep track of all the new shaders.
+
+```` C
+// Shader list:
+//
+// 0 ====== Ray Generation Shaders =====================================================
+//
+// Raygen shader: Ray generation shader. Casts primary rays from camera to scene.
+//
+// 1 ====== Miss Shaders ===============================================================
+//
+// Miss shader 0: Miss shader when casting primary rays. Fill in clear color.
+//
+// 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Miss shader 1: Miss shader when casting shadow rays towards main light.
+// Reports no shadow.
+//
+// 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// Miss shader 2: Miss shader when casting shadow rays towards a lantern.
+// Reports no lantern hit (-1).
+//
+// 4 ====== Hit Groups for Primary Rays (sbtRecordOffset=0) ============================
+//
+// chit shader 0: Closest hit shader for primary rays hitting OBJ instances
+// (hitGroupId=0). Casts shadow ray (to sky light or to lantern,
+// depending on pass number) and returns specular
+// and diffuse light to add to output image.
+//
+// 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// chit shader 1: Closest hit shader for primary rays hitting lantern instances
+// (hitGroupId=1). Returns color value to replace the current
+// image pixel color with (lanterns are self-illuminating).
+//
+// 6 - - - - Hit Groups for Lantern Shadow Rays (sbtRecordOffset=2) - - - - - - - - - - -
+//
+// chit shader 2: Closest hit shader for OBJ instances hit when casting shadow
+// rays to a lantern. Returns -1 to report that the shadow ray
+// failed to reach the targetted lantern.
+//
+// 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+//
+// chit shader 3: Closest hit shader for lantern instances hit when casting shadow
+// rays to a lantern. Returns the gl_CustomInstanceIndexEXT [lantern
+// number] of the lantern hit.
+//
+// 8 =====================================================================================
+````
+
+## Compute Lighting Intensity
+
+Because the lanterns have color, we have to replace the scalar `lightIntensity`
+in `raytrace.rchit` with an RGB `colorIntensity`.
+
+```` C
+ // Vector toward the light
+ vec3 L;
+ vec3 colorIntensity = vec3(pushC.lightIntensity);
+ float lightDistance = 100000.0;
+````
+
+Then, we have to check if we're in a lantern pass (`lanternPassNumber >= 0`).
+If so, look up the lantern location in the `LanternIndirectEntry` array,
+and compute the light direction and intensity based on that position.
+
+```` C
+ // ray direction is towards lantern, if in lantern pass.
+ if (pushC.lanternPassNumber >= 0)
+ {
+ LanternIndirectEntry lantern = lanterns.lanterns[pushC.lanternPassNumber];
+ vec3 lDir = vec3(lantern.x, lantern.y, lantern.z) - worldPos;
+ lightDistance = length(lDir);
+ vec3 color = vec3(lantern.red, lantern.green, lantern.blue);
+ // Lantern light decreases linearly. Not physically accurate, but looks good
+ // and avoids a hard "edge" at the radius limit. Use a constant value
+ // if lantern debug is enabled to clearly see the covered screen rectangle.
+ float distanceFade =
+ pushC.lanternDebug != 0
+ ? 0.3
+ : max(0, (lantern.radius - lightDistance) / lantern.radius);
+ colorIntensity = color * lantern.brightness * distanceFade;
+ L = normalize(lDir);
+ }
+````
+
+otherwise, do the old lighting calculations, except we again have to
+replace `float lightIntensity` with `vec3 colorIntensity`.
+
+```` C
+ // Non-lantern pass may have point light...
+ else if(pushC.lightType == 0)
+ {
+ vec3 lDir = pushC.lightPosition - worldPos;
+ lightDistance = length(lDir);
+ colorIntensity = vec3(pushC.lightIntensity / (lightDistance * lightDistance));
+ L = normalize(lDir);
+ }
+ else // or directional light.
+ {
+ L = normalize(pushC.lightPosition - vec3(0));
+ }
+````
+
+!!! NOTE `lanternDebug`
+ When `lanternDebug` is on, I disable diminishing lighting with distance, so
+ that the light will reach the edge of the scissor box, making the scissor
+ box easy to see. To toggle this variable, I declare `bool m_lanternDebug`
+ in `hello_vulkan.h`, and allow ImGui to control it:
+
+ ```` C
+ void renderUI(HelloVulkan& helloVk)
+ {
+ ImGuiH::CameraWidget();
+ if(ImGui::CollapsingHeader("Light"))
+ {
+ // ...
+ ImGui::Checkbox("Lantern Debug", &helloVk.m_lanternDebug);
+ }
+ }
+ ````
+
+ Then, every frame I copy `m_lanternDebug` to the push constant. The reason
+ I cannot directly modify the push constant through ImGui is that ImGui expects
+ a `bool` (usually 8-bits) while Vulkan expects a 32-bit boolean.
+
+## Casting Lantern Shadow Rays
+
+Use an `if` to ensure the original shadow rays are cast only in the non-lantern pass.
+
+```` C
+ // Ordinary shadow from the simple tutorial.
+ if (pushC.lanternPassNumber < 0) {
+ isShadowed = true;
+ uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT
+ | gl_RayFlagsSkipClosestHitShaderEXT;
+ traceRayEXT(topLevelAS, // acceleration structure
+ flags, // rayFlags
+ 0xFF, // cullMask
+ 0, // sbtRecordOffset
+ 0, // sbtRecordStride
+ 1, // missIndex
+ origin, // ray origin
+ tMin, // ray min range
+ rayDir, // ray direction
+ tMax, // ray max range
+ 1 // payload (location = 1)
+ );
+ }
+````
+
+Otherwise, we cast a ray towards a lantern. This ray is different in that
+
+* We actually need the closest hit shaders to run to return `hitLanternInstance`,
+ so do not provide the flags
+ ` gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsSkipClosestHitShaderEXT`.
+
+* Use miss shader 2, which we added earlier.
+
+* Pass 2 as `sbtRecordOffset`, so that the closest hit shaders we just added (number 2 and 3)
+ are used when hitting OBJ instances (`hitGroupId=0`) and lantern instances (`hitGroupId=1`)
+ respectively.
+
+The code is
+
+```` C
+ // Lantern shadow ray. Cast a ray towards the lantern whose lighting is being
+ // added this pass. Only the closest hit shader for lanterns will set
+ // hitLanternInstance (payload 2) to non-negative value.
+ else {
+ // Skip ray if no light would be added anyway.
+ if (colorIntensity == vec3(0)) {
+ isShadowed = true;
+ }
+ else {
+ uint flags = gl_RayFlagsOpaqueEXT;
+ hitLanternInstance = -1;
+ traceRayEXT(topLevelAS, // acceleration structure
+ flags, // rayFlags
+ 0xFF, // cullMask
+ 2, // sbtRecordOffset : lantern shadow hit groups start at index 2.
+ 0, // sbtRecordStride
+ 2, // missIndex : lantern shadow miss shader is number 2.
+ origin, // ray origin
+ tMin, // ray min range
+ rayDir, // ray direction
+ tMax, // ray max range
+ 2 // payload (location = 2)
+ );
+ // Did we hit the lantern we expected?
+ isShadowed = (hitLanternInstance != pushC.lanternPassNumber);
+ }
+ }
+````
+
+Notice that we determine whether this lantern is shadowed at this pixel by
+checking if the hit lantern number matches the lantern whose light is being
+added this pass; again, this ensures lanterns correctly shadow each others' light.
+
+## Write Payload
+
+Replace `lightIntensity` with `colorIntensity` and ask the raygen shader
+for additive blending.
+
+```` C
+ prd.hitValue = colorIntensity * (attenuation * (diffuse + specular));
+ prd.additiveBlending = true;
+````
+
+# Trace Rays Indirect
+
+Everything is finally set up to actually run the extra lantern passes
+in `HelloVulkan::raytrace`. We've already dispatched the compute
+shader in an earlier section. After that, we can run the first raytrace
+pass. There are minimal changes from before, we just have to
+
+* Initialize the new push constant values (especially setting
+ `lanternPassNumber=-1` to indicate this is not a lantern pass).
+
+```` C
+ // Initialize push constant values
+ m_rtPushConstants.clearColor = clearColor;
+ m_rtPushConstants.lightPosition = m_pushConstant.lightPosition;
+ m_rtPushConstants.lightIntensity = m_pushConstant.lightIntensity;
+ m_rtPushConstants.lightType = m_pushConstant.lightType;
+ m_rtPushConstants.lanternPassNumber = -1; // Global non-lantern pass
+ m_rtPushConstants.screenX = m_size.width;
+ m_rtPushConstants.screenY = m_size.height;
+ m_rtPushConstants.lanternDebug = m_lanternDebug;
+````
+
+* Update the addresses of the raygen, miss, and hit group sections of the SBT
+ to account for the added shaders.
+
+```` C
+ using Stride = vk::StridedDeviceAddressRegionKHR;
+ std::array strideAddresses{
+ Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen
+ Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 3}, // miss
+ Stride{sbtAddress + 4u * groupSize, groupStride, groupSize * 4}, // hit
+ Stride{0u, 0u, 0u}}; // callable
+
+ // First pass, illuminate scene with global light.
+ cmdBuf.traceRaysKHR(
+ &strideAddresses[0], &strideAddresses[1], //
+ &strideAddresses[2], &strideAddresses[3], //
+ m_size.width, m_size.height, 1);
+````
+
+After that, we can open a loop for performing all lantern passes.
+
+```` C
+ // Lantern passes, ensure previous pass completed, then add light contribution from each lantern.
+ for (int i = 0; i < static_cast(m_lanternCount); ++i)
+ {
+````
+
+Because the additive blending in the shader requires read-modify-write operations,
+we need a barrier between every pass.
+
+```` C
+ // Barrier to ensure previous pass finished.
+ vk::Image offscreenImage{m_offscreenColor.image};
+ vk::ImageSubresourceRange colorRange(
+ vk::ImageAspectFlagBits::eColor, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS
+ );
+ vk::ImageMemoryBarrier imageBarrier;
+ imageBarrier.setOldLayout(vk::ImageLayout::eGeneral);
+ imageBarrier.setNewLayout(vk::ImageLayout::eGeneral);
+ imageBarrier.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ imageBarrier.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED);
+ imageBarrier.setImage(offscreenImage);
+ imageBarrier.setSubresourceRange(colorRange);
+ imageBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
+ imageBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
+ cmdBuf.pipelineBarrier(
+ vk::PipelineStageFlagBits::eRayTracingShaderKHR, //
+ vk::PipelineStageFlagBits::eRayTracingShaderKHR, //
+ vk::DependencyFlags(0), //
+ {}, {}, {imageBarrier});
+````
+
+Then, we can pass the number of the lantern pass being performed (`i`), and look
+up the indirect parameters for that entry. Unlike draw and dispatch indirect, the
+indirect paramater location is passed as a raw device address, so we need to
+perform manual device pointer arithmetic to look up the $i^{th}$ entry of the
+`LanternIndirectEntry` array. Take advantage of the fact that `VkTraceRaysIndirectCommandKHR`
+is the first member of `LanternIndirectEntry`.
+
+```` C
+ // Set lantern pass number.
+ m_rtPushConstants.lanternPassNumber = i;
+ cmdBuf.pushConstants(m_rtPipelineLayout,
+ vk::ShaderStageFlagBits::eRaygenKHR
+ | vk::ShaderStageFlagBits::eClosestHitKHR
+ | vk::ShaderStageFlagBits::eMissKHR,
+ 0, m_rtPushConstants);
+
+ // Execute lantern pass.
+ cmdBuf.traceRaysIndirectKHR(
+ &strideAddresses[0], &strideAddresses[1], //
+ &strideAddresses[2], &strideAddresses[3], //
+ m_device.getBufferAddress({m_lanternIndirectBuffer.buffer}) + i * sizeof(LanternIndirectEntry));
+ }
+````
+
+Everything should be in order now. We can see in this image that the cyan and purple lanterns
+are both shadowed by the doodad hanging off the side of the building, and the spikes on the
+roof cut shadows in the yellow lantern's light.
+
+
+
+Zoom out and enable the lantern debug checkbox to see the scissor rectangles.
+
+
+
+## Cleanup
+
+One last loose end, we have to clean up all the new resources is `HelloVulkan::destroyResources`.
+
+```` C
+// Destroying all allocations
+//
+void HelloVulkan::destroyResources()
+{
+ // ...
+
+ // #VKRay
+ // ...
+ m_device.destroy(m_lanternIndirectDescPool);
+ m_device.destroy(m_lanternIndirectDescSetLayout);
+ m_device.destroy(m_lanternIndirectCompPipeline);
+ m_device.destroy(m_lanternIndirectCompPipelineLayout);
+ m_alloc.destroy(m_lanternIndirectBuffer);
+ m_alloc.destroy(m_lanternVertexBuffer);
+ m_alloc.destroy(m_lanternIndexBuffer);
+}
+````
+
+# Final Code
+
+You can find the final code in the folder [ray_tracing_indirect_scissor](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_indirect_scissor)
+
+
+
+
+
+
+
+