diff --git a/CMakeLists.txt b/CMakeLists.txt index 9484aee..14077ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -52,3 +52,4 @@ add_subdirectory(ray_tracing_manyhits) add_subdirectory(ray_tracing_rayquery) add_subdirectory(ray_tracing_reflections) +add_subdirectory(ray_tracing_indirect_scissor) diff --git a/docs/Images/indirect_scissor/bounding.png b/docs/Images/indirect_scissor/bounding.png new file mode 100644 index 0000000..0465eb9 Binary files /dev/null and b/docs/Images/indirect_scissor/bounding.png differ diff --git a/docs/Images/indirect_scissor/bounding2.png b/docs/Images/indirect_scissor/bounding2.png new file mode 100644 index 0000000..03a5b36 Binary files /dev/null and b/docs/Images/indirect_scissor/bounding2.png differ diff --git a/docs/Images/indirect_scissor/intro.png b/docs/Images/indirect_scissor/intro.png new file mode 100644 index 0000000..463a797 Binary files /dev/null and b/docs/Images/indirect_scissor/intro.png differ diff --git a/docs/Images/indirect_scissor/rgb.png b/docs/Images/indirect_scissor/rgb.png new file mode 100644 index 0000000..6c60977 Binary files /dev/null and b/docs/Images/indirect_scissor/rgb.png differ diff --git a/docs/Images/indirect_scissor/shadows.png b/docs/Images/indirect_scissor/shadows.png new file mode 100644 index 0000000..a8f068b Binary files /dev/null and b/docs/Images/indirect_scissor/shadows.png differ diff --git a/docs/setup.md b/docs/setup.md index a954bae..2dc23eb 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -35,16 +35,17 @@ See also [build_all](https://github.com/nvpro-samples/build_all) from nvpro-samp ## Latest Vulkan SDK This repository tries to always be up to date with the latest Vulkan SDK, therefore we suggest to download and install it. +Version 1.2.162.0 and up has ray tracing extensions support. **Vulkan SDK**: https://vulkan.lunarg.com/sdk/home -## Beta Installation +## Driver -KHR ray tracing is still in Beta, therefore you will need the latest -Vulkan driver. +NVIDIA driver 160.0 and up support Vulkan ray tracing. -**Latest driver**: https://developer.nvidia.com/vulkan-driver +* Standard driver: https://www.nvidia.com/Download/index.aspx +* Vulkan beta driver: https://developer.nvidia.com/vulkan-driver ## CMake diff --git a/docs/vkrt_tuto_indirect_scissor.md.htm b/docs/vkrt_tuto_indirect_scissor.md.htm new file mode 100644 index 0000000..3892bc8 --- /dev/null +++ b/docs/vkrt_tuto_indirect_scissor.md.htm @@ -0,0 +1,1505 @@ + +**NVIDIA Vulkan Ray Tracing Tutorial** +**Trace Rays Indirect** + +Authors: David Zhao Akeley + +![](Images/indirect_scissor/intro.png) + +This is an extension of the [Vulkan ray tracing tutorial](vkrt_tutorial.md.htm). + +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). + +![](Images/indirect_scissor/bounding.png) + +# 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 = 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(int 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, chitSM, "main"}); + hg.setClosestHitShader(static_cast(stages.size() - 1)); + 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternChitSM, "main"}); + lanternHg.setClosestHitShader(static_cast(stages.size() - 1)); + 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. + +![](Images/indirect_scissor/rgb.png) + +## 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowObjChitSM, "main"}); + lanternShadowObjHg.setClosestHitShader(static_cast(stages.size() - 1)); + 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowLanternChitSM, "main"}); + lanternShadowLanternHg.setClosestHitShader(static_cast(stages.size() - 1)); + 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() - 1`). 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. + +![](Images/indirect_scissor/shadows.png) + +Zoom out and enable the lantern debug checkbox to see the scissor rectangles. + +![](Images/indirect_scissor/bounding2.png) + +## 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) + + + + + + + + diff --git a/docs/vkrt_tutorial.md.htm b/docs/vkrt_tutorial.md.htm index 6c76782..be1b70f 100644 --- a/docs/vkrt_tutorial.md.htm +++ b/docs/vkrt_tutorial.md.htm @@ -1697,11 +1697,17 @@ the usage flag to include that stage. This was done in section Additions to the ## updateUniformBuffer -The computation of the matrix inverses is done in `updateUniformBuffer`, after setting the `ubo.proj` matrix: +The computation of the matrix inverses is done in `updateUniformBuffer`, after setting the `hostUBO.proj` matrix: ```` C // #VKRay -ubo.projInverse = nvmath::invert(ubo.proj); +hostUBO.projInverse = nvmath::invert(hostUBO.proj); +```` + +We also have to indicate that the UBO will be used in the raytracing shaders. +```` C + auto uboUsageStages = vk::PipelineStageFlagBits::eVertexShader + | vk::PipelineStageFlagBits::eRayTracingShaderKHR; ```` ## Ray generation (raytrace.rgen) @@ -2164,6 +2170,20 @@ At the end of the method, we destroy the shader module for the shadow miss shade The spec does not guarantee a recursion check at runtime. If you exceed either the recursion depth you reported in the raytrace pipeline create info, or the physical device recursion limit, undefined behaviour results. + + The KHR raytracing spec lowers the minimum guaranteed recursion limit from + 31 (in the original NV spec) to the much more modest limit of 1 (i.e. no + recursion at all). Since we now need a recursion limit of 2, we should check + that the device supports the needed level of recursion: + + ```` C + // Spec only guarantees 1 level of "recursion". Check for that sad possibility here. + if (m_rtProperties.maxRayRecursionDepth <= 1) { + throw std::runtime_error("Device fails to support ray recursion (m_rtProperties.maxRayRecursionDepth <= 1)"); + } + ```` + + Recall that `m_rtProperties` was filled in in `HelloVulkan::initRayTracing`. ## `traceRaysKHR` diff --git a/ray_tracing_indirect_scissor/CMakeLists.txt b/ray_tracing_indirect_scissor/CMakeLists.txt new file mode 100644 index 0000000..7b57615 --- /dev/null +++ b/ray_tracing_indirect_scissor/CMakeLists.txt @@ -0,0 +1,111 @@ +#***************************************************************************** +# Copyright 2020 NVIDIA Corporation. All rights reserved. +#***************************************************************************** + +cmake_minimum_required(VERSION 3.9.6 FATAL_ERROR) + +#-------------------------------------------------------------------------------------------------- +# Project setting +get_filename_component(PROJNAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) +SET(PROJNAME vk_${PROJNAME}_KHR) +project(${PROJNAME} LANGUAGES C CXX) +message(STATUS "-------------------------------") +message(STATUS "Processing Project ${PROJNAME}:") + + +#-------------------------------------------------------------------------------------------------- +# C++ target and defines +set(CMAKE_CXX_STANDARD 17) +add_executable(${PROJNAME}) +_add_project_definitions(${PROJNAME}) + + +#-------------------------------------------------------------------------------------------------- +# Source files for this project +# +file(GLOB SOURCE_FILES *.cpp *.hpp *.inl *.h *.c) +file(GLOB EXTRA_COMMON ${TUTO_KHR_DIR}/common/*.*) +list(APPEND COMMON_SOURCE_FILES ${EXTRA_COMMON}) +include_directories(${TUTO_KHR_DIR}/common) + + +#-------------------------------------------------------------------------------------------------- +# GLSL to SPIR-V custom build +# +SET(VULKAN_TARGET_ENV vulkan1.2) +UNSET(GLSL_SOURCES) +UNSET(SPV_OUTPUT) +file(GLOB_RECURSE GLSL_HEADER_FILES "shaders/*.h" "shaders/*.glsl") +file(GLOB_RECURSE GLSL_SOURCE_FILES + "shaders/*.comp" + "shaders/*.frag" + "shaders/*.vert" + "shaders/*.rchit" + "shaders/*.rahit" + "shaders/*.rint" + "shaders/*.rmiss" + "shaders/*.rgen" + "shaders/*.rcall" + ) +foreach(GLSL ${GLSL_SOURCE_FILES}) + get_filename_component(FILE_NAME ${GLSL} NAME) + _compile_GLSL(${GLSL} "shaders/${FILE_NAME}.spv" GLSL_SOURCES SPV_OUTPUT) +endforeach(GLSL) +list(APPEND GLSL_SOURCES ${GLSL_HEADER_FILES}) + + +#-------------------------------------------------------------------------------------------------- +# Sources +target_sources(${PROJNAME} PUBLIC ${SOURCE_FILES} ${HEADER_FILES}) +target_sources(${PROJNAME} PUBLIC ${COMMON_SOURCE_FILES}) +target_sources(${PROJNAME} PUBLIC ${PACKAGE_SOURCE_FILES}) +target_sources(${PROJNAME} PUBLIC ${GLSL_SOURCES}) + + +#-------------------------------------------------------------------------------------------------- +# Sub-folders in Visual Studio +# +source_group("Common" FILES ${COMMON_SOURCE_FILES} ${PACKAGE_SOURCE_FILES}) +source_group("Sources" FILES ${SOURCE_FILES}) +source_group("Headers" FILES ${HEADER_FILES}) +source_group("Shader_Files" FILES ${GLSL_SOURCES}) + + +#-------------------------------------------------------------------------------------------------- +# Linkage +# +target_link_libraries(${PROJNAME} ${PLATFORM_LIBRARIES} shared_sources) + +foreach(DEBUGLIB ${LIBRARIES_DEBUG}) + target_link_libraries(${PROJNAME} debug ${DEBUGLIB}) +endforeach(DEBUGLIB) + +foreach(RELEASELIB ${LIBRARIES_OPTIMIZED}) + target_link_libraries(${PROJNAME} optimized ${RELEASELIB}) +endforeach(RELEASELIB) + +#-------------------------------------------------------------------------------------------------- +# copies binaries that need to be put next to the exe files (ZLib, etc.) +# +_copy_binaries_to_target( ${PROJNAME} ) + + +#install(FILES ${SPV_OUTPUT} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}/shaders") +#install(FILES ${SPV_OUTPUT} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}/shaders") +#install(FILES ${CUBIN_SOURCES} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}") +#install(FILES ${CUBIN_SOURCES} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}") +#install(DIRECTORY "../media" CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}") +#install(DIRECTORY "../media" CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}") + +#---------------------------------------------------------------------------------------------------- +# Copying elements +# Media +# target_copy_to_output_dir(TARGET ${PROJECT_NAME} FILES "${TUTO_KHR_DIR}/media") +# Spir-V Shaders +target_copy_to_output_dir( + TARGET ${PROJECT_NAME} + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + DEST_SUBFOLDER "${PROJECT_NAME}/" + FILES ${SPV_OUTPUT} + ) + diff --git a/ray_tracing_indirect_scissor/README.md b/ray_tracing_indirect_scissor/README.md new file mode 100644 index 0000000..3cb49a5 --- /dev/null +++ b/ray_tracing_indirect_scissor/README.md @@ -0,0 +1,14 @@ +# NVIDIA Vulkan Ray Tracing Tutorial + +This example is the result of the ray tracing tutorial. +The tutorial is adding ray tracing capability to an OBJ rasterizer in Vulkan + +If you haven't done it, [**Start Ray Tracing Tutorial**](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/). + +![resultRaytraceShadowMedieval](../docs/Images/resultRaytraceShadowMedieval.png) + +## Going Further + +Once the tutorial completed and the basics of ray tracing are in place, other tuturials are going further from this code base. + +See all other [additional ray tracing tutorials](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/vkrt_tuto_further.md.html) diff --git a/ray_tracing_indirect_scissor/hello_vulkan.cpp b/ray_tracing_indirect_scissor/hello_vulkan.cpp new file mode 100644 index 0000000..38086e2 --- /dev/null +++ b/ray_tracing_indirect_scissor/hello_vulkan.cpp @@ -0,0 +1,1468 @@ +/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include + +extern std::vector defaultSearchPaths; + +#define STB_IMAGE_IMPLEMENTATION +#include "fileformats/stb_image.h" +#include "obj_loader.h" + +#include "hello_vulkan.h" +#include "nvh/alignment.hpp" +#include "nvh/cameramanipulator.hpp" +#include "nvh/fileoperations.hpp" +#include "nvvk/commands_vk.hpp" +#include "nvvk/descriptorsets_vk.hpp" +#include "nvvk/pipeline_vk.hpp" +#include "nvvk/renderpasses_vk.hpp" +#include "nvvk/shaders_vk.hpp" + + +// Holding the camera matrices +struct CameraMatrices +{ + nvmath::mat4f view; + nvmath::mat4f proj; + nvmath::mat4f viewInverse; + // #VKRay + nvmath::mat4f projInverse; +}; + + +//-------------------------------------------------------------------------------------------------- +// Keep the handle on the device +// Initialize the tool to do all our allocations: buffers, images +// +void HelloVulkan::setup(const vk::Instance& instance, + const vk::Device& device, + const vk::PhysicalDevice& physicalDevice, + uint32_t queueFamily) +{ + AppBase::setup(instance, device, physicalDevice, queueFamily); + m_alloc.init(device, physicalDevice); + m_debug.setup(m_device); +} + +//-------------------------------------------------------------------------------------------------- +// Called at each frame to update the camera matrix +// +void HelloVulkan::updateUniformBuffer(const vk::CommandBuffer& cmdBuf) +{ + // Prepare new UBO contents on host. + const float aspectRatio = m_size.width / static_cast(m_size.height); + CameraMatrices hostUBO = {}; + hostUBO.view = CameraManip.getMatrix(); + hostUBO.proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // hostUBO.proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + hostUBO.viewInverse = nvmath::invert(hostUBO.view); + // #VKRay + hostUBO.projInverse = nvmath::invert(hostUBO.proj); + + // UBO on the device, and what stages access it. + vk::Buffer deviceUBO = m_cameraMat.buffer; + auto uboUsageStages = vk::PipelineStageFlagBits::eVertexShader + | vk::PipelineStageFlagBits::eRayTracingShaderKHR; + + // Ensure that the modified UBO is not visible to previous frames. + vk::BufferMemoryBarrier beforeBarrier; + beforeBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderRead); + beforeBarrier.setDstAccessMask(vk::AccessFlagBits::eTransferWrite); + beforeBarrier.setBuffer(deviceUBO); + beforeBarrier.setOffset(0); + beforeBarrier.setSize(sizeof hostUBO); + cmdBuf.pipelineBarrier( + uboUsageStages, + vk::PipelineStageFlagBits::eTransfer, + vk::DependencyFlagBits::eDeviceGroup, {}, {beforeBarrier}, {}); + + // Schedule the host-to-device upload. (hostUBO is copied into the cmd + // buffer so it is okay to deallocate when the function returns). + cmdBuf.updateBuffer(m_cameraMat.buffer, 0, hostUBO); + + // Making sure the updated UBO will be visible. + vk::BufferMemoryBarrier afterBarrier; + afterBarrier.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite); + afterBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead); + afterBarrier.setBuffer(deviceUBO); + afterBarrier.setOffset(0); + afterBarrier.setSize(sizeof hostUBO); + cmdBuf.pipelineBarrier( + vk::PipelineStageFlagBits::eTransfer, + uboUsageStages, + vk::DependencyFlagBits::eDeviceGroup, {}, {afterBarrier}, {}); +} + + +//-------------------------------------------------------------------------------------------------- +// Describing the layout pushed when rendering +// +void HelloVulkan::createDescriptorSetLayout() +{ + using vkDS = vk::DescriptorSetLayoutBinding; + using vkDT = vk::DescriptorType; + using vkSS = vk::ShaderStageFlagBits; + uint32_t nbTxt = static_cast(m_textures.size()); + uint32_t nbObj = static_cast(m_objModel.size()); + + // Camera matrices (binding = 0) + m_descSetLayoutBind.addBinding( + vkDS(0, vkDT::eUniformBuffer, 1, vkSS::eVertex | vkSS::eRaygenKHR)); + // Materials (binding = 1) + m_descSetLayoutBind.addBinding( + vkDS(1, vkDT::eStorageBuffer, nbObj, vkSS::eVertex | vkSS::eFragment | vkSS::eClosestHitKHR)); + // Scene description (binding = 2) + m_descSetLayoutBind.addBinding( // + vkDS(2, vkDT::eStorageBuffer, 1, vkSS::eVertex | vkSS::eFragment | vkSS::eClosestHitKHR)); + // Textures (binding = 3) + m_descSetLayoutBind.addBinding( + vkDS(3, vkDT::eCombinedImageSampler, nbTxt, vkSS::eFragment | vkSS::eClosestHitKHR)); + // Materials (binding = 4) + m_descSetLayoutBind.addBinding( + vkDS(4, vkDT::eStorageBuffer, nbObj, vkSS::eFragment | vkSS::eClosestHitKHR)); + // Storing vertices (binding = 5) + m_descSetLayoutBind.addBinding( // + vkDS(5, vkDT::eStorageBuffer, nbObj, vkSS::eClosestHitKHR)); + // Storing indices (binding = 6) + m_descSetLayoutBind.addBinding( // + vkDS(6, vkDT::eStorageBuffer, nbObj, vkSS::eClosestHitKHR)); + + + m_descSetLayout = m_descSetLayoutBind.createLayout(m_device); + m_descPool = m_descSetLayoutBind.createPool(m_device, 1); + m_descSet = nvvk::allocateDescriptorSet(m_device, m_descPool, m_descSetLayout); +} + +//-------------------------------------------------------------------------------------------------- +// Setting up the buffers in the descriptor set +// +void HelloVulkan::updateDescriptorSet() +{ + std::vector writes; + + // Camera matrices and scene description + vk::DescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + vk::DescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 2, &dbiSceneDesc)); + + // All material buffers, 1 buffer per OBJ + std::vector dbiMat; + std::vector dbiMatIdx; + std::vector dbiVert; + std::vector dbiIdx; + for(auto& obj : m_objModel) + { + dbiMat.emplace_back(obj.matColorBuffer.buffer, 0, VK_WHOLE_SIZE); + dbiMatIdx.emplace_back(obj.matIndexBuffer.buffer, 0, VK_WHOLE_SIZE); + dbiVert.emplace_back(obj.vertexBuffer.buffer, 0, VK_WHOLE_SIZE); + dbiIdx.emplace_back(obj.indexBuffer.buffer, 0, VK_WHOLE_SIZE); + } + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 1, dbiMat.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 4, dbiMatIdx.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 5, dbiVert.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 6, dbiIdx.data())); + + // All texture samplers + std::vector diit; + for(auto& texture : m_textures) + { + diit.emplace_back(texture.descriptor); + } + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 3, diit.data())); + + // Writing the information + m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Creating the pipeline layout +// +void HelloVulkan::createGraphicsPipeline() +{ + using vkSS = vk::ShaderStageFlagBits; + + vk::PushConstantRange pushConstantRanges = {vkSS::eVertex | vkSS::eFragment, 0, + sizeof(ObjPushConstant)}; + + // Creating the Pipeline Layout + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo; + vk::DescriptorSetLayout descSetLayout(m_descSetLayout); + pipelineLayoutCreateInfo.setSetLayoutCount(1); + pipelineLayoutCreateInfo.setPSetLayouts(&descSetLayout); + pipelineLayoutCreateInfo.setPushConstantRangeCount(1); + pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges); + m_pipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo); + + // Creating the Pipeline + std::vector paths = defaultSearchPaths; + nvvk::GraphicsPipelineGeneratorCombined gpb(m_device, m_pipelineLayout, m_offscreenRenderPass); + gpb.depthStencilState.depthTestEnable = true; + gpb.addShader(nvh::loadFile("shaders/vert_shader.vert.spv", true, paths, true), vkSS::eVertex); + gpb.addShader(nvh::loadFile("shaders/frag_shader.frag.spv", true, paths, true), vkSS::eFragment); + gpb.addBindingDescription({0, sizeof(VertexObj)}); + gpb.addAttributeDescriptions(std::vector{ + {0, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, pos)}, + {1, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, nrm)}, + {2, 0, vk::Format::eR32G32B32Sfloat, offsetof(VertexObj, color)}, + {3, 0, vk::Format::eR32G32Sfloat, offsetof(VertexObj, texCoord)}}); + + m_graphicsPipeline = gpb.createPipeline(); + m_debug.setObjectName(m_graphicsPipeline, "Graphics"); +} + +//-------------------------------------------------------------------------------------------------- +// Loading the OBJ file and setting up all buffers +// +void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform) +{ + using vkBU = vk::BufferUsageFlagBits; + + LOGI("Loading File: %s \n", filename.c_str()); + ObjLoader loader; + loader.loadModel(filename); + + // Converting from Srgb to linear + for(auto& m : loader.m_materials) + { + m.ambient = nvmath::pow(m.ambient, 2.2f); + m.diffuse = nvmath::pow(m.diffuse, 2.2f); + m.specular = nvmath::pow(m.specular, 2.2f); + } + + ObjInstance instance; + instance.objIndex = static_cast(m_objModel.size()); + instance.transform = transform; + instance.transformIT = nvmath::transpose(nvmath::invert(transform)); + instance.txtOffset = static_cast(m_textures.size()); + + ObjModel model; + model.nbIndices = static_cast(loader.m_indices.size()); + model.nbVertices = static_cast(loader.m_vertices.size()); + + // Create the buffers on Device and copy vertices, indices and materials + nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex); + vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer(); + model.vertexBuffer = + m_alloc.createBuffer(cmdBuf, loader.m_vertices, + vkBU::eVertexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress + | vkBU::eAccelerationStructureBuildInputReadOnlyKHR); + model.indexBuffer = + m_alloc.createBuffer(cmdBuf, loader.m_indices, + vkBU::eIndexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress + | vkBU::eAccelerationStructureBuildInputReadOnlyKHR); + model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, vkBU::eStorageBuffer); + model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, vkBU::eStorageBuffer); + // Creates all textures found + createTextureImages(cmdBuf, loader.m_textures); + cmdBufGet.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + + std::string objNb = std::to_string(instance.objIndex); + m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb).c_str())); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb).c_str())); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb).c_str())); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb).c_str())); + + m_objModel.emplace_back(model); + m_objInstance.emplace_back(instance); +} + +// 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}); +} + +//-------------------------------------------------------------------------------------------------- +// Creating the uniform buffer holding the camera matrices +// - Buffer is host visible +// +void HelloVulkan::createUniformBuffer() +{ + using vkBU = vk::BufferUsageFlagBits; + using vkMP = vk::MemoryPropertyFlagBits; + + m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), + vkBU::eUniformBuffer | vkBU::eTransferDst, vkMP::eDeviceLocal); + m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); +} + +//-------------------------------------------------------------------------------------------------- +// Create a storage buffer containing the description of the scene elements +// - Which geometry is used by which instance +// - Transformation +// - Offset for texture +// +void HelloVulkan::createSceneDescriptionBuffer() +{ + using vkBU = vk::BufferUsageFlagBits; + nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); + + auto cmdBuf = cmdGen.createCommandBuffer(); + m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, vkBU::eStorageBuffer); + cmdGen.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); +} + +//-------------------------------------------------------------------------------------------------- +// Creating all textures and samplers +// +void HelloVulkan::createTextureImages(const vk::CommandBuffer& cmdBuf, + const std::vector& textures) +{ + using vkIU = vk::ImageUsageFlagBits; + + vk::SamplerCreateInfo samplerCreateInfo{ + {}, vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear}; + samplerCreateInfo.setMaxLod(FLT_MAX); + vk::Format format = vk::Format::eR8G8B8A8Srgb; + + // If no textures are present, create a dummy one to accommodate the pipeline layout + if(textures.empty() && m_textures.empty()) + { + nvvk::Texture texture; + + std::array color{255u, 255u, 255u, 255u}; + vk::DeviceSize bufferSize = sizeof(color); + auto imgSize = vk::Extent2D(1, 1); + auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format); + + // Creating the dummy texture + nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, color.data(), imageCreateInfo); + vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo); + texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo); + + // The image format must be in VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + nvvk::cmdBarrierImageLayout(cmdBuf, texture.image, vk::ImageLayout::eUndefined, + vk::ImageLayout::eShaderReadOnlyOptimal); + m_textures.push_back(texture); + } + else + { + // Uploading all images + for(const auto& texture : textures) + { + std::stringstream o; + int texWidth, texHeight, texChannels; + o << "media/textures/" << texture; + std::string txtFile = nvh::findFile(o.str(), defaultSearchPaths, true); + + stbi_uc* stbi_pixels = + stbi_load(txtFile.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + + std::array color{255u, 0u, 255u, 255u}; + + stbi_uc* pixels = stbi_pixels; + // Handle failure + if(!stbi_pixels) + { + texWidth = texHeight = 1; + texChannels = 4; + pixels = reinterpret_cast(color.data()); + } + + vk::DeviceSize bufferSize = static_cast(texWidth) * texHeight * sizeof(uint8_t) * 4; + auto imgSize = vk::Extent2D(texWidth, texHeight); + auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format, vkIU::eSampled, true); + + { + nvvk::ImageDedicated image = + m_alloc.createImage(cmdBuf, bufferSize, pixels, imageCreateInfo); + nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels); + vk::ImageViewCreateInfo ivInfo = + nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo); + nvvk::Texture texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo); + + m_textures.push_back(texture); + } + + stbi_image_free(stbi_pixels); + } + } +} + +//-------------------------------------------------------------------------------------------------- +// Destroying all allocations +// +void HelloVulkan::destroyResources() +{ + m_device.destroy(m_graphicsPipeline); + m_device.destroy(m_pipelineLayout); + m_device.destroy(m_descPool); + m_device.destroy(m_descSetLayout); + m_alloc.destroy(m_cameraMat); + m_alloc.destroy(m_sceneDesc); + + for(auto& m : m_objModel) + { + m_alloc.destroy(m.vertexBuffer); + m_alloc.destroy(m.indexBuffer); + m_alloc.destroy(m.matColorBuffer); + m_alloc.destroy(m.matIndexBuffer); + } + + for(auto& t : m_textures) + { + m_alloc.destroy(t); + } + + //#Post + m_device.destroy(m_postPipeline); + m_device.destroy(m_postPipelineLayout); + m_device.destroy(m_postDescPool); + m_device.destroy(m_postDescSetLayout); + m_alloc.destroy(m_offscreenColor); + m_alloc.destroy(m_offscreenDepth); + m_device.destroy(m_offscreenRenderPass); + m_device.destroy(m_offscreenFramebuffer); + + // #VKRay + m_rtBuilder.destroy(); + m_device.destroy(m_rtDescPool); + m_device.destroy(m_rtDescSetLayout); + m_device.destroy(m_rtPipeline); + m_device.destroy(m_rtPipelineLayout); + m_alloc.destroy(m_rtSBTBuffer); + + 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); +} + +//-------------------------------------------------------------------------------------------------- +// Drawing the scene in raster mode +// +void HelloVulkan::rasterize(const vk::CommandBuffer& cmdBuf) +{ + using vkPBP = vk::PipelineBindPoint; + using vkSS = vk::ShaderStageFlagBits; + vk::DeviceSize offset{0}; + + m_debug.beginLabel(cmdBuf, "Rasterize"); + + // Dynamic Viewport + cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)}); + cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}}); + + // Drawing all triangles + cmdBuf.bindPipeline(vkPBP::eGraphics, m_graphicsPipeline); + cmdBuf.bindDescriptorSets(vkPBP::eGraphics, m_pipelineLayout, 0, {m_descSet}, {}); + for(int i = 0; i < m_objInstance.size(); ++i) + { + auto& inst = m_objInstance[i]; + auto& model = m_objModel[inst.objIndex]; + m_pushConstant.instanceId = i; // Telling which instance is drawn + cmdBuf.pushConstants(m_pipelineLayout, vkSS::eVertex | vkSS::eFragment, 0, + m_pushConstant); + + cmdBuf.bindVertexBuffers(0, {model.vertexBuffer.buffer}, {offset}); + cmdBuf.bindIndexBuffer(model.indexBuffer.buffer, 0, vk::IndexType::eUint32); + cmdBuf.drawIndexed(model.nbIndices, 1, 0, 0, 0); + } + m_debug.endLabel(cmdBuf); +} + +//-------------------------------------------------------------------------------------------------- +// Handling resize of the window +// +void HelloVulkan::onResize(int /*w*/, int /*h*/) +{ + createOffscreenRender(); + updatePostDescriptorSet(); + updateRtDescriptorSet(); +} + +////////////////////////////////////////////////////////////////////////// +// Post-processing +////////////////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------------------------------------------- +// Creating an offscreen frame buffer and the associated render pass +// +void HelloVulkan::createOffscreenRender() +{ + m_alloc.destroy(m_offscreenColor); + m_alloc.destroy(m_offscreenDepth); + + // Creating the color image + { + auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenColorFormat, + vk::ImageUsageFlagBits::eColorAttachment + | vk::ImageUsageFlagBits::eSampled + | vk::ImageUsageFlagBits::eStorage); + + + nvvk::Image image = m_alloc.createImage(colorCreateInfo); + vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo); + m_offscreenColor = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo()); + m_offscreenColor.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + } + + // Creating the depth buffer + auto depthCreateInfo = + nvvk::makeImage2DCreateInfo(m_size, m_offscreenDepthFormat, + vk::ImageUsageFlagBits::eDepthStencilAttachment); + { + nvvk::Image image = m_alloc.createImage(depthCreateInfo); + + vk::ImageViewCreateInfo depthStencilView; + depthStencilView.setViewType(vk::ImageViewType::e2D); + depthStencilView.setFormat(m_offscreenDepthFormat); + depthStencilView.setSubresourceRange({vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1}); + depthStencilView.setImage(image.image); + + m_offscreenDepth = m_alloc.createTexture(image, depthStencilView); + } + + // Setting the image layout for both color and depth + { + nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex); + auto cmdBuf = genCmdBuf.createCommandBuffer(); + nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenColor.image, vk::ImageLayout::eUndefined, + vk::ImageLayout::eGeneral); + nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenDepth.image, vk::ImageLayout::eUndefined, + vk::ImageLayout::eDepthStencilAttachmentOptimal, + vk::ImageAspectFlagBits::eDepth); + + genCmdBuf.submitAndWait(cmdBuf); + } + + // Creating a renderpass for the offscreen + if(!m_offscreenRenderPass) + { + m_offscreenRenderPass = + nvvk::createRenderPass(m_device, {m_offscreenColorFormat}, m_offscreenDepthFormat, 1, true, + true, vk::ImageLayout::eGeneral, vk::ImageLayout::eGeneral); + } + + // Creating the frame buffer for offscreen + std::vector attachments = {m_offscreenColor.descriptor.imageView, + m_offscreenDepth.descriptor.imageView}; + + m_device.destroy(m_offscreenFramebuffer); + vk::FramebufferCreateInfo info; + info.setRenderPass(m_offscreenRenderPass); + info.setAttachmentCount(2); + info.setPAttachments(attachments.data()); + info.setWidth(m_size.width); + info.setHeight(m_size.height); + info.setLayers(1); + m_offscreenFramebuffer = m_device.createFramebuffer(info); +} + +//-------------------------------------------------------------------------------------------------- +// The pipeline is how things are rendered, which shaders, type of primitives, depth test and more +// +void HelloVulkan::createPostPipeline() +{ + // Push constants in the fragment shader + vk::PushConstantRange pushConstantRanges = {vk::ShaderStageFlagBits::eFragment, 0, sizeof(float)}; + + // Creating the pipeline layout + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo; + pipelineLayoutCreateInfo.setSetLayoutCount(1); + pipelineLayoutCreateInfo.setPSetLayouts(&m_postDescSetLayout); + pipelineLayoutCreateInfo.setPushConstantRangeCount(1); + pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges); + m_postPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo); + + // Pipeline: completely generic, no vertices + std::vector paths = defaultSearchPaths; + + nvvk::GraphicsPipelineGeneratorCombined pipelineGenerator(m_device, m_postPipelineLayout, + m_renderPass); + pipelineGenerator.addShader(nvh::loadFile("shaders/passthrough.vert.spv", true, paths, true), + vk::ShaderStageFlagBits::eVertex); + pipelineGenerator.addShader(nvh::loadFile("shaders/post.frag.spv", true, paths, true), + vk::ShaderStageFlagBits::eFragment); + pipelineGenerator.rasterizationState.setCullMode(vk::CullModeFlagBits::eNone); + m_postPipeline = pipelineGenerator.createPipeline(); + m_debug.setObjectName(m_postPipeline, "post"); +} + +//-------------------------------------------------------------------------------------------------- +// The descriptor layout is the description of the data that is passed to the vertex or the +// fragment program. +// +void HelloVulkan::createPostDescriptor() +{ + using vkDS = vk::DescriptorSetLayoutBinding; + using vkDT = vk::DescriptorType; + using vkSS = vk::ShaderStageFlagBits; + + m_postDescSetLayoutBind.addBinding(vkDS(0, vkDT::eCombinedImageSampler, 1, vkSS::eFragment)); + m_postDescSetLayout = m_postDescSetLayoutBind.createLayout(m_device); + m_postDescPool = m_postDescSetLayoutBind.createPool(m_device); + m_postDescSet = nvvk::allocateDescriptorSet(m_device, m_postDescPool, m_postDescSetLayout); +} + +//-------------------------------------------------------------------------------------------------- +// Update the output +// +void HelloVulkan::updatePostDescriptorSet() +{ + vk::WriteDescriptorSet writeDescriptorSets = + m_postDescSetLayoutBind.makeWrite(m_postDescSet, 0, &m_offscreenColor.descriptor); + m_device.updateDescriptorSets(writeDescriptorSets, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Draw a full screen quad with the attached image +// +void HelloVulkan::drawPost(vk::CommandBuffer cmdBuf) +{ + m_debug.beginLabel(cmdBuf, "Post"); + + cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)}); + cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}}); + + auto aspectRatio = static_cast(m_size.width) / static_cast(m_size.height); + cmdBuf.pushConstants(m_postPipelineLayout, vk::ShaderStageFlagBits::eFragment, 0, + aspectRatio); + cmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, m_postPipeline); + cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_postPipelineLayout, 0, + m_postDescSet, {}); + cmdBuf.draw(3, 1, 0, 0); + + m_debug.endLabel(cmdBuf); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------------------------------------------- +// Initialize Vulkan ray tracing +// #VKRay +void HelloVulkan::initRayTracing() +{ + // Requesting ray tracing properties + auto properties = + m_physicalDevice.getProperties2(); + m_rtProperties = properties.get(); + m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex); +} + +//-------------------------------------------------------------------------------------------------- +// Convert an OBJ model into the ray tracing geometry used to build the BLAS +// +nvvk::RaytracingBuilderKHR::BlasInput HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) +{ + // BLAS builder requires raw device addresses. + vk::DeviceAddress vertexAddress = m_device.getBufferAddress({model.vertexBuffer.buffer}); + vk::DeviceAddress indexAddress = m_device.getBufferAddress({model.indexBuffer.buffer}); + + uint32_t maxPrimitiveCount = model.nbIndices / 3; + + // Describe buffer as array of VertexObj. + vk::AccelerationStructureGeometryTrianglesDataKHR triangles; + triangles.setVertexFormat(vk::Format::eR32G32B32Sfloat); // vec3 vertex position data. + triangles.setVertexData(vertexAddress); + triangles.setVertexStride(sizeof(VertexObj)); + // 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(model.nbVertices); + + // 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 + nvvk::RaytracingBuilderKHR::BlasInput input; + input.asGeometry.emplace_back(asGeom); + input.asBuildOffsetInfo.emplace_back(offset); + + return input; +} + +// Tesselate a sphere as a list of triangles; return its +// vertices and indices as reference arguments. +void HelloVulkan::fillLanternVerts(std::vector& vertices, std::vector& indices) +{ + // Create a spherical lantern model by recursively tesselating an octahedron. + struct VertexIndex + { + nvmath::vec3f vertex; + uint32_t index; // Keep track of this vert's _eventual_ index in vertices. + }; + struct Triangle + { + VertexIndex vert0, vert1, vert2; + }; + + VertexIndex posX{{ m_lanternModelRadius, 0, 0}, 0}; + VertexIndex negX{{-m_lanternModelRadius, 0, 0}, 1}; + VertexIndex posY{{0, m_lanternModelRadius, 0}, 2}; + VertexIndex negY{{0, -m_lanternModelRadius, 0}, 3}; + VertexIndex posZ{{0, 0, m_lanternModelRadius}, 4}; + VertexIndex negZ{{0, 0, -m_lanternModelRadius}, 5}; + uint32_t vertexCount = 6; + + // Initial triangle list is octahedron. + std::vector triangles { + { posX, posY, posZ }, + { posX, posY, negZ }, + { posX, negY, posZ }, + { posX, negY, negZ }, + { negX, posY, posZ }, + { negX, posY, negZ }, + { negX, negY, posZ }, + { negX, negY, negZ } }; + + // Recursion: every iteration, convert the current model to a new + // model by breaking each triangle into 4 triangles. + for (int recursions = 0; recursions < 3; ++recursions) + { + std::vector new_triangles; + for (Triangle t : triangles) { + // Split each of three edges in half, then fixup the + // length of the midpoint to match m_lanternModelRadius. + // Record the index the new vertices will eventually have in vertices. + VertexIndex v01 { + m_lanternModelRadius * nvmath::normalize(t.vert0.vertex + t.vert1.vertex), + vertexCount++ }; + VertexIndex v12 { + m_lanternModelRadius * nvmath::normalize(t.vert1.vertex + t.vert2.vertex), + vertexCount++ }; + VertexIndex v02 { + m_lanternModelRadius * nvmath::normalize(t.vert0.vertex + t.vert2.vertex), + vertexCount++ }; + + // Old triangle becomes 4 new triangles. + new_triangles.push_back({t.vert0, v01, v02}); + new_triangles.push_back({t.vert1, v01, v12}); + new_triangles.push_back({t.vert2, v02, v12}); + new_triangles.push_back({v01, v02, v12}); + } + triangles = std::move(new_triangles); + } + + vertices.resize(vertexCount); + indices.clear(); + indices.reserve(triangles.size() * 3); + + // Write out the vertices to the vertices vector, and + // connect the tesselated triangles with indices in the indices vector. + for (Triangle t : triangles) + { + vertices[t.vert0.index] = t.vert0.vertex; + vertices[t.vert1.index] = t.vert1.vertex; + vertices[t.vert2.index] = t.vert2.vertex; + indices.push_back(t.vert0.index); + indices.push_back(t.vert1.index); + indices.push_back(t.vert2.index); + } +} + +// Create the BLAS storing triangles for the spherical lantern model. This fills in +// m_lanternVertexBuffer: packed VkBuffer of vec3 +// m_lanternIndexBuffer: packed VkBuffer of 32-bit indices +// m_lanternBlasInput: BLAS for spherical lantern +// +// NOTE: A more elegant way to do this is to use a procedular hit group instead, +// then, this BLAS can just be one AABB. I wanted to avoid introducing the new +// concept of intersection shaders here. +void HelloVulkan::createLanternModel() +{ + std::vector vertices; + std::vector indices; + fillLanternVerts(vertices, indices); + + // Upload vertex and index data to buffers. + auto usageFlags = vk::BufferUsageFlagBits::eAccelerationStructureBuildInputReadOnlyKHR + | vk::BufferUsageFlagBits::eShaderDeviceAddress; + auto memFlags = + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent; + + auto vertexBytes = vertices.size() * sizeof vertices[0]; + m_lanternVertexBuffer = m_alloc.createBuffer(vertexBytes, usageFlags, memFlags); + void* map = m_alloc.map(m_lanternVertexBuffer); + memcpy(map, vertices.data(), vertexBytes); + m_alloc.unmap(m_lanternVertexBuffer); + + auto indexBytes = indices.size() * sizeof indices[0]; + m_lanternIndexBuffer = m_alloc.createBuffer(indexBytes, usageFlags, memFlags); + map = m_alloc.map(m_lanternIndexBuffer); + memcpy(map, indices.data(), indexBytes); + m_alloc.unmap(m_lanternIndexBuffer); + + // 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(uint32_t(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); +} + +//-------------------------------------------------------------------------------------------------- +// +// 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); +} + +// 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(int 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 = uint32_t(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); +} + +//-------------------------------------------------------------------------------------------------- +// 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; + + // TLAS (binding = 0) + m_rtDescSetLayoutBind.addBinding(vkDSLB(0, vkDT::eAccelerationStructureKHR, 1, + vkSS::eRaygenKHR | vkSS::eClosestHitKHR)); + // Output image (binding = 1) + m_rtDescSetLayoutBind.addBinding( + vkDSLB(1, vkDT::eStorageImage, 1, vkSS::eRaygenKHR)); // Output image + + // Lantern buffer (binding = 2) + m_rtDescSetLayoutBind.addBinding( // + vkDSLB(2, vkDT::eStorageBuffer, 1, vkSS::eRaygenKHR | vkSS::eClosestHitKHR)); + assert(m_lanternCount > 0); + + m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); + m_rtDescSetLayout = m_rtDescSetLayoutBind.createLayout(m_device); + m_rtDescSet = m_device.allocateDescriptorSets({m_rtDescPool, 1, &m_rtDescSetLayout})[0]; + + vk::AccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure(); + vk::WriteDescriptorSetAccelerationStructureKHR descASInfo; + descASInfo.setAccelerationStructureCount(1); + descASInfo.setPAccelerationStructures(&tlas); + vk::DescriptorImageInfo imageInfo{ + {}, m_offscreenColor.descriptor.imageView, vk::ImageLayout::eGeneral}; + vk::DescriptorBufferInfo lanternBufferInfo{ + m_lanternIndirectBuffer.buffer, 0, m_lanternCount * sizeof(LanternIndirectEntry)}; + + std::vector writes; + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 0, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &lanternBufferInfo)); + m_device.updateDescriptorSets(static_cast(writes.size()), writes.data(), 0, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Writes the output image to the descriptor set +// - Required when changing resolution +// +void HelloVulkan::updateRtDescriptorSet() +{ + using vkDT = vk::DescriptorType; + + // (1) Output buffer + vk::DescriptorImageInfo imageInfo{ + {}, m_offscreenColor.descriptor.imageView, vk::ImageLayout::eGeneral}; + vk::WriteDescriptorSet wds{m_rtDescSet, 1, 0, 1, vkDT::eStorageImage, &imageInfo}; + m_device.updateDescriptorSets(wds, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Pipeline for the ray tracer: all shaders, raygen, chit, miss +// +// 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 ===================================================================================== +void HelloVulkan::createRtPipeline() +{ + std::vector paths = defaultSearchPaths; + + vk::ShaderModule raygenSM = + nvvk::createShaderModule(m_device, // + nvh::loadFile("shaders/raytrace.rgen.spv", true, paths, true)); + + // Miss shader 0 invoked when a primary ray doesn't hit geometry. Fills in clear color. + vk::ShaderModule missSM = + nvvk::createShaderModule(m_device, // + nvh::loadFile("shaders/raytrace.rmiss.spv", true, paths, true)); + + // Miss shader 1 is invoked when a shadow ray (for the main scene light) + // misses the geometry. It simply indicates that no occlusion has been found. + vk::ShaderModule shadowmissSM = nvvk::createShaderModule( + m_device, nvh::loadFile("shaders/raytraceShadow.rmiss.spv", true, paths, true)); + + // 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)); + + std::vector stages; + + // Raygen + vk::RayTracingShaderGroupCreateInfoKHR rg{vk::RayTracingShaderGroupTypeKHR::eGeneral, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; + stages.push_back({{}, vk::ShaderStageFlagBits::eRaygenKHR, raygenSM, "main"}); + rg.setGeneralShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(rg); + // Miss + vk::RayTracingShaderGroupCreateInfoKHR mg{vk::RayTracingShaderGroupTypeKHR::eGeneral, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, + VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; + stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, missSM, "main"}); + mg.setGeneralShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(mg); + + // Shadow Miss + stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, shadowmissSM, "main"}); + mg.setGeneralShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(mg); + + // Lantern Miss + stages.push_back({{}, vk::ShaderStageFlagBits::eMissKHR, lanternmissSM, "main"}); + mg.setGeneralShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(mg); + + // 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, chitSM, "main"}); + hg.setClosestHitShader(static_cast(stages.size() - 1)); + 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternChitSM, "main"}); + lanternHg.setClosestHitShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(lanternHg); + + // 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowObjChitSM, "main"}); + lanternShadowObjHg.setClosestHitShader(static_cast(stages.size() - 1)); + 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}; + stages.push_back({{}, vk::ShaderStageFlagBits::eClosestHitKHR, lanternShadowLanternChitSM, "main"}); + lanternShadowLanternHg.setClosestHitShader(static_cast(stages.size() - 1)); + m_rtShaderGroups.push_back(lanternShadowLanternHg); + + vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo; + + // Push constant: we want to be able to update constants used by the shaders + vk::PushConstantRange pushConstant{vk::ShaderStageFlagBits::eRaygenKHR + | vk::ShaderStageFlagBits::eClosestHitKHR + | vk::ShaderStageFlagBits::eMissKHR, + 0, sizeof(RtPushConstant)}; + pipelineLayoutCreateInfo.setPushConstantRangeCount(1); + pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstant); + + // Descriptor sets: one specific to ray tracing, and one shared with the rasterization pipeline + std::vector rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout}; + pipelineLayoutCreateInfo.setSetLayoutCount(static_cast(rtDescSetLayouts.size())); + pipelineLayoutCreateInfo.setPSetLayouts(rtDescSetLayouts.data()); + + m_rtPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo); + + // Assemble the shader stages and recursion depth info into the ray tracing pipeline + + vk::RayTracingPipelineCreateInfoKHR rayPipelineInfo; + rayPipelineInfo.setStageCount(static_cast(stages.size())); // Stages are shaders + rayPipelineInfo.setPStages(stages.data()); + + // In this case, m_rtShaderGroups.size() == 8: we have one raygen group, + // three miss shader groups, and four hit groups. + rayPipelineInfo.setGroupCount(static_cast( + m_rtShaderGroups.size())); + rayPipelineInfo.setPGroups(m_rtShaderGroups.data()); + + rayPipelineInfo.setMaxPipelineRayRecursionDepth(2); // Ray depth + rayPipelineInfo.setLayout(m_rtPipelineLayout); + m_rtPipeline = static_cast( + m_device.createRayTracingPipelineKHR({}, {}, rayPipelineInfo)); + + m_device.destroy(raygenSM); + m_device.destroy(missSM); + m_device.destroy(shadowmissSM); + m_device.destroy(lanternmissSM); + m_device.destroy(chitSM); + m_device.destroy(lanternChitSM); + m_device.destroy(lanternShadowObjChitSM); + m_device.destroy(lanternShadowLanternChitSM); +} + +//-------------------------------------------------------------------------------------------------- +// The Shader Binding Table (SBT) +// - getting all shader handles and write them in a SBT buffer +// - Besides exception, this could be always done like this +// See how the SBT buffer is used in run() +// +void HelloVulkan::createRtShaderBindingTable() +{ + auto groupCount = static_cast(m_rtShaderGroups.size()); + assert(groupCount == 8 && "Update Comment"); // 8 shaders: raygen, 3 miss, 4 chit + + uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier + // Compute the actual size needed per SBT entry (round-up to alignment needed). + uint32_t groupSizeAligned = + nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); + // Bytes needed for the SBT. + uint32_t sbtSize = groupCount * groupSizeAligned; + + // Fetch all the shader handles used in the pipeline. This is opaque data, + // so we store it in a vector of bytes. + std::vector shaderHandleStorage(sbtSize); + auto result = m_device.getRayTracingShaderGroupHandlesKHR(m_rtPipeline, 0, groupCount, sbtSize, + shaderHandleStorage.data()); + assert(result == vk::Result::eSuccess); + + // Allocate a buffer for storing the SBT. Give it a debug name for NSight. + m_rtSBTBuffer = m_alloc.createBuffer( + sbtSize, + vk::BufferUsageFlagBits::eTransferSrc | vk::BufferUsageFlagBits::eShaderDeviceAddress + | vk::BufferUsageFlagBits::eShaderBindingTableKHR, + vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + + // Map the SBT buffer and write in the handles. + void* mapped = m_alloc.map(m_rtSBTBuffer); + auto* pData = reinterpret_cast(mapped); + for(uint32_t g = 0; g < groupCount; g++) + { + memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); + pData += groupSizeAligned; + } + m_alloc.unmap(m_rtSBTBuffer); + m_alloc.finalizeAndReleaseStaging(); +} + + +//-------------------------------------------------------------------------------------------------- +// 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 = 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); +} + +// 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); +} + +//-------------------------------------------------------------------------------------------------- +// Ray Tracing the scene +// +// The raytracing is split into multiple passes: +// +// First pass fills in the initial values for every pixel in the output image. +// Illumination and shadow rays come from the main light. +// +// Subsequently, one lantern pass is run for each lantern in the scene. We run +// a compute shader to calculate a bounding scissor rectangle for each lantern's light +// effect. This is stored in m_lanternIndirectBuffer. Then an indirect trace rays command +// is run for every lantern within its scissor rectangle. The lanterns' light +// contribution is additively blended into the output image. +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 = int32_t(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"); + + // 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; + + cmdBuf.bindPipeline(vk::PipelineBindPoint::eRayTracingKHR, m_rtPipeline); + cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eRayTracingKHR, m_rtPipelineLayout, 0, + {m_rtDescSet, m_descSet}, {}); + cmdBuf.pushConstants(m_rtPipelineLayout, + vk::ShaderStageFlagBits::eRaygenKHR + | vk::ShaderStageFlagBits::eClosestHitKHR + | vk::ShaderStageFlagBits::eMissKHR, + 0, m_rtPushConstants); + + // Size of a program identifier + uint32_t groupSize = + nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); + uint32_t groupStride = groupSize; + vk::DeviceAddress sbtAddress = m_device.getBufferAddress({m_rtSBTBuffer.buffer}); + + 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); + + // Lantern passes, ensure previous pass completed, then add light contribution from each lantern. + for (int i = 0; i < static_cast(m_lanternCount); ++i) + { + // 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}); + + // 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)); + } + + m_debug.endLabel(cmdBuf); +} diff --git a/ray_tracing_indirect_scissor/hello_vulkan.h b/ray_tracing_indirect_scissor/hello_vulkan.h new file mode 100644 index 0000000..0a9a750 --- /dev/null +++ b/ray_tracing_indirect_scissor/hello_vulkan.h @@ -0,0 +1,278 @@ +/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +#pragma once +#include + +#define NVVK_ALLOC_DEDICATED +#include "nvvk/allocator_vk.hpp" +#include "nvvk/appbase_vkpp.hpp" +#include "nvvk/debug_util_vk.hpp" +#include "nvvk/descriptorsets_vk.hpp" + +// #VKRay +#include "nvvk/raytraceKHR_vk.hpp" + +//-------------------------------------------------------------------------------------------------- +// Simple rasterizer of OBJ objects +// - Each OBJ loaded are stored in an `ObjModel` and referenced by a `ObjInstance` +// - It is possible to have many `ObjInstance` referencing the same `ObjModel` +// - Rendering is done in an offscreen framebuffer +// - The image of the framebuffer is displayed in post-process in a full-screen quad +// +class HelloVulkan : public nvvk::AppBase +{ +public: + void setup(const vk::Instance& instance, + const vk::Device& device, + const vk::PhysicalDevice& physicalDevice, + uint32_t queueFamily) override; + void createDescriptorSetLayout(); + void createGraphicsPipeline(); + void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); + void addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius); + void updateDescriptorSet(); + void createUniformBuffer(); + void createSceneDescriptionBuffer(); + void createTextureImages(const vk::CommandBuffer& cmdBuf, + const std::vector& textures); + + 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); + } + + void updateUniformBuffer(const vk::CommandBuffer&); + void onResize(int /*w*/, int /*h*/) override; + void destroyResources(); + void rasterize(const vk::CommandBuffer& cmdBuff); + + // The OBJ model + struct ObjModel + { + uint32_t nbIndices{0}; + uint32_t nbVertices{0}; + nvvk::Buffer vertexBuffer; // Device buffer of all 'Vertex' + nvvk::Buffer indexBuffer; // Device buffer of the indices forming triangles + nvvk::Buffer matColorBuffer; // Device buffer of array of 'Wavefront material' + nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' + }; + + // Instance of the OBJ + struct ObjInstance + { + uint32_t objIndex{0}; // Reference to the `m_objModel` + uint32_t txtOffset{0}; // Offset in `m_textures` + nvmath::mat4f transform{1}; // Position of the instance + nvmath::mat4f transformIT{1}; // Inverse transpose + }; + + // Information pushed at each draw call + struct ObjPushConstant + { + nvmath::vec3f lightPosition{10.f, 15.f, 8.f}; + int instanceId{0}; // To retrieve the transformation matrix + float lightIntensity{40.f}; + int lightType{0}; // 0: point, 1: infinite + }; + ObjPushConstant m_pushConstant; + + // 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. + }; + + // 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; + }; + + // Array of objects and instances in the scene. Not modifiable after acceleration structure build. + std::vector m_objModel; + std::vector m_objInstance; + + // Array of lanterns in scene. Not modifiable after acceleration structure build. + std::vector m_lanterns; + + // Graphic pipeline + vk::PipelineLayout m_pipelineLayout; + vk::Pipeline m_graphicsPipeline; + nvvk::DescriptorSetBindings m_descSetLayoutBind; + vk::DescriptorPool m_descPool; + vk::DescriptorSetLayout m_descSetLayout; + vk::DescriptorSet m_descSet; + + nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices + nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + std::vector m_textures; // vector of all textures of the scene + + nvvk::AllocatorDedicated m_alloc; // Allocator for buffer, images, acceleration structures + nvvk::DebugUtil m_debug; // Utility to name objects + + // #Post + void createOffscreenRender(); + void createPostPipeline(); + void createPostDescriptor(); + void updatePostDescriptorSet(); + void drawPost(vk::CommandBuffer cmdBuf); + + nvvk::DescriptorSetBindings m_postDescSetLayoutBind; + vk::DescriptorPool m_postDescPool; + vk::DescriptorSetLayout m_postDescSetLayout; + vk::DescriptorSet m_postDescSet; + vk::Pipeline m_postPipeline; + vk::PipelineLayout m_postPipelineLayout; + vk::RenderPass m_offscreenRenderPass; + vk::Framebuffer m_offscreenFramebuffer; + nvvk::Texture m_offscreenColor; + vk::Format m_offscreenColorFormat{vk::Format::eR32G32B32A32Sfloat}; + nvvk::Texture m_offscreenDepth; + vk::Format m_offscreenDepthFormat{vk::Format::eD32Sfloat}; + + // #VKRay + void initRayTracing(); + nvvk::RaytracingBuilderKHR::BlasInput objectToVkGeometryKHR(const ObjModel& model); + +private: + void fillLanternVerts(std::vector& vertices, std::vector& indices); + void createLanternModel(); + +public: + void createBottomLevelAS(); + void createTopLevelAS(); + void createRtDescriptorSet(); + void updateRtDescriptorSet(); + void createRtPipeline(); + void createLanternIndirectDescriptorSet(); + void createLanternIndirectCompPipeline(); + void createRtShaderBindingTable(); + void createLanternIndirectBuffer(); + + void raytrace(const vk::CommandBuffer& cmdBuf, const nvmath::vec4f& clearColor); + + // 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; + + vk::PhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties; + nvvk::RaytracingBuilderKHR m_rtBuilder; + nvvk::DescriptorSetBindings m_rtDescSetLayoutBind; + vk::DescriptorPool m_rtDescPool; + vk::DescriptorSetLayout m_rtDescSetLayout; + vk::DescriptorSet m_rtDescSet; + std::vector m_rtShaderGroups; + vk::PipelineLayout m_rtPipelineLayout; + vk::Pipeline m_rtPipeline; + nvvk::DescriptorSetBindings m_lanternIndirectDescSetLayoutBind; + vk::DescriptorPool m_lanternIndirectDescPool; + vk::DescriptorSetLayout m_lanternIndirectDescSetLayout; + vk::DescriptorSet m_lanternIndirectDescSet; + vk::PipelineLayout m_lanternIndirectCompPipelineLayout; + vk::Pipeline m_lanternIndirectCompPipeline; + nvvk::Buffer m_rtSBTBuffer; + + // 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. + + // 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; + + // Copied to RtPushConstant::lanternDebug. If true, + // make lantern produce constant light regardless of distance + // so that I can see the screen rectangle coverage. + bool m_lanternDebug = false; + + + // 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; +}; diff --git a/ray_tracing_indirect_scissor/main.cpp b/ray_tracing_indirect_scissor/main.cpp new file mode 100644 index 0000000..814701b --- /dev/null +++ b/ray_tracing_indirect_scissor/main.cpp @@ -0,0 +1,324 @@ +/* Copyright (c) 2014-2018, NVIDIA CORPORATION. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of NVIDIA CORPORATION nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// ImGui - standalone example application for Glfw + Vulkan, using programmable +// pipeline If you are new to ImGui, see examples/README.txt and documentation +// at the top of imgui.cpp. + +#include +#include +VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE + +#include "imgui.h" +#include "imgui_impl_glfw.h" + +#include "hello_vulkan.h" +#include "imgui_camera_widget.h" +#include "nvh/cameramanipulator.hpp" +#include "nvh/fileoperations.hpp" +#include "nvpsystem.hpp" +#include "nvvk/appbase_vkpp.hpp" +#include "nvvk/commands_vk.hpp" +#include "nvvk/context_vk.hpp" + + +////////////////////////////////////////////////////////////////////////// +#define UNUSED(x) (void)(x) +////////////////////////////////////////////////////////////////////////// + +// Default search path for shaders +std::vector defaultSearchPaths; + +// GLFW Callback functions +static void onErrorCallback(int error, const char* description) +{ + fprintf(stderr, "GLFW Error %d: %s\n", error, description); +} + +// Extra UI +void renderUI(HelloVulkan& helloVk) +{ + ImGuiH::CameraWidget(); + if(ImGui::CollapsingHeader("Light")) + { + ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::SameLine(); + ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + + ImGui::SliderFloat3("Position", &helloVk.m_pushConstant.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pushConstant.lightIntensity, 0.f, 150.f); + ImGui::Checkbox("Lantern Debug", &helloVk.m_lanternDebug); + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +static int const SAMPLE_WIDTH = 1280; +static int const SAMPLE_HEIGHT = 720; + +//-------------------------------------------------------------------------------------------------- +// Application Entry +// +int main(int argc, char** argv) +{ + UNUSED(argc); + + // Setup GLFW window + glfwSetErrorCallback(onErrorCallback); + if(!glfwInit()) + { + return 1; + } + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = + glfwCreateWindow(SAMPLE_WIDTH, SAMPLE_HEIGHT, PROJECT_NAME, nullptr, nullptr); + + // Setup camera + CameraManip.setWindowSize(SAMPLE_WIDTH, SAMPLE_HEIGHT); + CameraManip.setLookat(nvmath::vec3f(5, 4, -4), nvmath::vec3f(0, 1, 0), nvmath::vec3f(0, 1, 0)); + + // Setup Vulkan + if(!glfwVulkanSupported()) + { + printf("GLFW: Vulkan Not Supported\n"); + return 1; + } + + // setup some basic things for the sample, logging file for example + NVPSystem system(argv[0], PROJECT_NAME); + + // Search path for shaders and other media + defaultSearchPaths = { + PROJECT_ABSDIRECTORY, + PROJECT_ABSDIRECTORY "..", + NVPSystem::exePath(), + NVPSystem::exePath() + "..", + NVPSystem::exePath() + std::string(PROJECT_NAME), + }; + + // Requesting Vulkan extensions and layers + nvvk::ContextCreateInfo contextInfo(true); + contextInfo.setVersion(1, 2); + contextInfo.addInstanceLayer("VK_LAYER_LUNARG_monitor", true); + contextInfo.addInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, true); + contextInfo.addInstanceExtension(VK_KHR_SURFACE_EXTENSION_NAME); +#ifdef WIN32 + contextInfo.addInstanceExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#else + contextInfo.addInstanceExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + contextInfo.addInstanceExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME); +#endif + contextInfo.addInstanceExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); + // #VKRay: Activate the ray tracing extension + vk::PhysicalDeviceAccelerationStructureFeaturesKHR accelFeature; + contextInfo.addDeviceExtension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, false, + &accelFeature); + vk::PhysicalDeviceRayTracingPipelineFeaturesKHR rtPipelineFeature; + contextInfo.addDeviceExtension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, false, + &rtPipelineFeature); + contextInfo.addDeviceExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + + + // Creating Vulkan base application + nvvk::Context vkctx{}; + vkctx.initInstance(contextInfo); + // Find all compatible devices + auto compatibleDevices = vkctx.getCompatibleDevices(contextInfo); + assert(!compatibleDevices.empty()); + // Use a compatible device + vkctx.initDevice(compatibleDevices[0], contextInfo); + + + // Create example + HelloVulkan helloVk; + + // Window need to be opened to get the surface on which to draw + const vk::SurfaceKHR surface = helloVk.getVkSurface(vkctx.m_instance, window); + vkctx.setGCTQueueWithPresent(surface); + + helloVk.setup(vkctx.m_instance, vkctx.m_device, vkctx.m_physicalDevice, + vkctx.m_queueGCT.familyIndex); + helloVk.createSwapchain(surface, SAMPLE_WIDTH, SAMPLE_HEIGHT); + helloVk.createDepthBuffer(); + helloVk.createRenderPass(); + helloVk.createFrameBuffers(); + + // Setup Imgui + helloVk.initGUI(0); // Using sub-pass 0 + + // 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); + + helloVk.createOffscreenRender(); + helloVk.createDescriptorSetLayout(); + helloVk.createGraphicsPipeline(); + helloVk.createUniformBuffer(); + helloVk.createSceneDescriptionBuffer(); + helloVk.updateDescriptorSet(); + + // #VKRay + helloVk.initRayTracing(); + helloVk.createBottomLevelAS(); + helloVk.createTopLevelAS(); + helloVk.createLanternIndirectBuffer(); + helloVk.createRtDescriptorSet(); + helloVk.createRtPipeline(); + helloVk.createLanternIndirectDescriptorSet(); + helloVk.createLanternIndirectCompPipeline(); + helloVk.createRtShaderBindingTable(); + + helloVk.createPostDescriptor(); + helloVk.createPostPipeline(); + helloVk.updatePostDescriptorSet(); + + + nvmath::vec4f clearColor = nvmath::vec4f(1, 1, 1, 1.00f); + bool useRaytracer = true; + + + helloVk.setupGlfwCallbacks(window); + ImGui_ImplGlfw_InitForVulkan(window, true); + + // Main loop + while(!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + if(helloVk.isMinimized()) + continue; + + // Start the Dear ImGui frame + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Show UI window. + if(helloVk.showGui()) + { + ImGuiH::Panel::Begin(); + ImGui::ColorEdit3("Clear color", reinterpret_cast(&clearColor)); + ImGui::Checkbox("Ray Tracer mode", &useRaytracer); // Switch between raster and ray tracing + + renderUI(helloVk); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", + 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + + ImGuiH::Control::Info("", "", "(F10) Toggle Pane", ImGuiH::Control::Flags::Disabled); + ImGuiH::Panel::End(); + } + + // Start rendering the scene + helloVk.prepareFrame(); + + // Start command buffer of this frame + auto curFrame = helloVk.getCurFrame(); + const vk::CommandBuffer& cmdBuf = helloVk.getCommandBuffers()[curFrame]; + + cmdBuf.begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + + // Updating camera buffer + helloVk.updateUniformBuffer(cmdBuf); + + // Clearing screen + vk::ClearValue clearValues[2]; + clearValues[0].setColor( + std::array({clearColor[0], clearColor[1], clearColor[2], clearColor[3]})); + clearValues[1].setDepthStencil({1.0f, 0}); + + // Offscreen render pass + { + vk::RenderPassBeginInfo offscreenRenderPassBeginInfo; + offscreenRenderPassBeginInfo.setClearValueCount(2); + offscreenRenderPassBeginInfo.setPClearValues(clearValues); + offscreenRenderPassBeginInfo.setRenderPass(helloVk.m_offscreenRenderPass); + offscreenRenderPassBeginInfo.setFramebuffer(helloVk.m_offscreenFramebuffer); + offscreenRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()}); + + // Rendering Scene + if(useRaytracer) + { + helloVk.raytrace(cmdBuf, clearColor); + } + else + { + cmdBuf.beginRenderPass(offscreenRenderPassBeginInfo, vk::SubpassContents::eInline); + helloVk.rasterize(cmdBuf); + cmdBuf.endRenderPass(); + } + } + + // 2nd rendering pass: tone mapper, UI + { + vk::RenderPassBeginInfo postRenderPassBeginInfo; + postRenderPassBeginInfo.setClearValueCount(2); + postRenderPassBeginInfo.setPClearValues(clearValues); + postRenderPassBeginInfo.setRenderPass(helloVk.getRenderPass()); + postRenderPassBeginInfo.setFramebuffer(helloVk.getFramebuffers()[curFrame]); + postRenderPassBeginInfo.setRenderArea({{}, helloVk.getSize()}); + + cmdBuf.beginRenderPass(postRenderPassBeginInfo, vk::SubpassContents::eInline); + // Rendering tonemapper + helloVk.drawPost(cmdBuf); + // Rendering UI + ImGui::Render(); + ImGui::RenderDrawDataVK(cmdBuf, ImGui::GetDrawData()); + cmdBuf.endRenderPass(); + } + + // Submit for display + cmdBuf.end(); + helloVk.submitFrame(); + } + + // Cleanup + helloVk.getDevice().waitIdle(); + helloVk.destroyResources(); + helloVk.destroy(); + + vkctx.deinit(); + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} diff --git a/ray_tracing_indirect_scissor/shaders/LanternIndirectEntry.glsl b/ray_tracing_indirect_scissor/shaders/LanternIndirectEntry.glsl new file mode 100644 index 0000000..a711cc3 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/LanternIndirectEntry.glsl @@ -0,0 +1,18 @@ +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; +}; diff --git a/ray_tracing_indirect_scissor/shaders/frag_shader.frag b/ray_tracing_indirect_scissor/shaders/frag_shader.frag new file mode 100644 index 0000000..d1c55f7 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/frag_shader.frag @@ -0,0 +1,79 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_scalar_block_layout : enable + +#include "wavefront.glsl" + + +layout(push_constant) uniform shaderInformation +{ + vec3 lightPosition; + uint instanceId; + float lightIntensity; + int lightType; +} +pushC; + +// clang-format off +// Incoming +//layout(location = 0) flat in int matIndex; +layout(location = 1) in vec2 fragTexCoord; +layout(location = 2) in vec3 fragNormal; +layout(location = 3) in vec3 viewDir; +layout(location = 4) in vec3 worldPos; +// Outgoing +layout(location = 0) out vec4 outColor; +// Buffers +layout(binding = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[]; +layout(binding = 2, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc; +layout(binding = 3) uniform sampler2D[] textureSamplers; +layout(binding = 4, scalar) buffer MatIndex { int i[]; } matIdx[]; + +// clang-format on + + +void main() +{ + // Object of this instance + int objId = scnDesc.i[pushC.instanceId].objId; + + // Material of the object + int matIndex = matIdx[nonuniformEXT(objId)].i[gl_PrimitiveID]; + WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIndex]; + + vec3 N = normalize(fragNormal); + + // Vector toward light + vec3 L; + float lightIntensity = pushC.lightIntensity; + if(pushC.lightType == 0) + { + vec3 lDir = pushC.lightPosition - worldPos; + float d = length(lDir); + lightIntensity = pushC.lightIntensity / (d * d); + L = normalize(lDir); + } + else + { + L = normalize(pushC.lightPosition - vec3(0)); + } + + + // Diffuse + vec3 diffuse = computeDiffuse(mat, L, N); + if(mat.textureId >= 0) + { + int txtOffset = scnDesc.i[pushC.instanceId].txtOffset; + uint txtId = txtOffset + mat.textureId; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + diffuse *= diffuseTxt; + } + + // Specular + vec3 specular = computeSpecular(mat, viewDir, L, N); + + // Result + outColor = vec4(lightIntensity * (diffuse + specular), 1); +} diff --git a/ray_tracing_indirect_scissor/shaders/lantern.rchit b/ray_tracing_indirect_scissor/shaders/lantern.rchit new file mode 100644 index 0000000..cd80a0a --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/lantern.rchit @@ -0,0 +1,23 @@ +#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; +} diff --git a/ray_tracing_indirect_scissor/shaders/lanternIndirect.comp b/ray_tracing_indirect_scissor/shaders/lanternIndirect.comp new file mode 100644 index 0000000..c606b0f --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/lanternIndirect.comp @@ -0,0 +1,163 @@ +#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); + } +} + +// Functions below modified from the paper. +float square(float a) { return a*a; } + +void getBoundsForAxis( + in bool xAxis, + in vec3 center, + in float radius, + in float nearZ, + in mat4 projMatrix, + out vec3 U, + out vec3 L) { + bool trivialAccept = (center.z + radius) < nearZ; // Entirely in back of nearPlane (Trivial Accept) + vec3 a = xAxis ? vec3(1, 0, 0) : vec3(0, 1, 0); + + // given in coordinates (a,z), where a is in the direction of the vector a, and z is in the standard z direction + vec2 projectedCenter = vec2(dot(a, center), center.z); + vec2 bounds_az[2]; + float tSquared = dot(projectedCenter, projectedCenter) - square(radius); + float t, cLength, costheta = 0, sintheta = 0; + + if(tSquared > 0) { // Camera is outside sphere + // Distance to the tangent points of the sphere (points where a vector from the camera are tangent to the sphere) (calculated a-z space) + t = sqrt(tSquared); + cLength = length(projectedCenter); + + // Theta is the angle between the vector from the camera to the center of the sphere and the vectors from the camera to the tangent points + costheta = t / cLength; + sintheta = radius / cLength; + } + float sqrtPart = 0.0f; + if(!trivialAccept) sqrtPart = sqrt(square(radius) - square(nearZ - projectedCenter.y)); + + for(int i = 0; i < 2; ++i){ + if(tSquared > 0) { + float x = costheta * projectedCenter.x + -sintheta * projectedCenter.y; + float y = sintheta * projectedCenter.x + costheta * projectedCenter.y; + bounds_az[i] = costheta * vec2(x, y); + } + + if(!trivialAccept && (tSquared <= 0 || bounds_az[i].y > nearZ)) { + bounds_az[i].x = projectedCenter.x + sqrtPart; + bounds_az[i].y = nearZ; + } + sintheta *= -1; // negate theta for B + sqrtPart *= -1; // negate sqrtPart for B + } + U = bounds_az[0].x * a; + U.z = bounds_az[0].y; + L = bounds_az[1].x * a; + L.z = bounds_az[1].y; +} + +/** 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) { + vec3 maxXHomogenous, minXHomogenous, maxYHomogenous, minYHomogenous; + getBoundsForAxis(true, center, radius, nearZ, projMatrix, maxXHomogenous, minXHomogenous); + getBoundsForAxis(false, center, radius, nearZ, projMatrix, maxYHomogenous, minYHomogenous); + + vec4 projRow0 = vec4(projMatrix[0][0], projMatrix[1][0], projMatrix[2][0], projMatrix[3][0]); + vec4 projRow1 = vec4(projMatrix[0][1], projMatrix[1][1], projMatrix[2][1], projMatrix[3][1]); + vec4 projRow3 = vec4(projMatrix[0][3], projMatrix[1][3], projMatrix[2][3], projMatrix[3][3]); + + // We only need one coordinate for each point, so we save computation by only calculating x(or y) and w + float maxX_w = dot(vec4(maxXHomogenous, 1.0f), projRow3); + float minX_w = dot(vec4(minXHomogenous, 1.0f), projRow3); + float maxY_w = dot(vec4(maxYHomogenous, 1.0f), projRow3); + float minY_w = dot(vec4(minYHomogenous, 1.0f), projRow3); + + float maxX = dot(vec4(maxXHomogenous, 1.0f), projRow0) / maxX_w; + float minX = dot(vec4(minXHomogenous, 1.0f), projRow0) / minX_w; + float maxY = dot(vec4(maxYHomogenous, 1.0f), projRow1) / maxY_w; + float minY = dot(vec4(minYHomogenous, 1.0f), projRow1) / minY_w; + + // Paper minX, etc. names are misleading, not necessarily min. Fix here. + ndc_low = vec2(min(minX, maxX), min(minY, maxY)); + ndc_high = vec2(max(minX, maxX), max(minY, maxY)); +} + +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); +} diff --git a/ray_tracing_indirect_scissor/shaders/lanternShadow.rmiss b/ray_tracing_indirect_scissor/shaders/lanternShadow.rmiss new file mode 100644 index 0000000..f230893 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/lanternShadow.rmiss @@ -0,0 +1,12 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +// 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; +} diff --git a/ray_tracing_indirect_scissor/shaders/lanternShadowLantern.rchit b/ray_tracing_indirect_scissor/shaders/lanternShadowLantern.rchit new file mode 100644 index 0000000..62b9afc --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/lanternShadowLantern.rchit @@ -0,0 +1,18 @@ +#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; +} diff --git a/ray_tracing_indirect_scissor/shaders/lanternShadowObj.rchit b/ray_tracing_indirect_scissor/shaders/lanternShadowObj.rchit new file mode 100644 index 0000000..9ce2b22 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/lanternShadowObj.rchit @@ -0,0 +1,18 @@ +#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; +} diff --git a/ray_tracing_indirect_scissor/shaders/passthrough.vert b/ray_tracing_indirect_scissor/shaders/passthrough.vert new file mode 100644 index 0000000..3e15d82 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/passthrough.vert @@ -0,0 +1,15 @@ +#version 450 +layout (location = 0) out vec2 outUV; + + +out gl_PerVertex +{ + vec4 gl_Position; +}; + + +void main() +{ + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 1.0f, 1.0f); +} diff --git a/ray_tracing_indirect_scissor/shaders/post.frag b/ray_tracing_indirect_scissor/shaders/post.frag new file mode 100644 index 0000000..b8f30f1 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/post.frag @@ -0,0 +1,18 @@ +#version 450 +layout(location = 0) in vec2 outUV; +layout(location = 0) out vec4 fragColor; + +layout(set = 0, binding = 0) uniform sampler2D noisyTxt; + +layout(push_constant) uniform shaderInformation +{ + float aspectRatio; +} +pushc; + +void main() +{ + vec2 uv = outUV; + float gamma = 1. / 2.2; + fragColor = pow(texture(noisyTxt, uv).rgba, vec4(gamma)); +} diff --git a/ray_tracing_indirect_scissor/shaders/raycommon.glsl b/ray_tracing_indirect_scissor/shaders/raycommon.glsl new file mode 100644 index 0000000..4ce2b6d --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/raycommon.glsl @@ -0,0 +1,20 @@ +#include "LanternIndirectEntry.glsl" + +struct hitPayload +{ + vec3 hitValue; + bool additiveBlending; +}; + +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; \ No newline at end of file diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rchit b/ray_tracing_indirect_scissor/shaders/raytrace.rchit new file mode 100644 index 0000000..43902de --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/raytrace.rchit @@ -0,0 +1,175 @@ +#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" +#include "wavefront.glsl" + +hitAttributeEXT vec2 attribs; + +// clang-format off +layout(location = 0) rayPayloadInEXT hitPayload prd; +layout(location = 1) rayPayloadEXT bool isShadowed; +layout(location = 2) rayPayloadEXT int hitLanternInstance; + +layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; +layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns; + +layout(binding = 1, set = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[]; +layout(binding = 2, set = 1, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc; +layout(binding = 3, set = 1) uniform sampler2D textureSamplers[]; +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[]; + +// clang-format on + +void main() +{ + // Object of this instance + uint objId = scnDesc.i[gl_InstanceCustomIndexEXT].objId; + + // Indices of the triangle + ivec3 ind = ivec3(indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 0], // + indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 1], // + indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 2]); // + // Vertex of the triangle + Vertex v0 = vertices[nonuniformEXT(objId)].v[ind.x]; + Vertex v1 = vertices[nonuniformEXT(objId)].v[ind.y]; + Vertex v2 = vertices[nonuniformEXT(objId)].v[ind.z]; + + const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); + + // Computing the normal at hit position + vec3 normal = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + // Transforming the normal to world space + normal = normalize(vec3(scnDesc.i[gl_InstanceCustomIndexEXT].transfoIT * vec4(normal, 0.0))); + + + // Computing the coordinates of the hit position + vec3 worldPos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + // Transforming the position to world space + worldPos = vec3(scnDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + + // Vector toward the light + vec3 L; + vec3 colorIntensity = vec3(pushC.lightIntensity); + float lightDistance = 100000.0; + + // 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); + } + // 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)); + } + + // Material of the object + int matIdx = matIndex[nonuniformEXT(objId)].i[gl_PrimitiveID]; + WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIdx]; + + + // Diffuse + vec3 diffuse = computeDiffuse(mat, L, normal); + if(mat.textureId >= 0) + { + uint txtId = mat.textureId + scnDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + vec2 texCoord = + v0.texCoord * barycentrics.x + v1.texCoord * barycentrics.y + v2.texCoord * barycentrics.z; + diffuse *= texture(textureSamplers[nonuniformEXT(txtId)], texCoord).xyz; + } + + vec3 specular = vec3(0); + float attenuation = 1; + + // Tracing shadow ray only if the light is visible from the surface + if(dot(normal, L) > 0) + { + float tMin = 0.001; + float tMax = lightDistance; + vec3 origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; + vec3 rayDir = L; + + // 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) + ); + } + // 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); + } + } + + if(isShadowed) + { + attenuation = 0.1; + } + else + { + // Specular + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + } + } + + prd.hitValue = colorIntensity * (attenuation * (diffuse + specular)); + prd.additiveBlending = true; +} diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rgen b/ray_tracing_indirect_scissor/shaders/raytrace.rgen new file mode 100644 index 0000000..645544a --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/raytrace.rgen @@ -0,0 +1,71 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#include "raycommon.glsl" + +layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; +layout(binding = 1, set = 0, rgba32f) uniform image2D image; + +layout(location = 0) rayPayloadEXT hitPayload prd; + +layout(binding = 0, set = 1) uniform CameraProperties +{ + mat4 view; + mat4 proj; + mat4 viewInverse; + mat4 projInverse; +} +cam; + +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; + + vec4 origin = cam.viewInverse * vec4(0, 0, 0, 1); + vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0); + + uint rayFlags = gl_RayFlagsOpaqueEXT; + float tMin = 0.001; + float tMax = 10000.0; + + // Lanterns (self-illuminating) and miss shader (constant background color) + // do not use additive blending. Only normal OBJ geometry is additive, + // OBJ closest hit sets this to true. + prd.additiveBlending = false; + + traceRayEXT(topLevelAS, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + origin.xyz, // ray origin + tMin, // ray min range + direction.xyz, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + + // 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)); +} diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rmiss b/ray_tracing_indirect_scissor/shaders/raytrace.rmiss new file mode 100644 index 0000000..3202aa3 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/raytrace.rmiss @@ -0,0 +1,12 @@ +#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; +} diff --git a/ray_tracing_indirect_scissor/shaders/raytraceShadow.rmiss b/ray_tracing_indirect_scissor/shaders/raytraceShadow.rmiss new file mode 100644 index 0000000..57be266 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/raytraceShadow.rmiss @@ -0,0 +1,9 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 1) rayPayloadInEXT bool isShadowed; + +void main() +{ + isShadowed = false; +} diff --git a/ray_tracing_indirect_scissor/shaders/vert_shader.vert b/ray_tracing_indirect_scissor/shaders/vert_shader.vert new file mode 100644 index 0000000..e358821 --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/vert_shader.vert @@ -0,0 +1,61 @@ +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_GOOGLE_include_directive : enable + +#include "wavefront.glsl" + +// clang-format off +layout(binding = 2, set = 0, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc; +// clang-format on + +layout(binding = 0) uniform UniformBufferObject +{ + mat4 view; + mat4 proj; + mat4 viewI; +} +ubo; + +layout(push_constant) uniform shaderInformation +{ + vec3 lightPosition; + uint instanceId; + float lightIntensity; + int lightType; +} +pushC; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; +layout(location = 3) in vec2 inTexCoord; + + +//layout(location = 0) flat out int matIndex; +layout(location = 1) out vec2 fragTexCoord; +layout(location = 2) out vec3 fragNormal; +layout(location = 3) out vec3 viewDir; +layout(location = 4) out vec3 worldPos; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + + +void main() +{ + mat4 objMatrix = scnDesc.i[pushC.instanceId].transfo; + mat4 objMatrixIT = scnDesc.i[pushC.instanceId].transfoIT; + + vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + + worldPos = vec3(objMatrix * vec4(inPosition, 1.0)); + viewDir = vec3(worldPos - origin); + fragTexCoord = inTexCoord; + fragNormal = vec3(objMatrixIT * vec4(inNormal, 0.0)); + // matIndex = inMatID; + + gl_Position = ubo.proj * ubo.view * vec4(worldPos, 1.0); +} diff --git a/ray_tracing_indirect_scissor/shaders/wavefront.glsl b/ray_tracing_indirect_scissor/shaders/wavefront.glsl new file mode 100644 index 0000000..d0810fb --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/wavefront.glsl @@ -0,0 +1,58 @@ +struct Vertex +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial +{ + vec3 ambient; + vec3 diffuse; + vec3 specular; + vec3 transmittance; + vec3 emission; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + int illum; // illumination model (see http://www.fileformat.info/format/material/) + int textureId; +}; + +struct sceneDesc +{ + int objId; + int txtOffset; + mat4 transfo; + mat4 transfoIT; +}; + + +vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) +{ + // Lambertian + float dotNL = max(dot(normal, lightDir), 0.0); + vec3 c = mat.diffuse * dotNL; + if(mat.illum >= 1) + c += mat.ambient; + return c; +} + +vec3 computeSpecular(WaveFrontMaterial mat, vec3 viewDir, vec3 lightDir, vec3 normal) +{ + if(mat.illum < 2) + return vec3(0); + + // Compute specular only if not in shadow + const float kPi = 3.14159265; + const float kShininess = max(mat.shininess, 4.0); + + // Specular + const float kEnergyConservation = (2.0 + kShininess) / (2.0 * kPi); + vec3 V = normalize(-viewDir); + vec3 R = reflect(-lightDir, normal); + float specular = kEnergyConservation * pow(max(dot(V, R), 0.0), kShininess); + + return vec3(mat.specular * specular); +}