From d90ce79135336874384fce81b0e439ca6c64ddca Mon Sep 17 00:00:00 2001 From: mklefrancois Date: Tue, 7 Sep 2021 09:42:21 +0200 Subject: [PATCH] Refactoring --- .gitignore | 4 +- CMakeLists.txt | 1 + docs/vkrt_tuto_indirect_scissor.md.html | 97 +- docs/vkrt_tutorial.md.html | 918 +++++++-------- media/scenes/cube.obj | 4 - media/scenes/cube_modif.obj | 41 + media/scenes/cube_multi.obj | 4 - media/scenes/sphere.obj | 4 - ray_tracing__advance/hello_vulkan.cpp | 142 +-- ray_tracing__advance/hello_vulkan.h | 30 +- ray_tracing__advance/main.cpp | 45 +- ray_tracing__advance/obj.hpp | 24 +- ray_tracing__advance/raytrace.cpp | 48 +- ray_tracing__advance/raytrace.hpp | 17 +- ray_tracing__advance/shaders/frag_shader.frag | 61 +- ray_tracing__advance/shaders/host_device.h | 126 ++ ray_tracing__advance/shaders/light_inf.rcall | 15 +- .../shaders/light_point.rcall | 18 +- ray_tracing__advance/shaders/light_spot.rcall | 23 +- ray_tracing__advance/shaders/raytrace.rahit | 4 +- ray_tracing__advance/shaders/raytrace.rchit | 52 +- ray_tracing__advance/shaders/raytrace.rgen | 47 +- ray_tracing__advance/shaders/raytrace.rint | 3 +- ray_tracing__advance/shaders/raytrace.rmiss | 9 +- ray_tracing__advance/shaders/raytrace2.rahit | 6 +- ray_tracing__advance/shaders/raytrace2.rchit | 20 +- ray_tracing__advance/shaders/vert_shader.vert | 59 +- ray_tracing__advance/shaders/wavefront.glsl | 36 +- ray_tracing__before/README.md | 142 ++- ray_tracing__before/hello_vulkan.cpp | 113 +- ray_tracing__before/hello_vulkan.h | 40 +- ray_tracing__before/images/base_pipeline.png | Bin 0 -> 16631 bytes .../images/vk_ray_tracing__before.png | Bin 0 -> 29124 bytes ray_tracing__before/main.cpp | 10 +- ray_tracing__before/shaders/frag_shader.frag | 49 +- ray_tracing__before/shaders/host_device.h | 117 ++ ray_tracing__before/shaders/vert_shader.vert | 56 +- ray_tracing__before/shaders/wavefront.glsl | 36 +- ray_tracing__simple/hello_vulkan.cpp | 169 ++- ray_tracing__simple/hello_vulkan.h | 49 +- ray_tracing__simple/main.cpp | 10 +- ray_tracing__simple/shaders/frag_shader.frag | 46 +- ray_tracing__simple/shaders/host_device.h | 117 ++ ray_tracing__simple/shaders/raytrace.rchit | 52 +- ray_tracing__simple/shaders/raytrace.rgen | 28 +- ray_tracing__simple/shaders/raytrace.rmiss | 9 +- ray_tracing__simple/shaders/vert_shader.vert | 56 +- ray_tracing__simple/shaders/wavefront.glsl | 36 +- ray_tracing_advanced_compilation/README.md | 48 +- .../hello_vulkan.cpp | 166 ++- .../hello_vulkan.h | 54 +- ray_tracing_advanced_compilation/main.cpp | 16 +- .../shaders/frag_shader.frag | 49 +- .../shaders/host_device.h | 118 ++ .../shaders/raytrace.rchit | 52 +- .../shaders/raytrace.rgen | 38 +- .../shaders/raytrace.rmiss | 9 +- .../shaders/vert_shader.vert | 56 +- .../shaders/wavefront.glsl | 36 +- ray_tracing_animation/README.md | 146 +-- ray_tracing_animation/hello_vulkan.cpp | 200 ++-- ray_tracing_animation/hello_vulkan.h | 51 +- ray_tracing_animation/main.cpp | 18 +- .../shaders/frag_shader.frag | 47 +- ray_tracing_animation/shaders/host_device.h | 117 ++ ray_tracing_animation/shaders/raytrace.rchit | 52 +- ray_tracing_animation/shaders/raytrace.rgen | 28 +- ray_tracing_animation/shaders/raytrace.rmiss | 9 +- .../shaders/vert_shader.vert | 56 +- ray_tracing_animation/shaders/wavefront.glsl | 36 +- ray_tracing_anyhit/README.md | 13 +- ray_tracing_anyhit/hello_vulkan.cpp | 165 ++- ray_tracing_anyhit/hello_vulkan.h | 50 +- ray_tracing_anyhit/main.cpp | 10 +- ray_tracing_anyhit/shaders/frag_shader.frag | 46 +- ray_tracing_anyhit/shaders/host_device.h | 118 ++ ray_tracing_anyhit/shaders/raytrace.rahit | 4 +- ray_tracing_anyhit/shaders/raytrace.rchit | 52 +- ray_tracing_anyhit/shaders/raytrace.rgen | 45 +- ray_tracing_anyhit/shaders/raytrace.rmiss | 9 +- .../shaders/raytrace_rahit.glsl | 4 +- ray_tracing_anyhit/shaders/vert_shader.vert | 56 +- ray_tracing_anyhit/shaders/wavefront.glsl | 36 +- ray_tracing_ao/README.md | 112 +- ray_tracing_ao/hello_vulkan.cpp | 135 ++- ray_tracing_ao/hello_vulkan.h | 40 +- ray_tracing_ao/main.cpp | 11 +- ray_tracing_ao/shaders/frag_shader.frag | 49 +- ray_tracing_ao/shaders/host_device.h | 117 ++ ray_tracing_ao/shaders/vert_shader.vert | 56 +- ray_tracing_ao/shaders/wavefront.glsl | 36 +- ray_tracing_callable/hello_vulkan.cpp | 169 +-- ray_tracing_callable/hello_vulkan.h | 57 +- ray_tracing_callable/main.cpp | 32 +- ray_tracing_callable/shaders/frag_shader.frag | 59 +- ray_tracing_callable/shaders/host_device.h | 123 ++ ray_tracing_callable/shaders/raytrace.rchit | 67 +- ray_tracing_callable/shaders/raytrace.rgen | 28 +- ray_tracing_callable/shaders/raytrace.rmiss | 9 +- ray_tracing_callable/shaders/vert_shader.vert | 59 +- ray_tracing_callable/shaders/wavefront.glsl | 35 +- ray_tracing_gltf/README.md | 153 ++- ray_tracing_gltf/hello_vulkan.cpp | 152 ++- ray_tracing_gltf/hello_vulkan.h | 57 +- ray_tracing_gltf/main.cpp | 12 +- ray_tracing_gltf/shaders/frag_shader.frag | 50 +- ray_tracing_gltf/shaders/gltf.glsl | 27 +- ray_tracing_gltf/shaders/host_device.h | 112 ++ ray_tracing_gltf/shaders/pathtrace.rchit | 17 +- ray_tracing_gltf/shaders/pathtrace.rgen | 39 +- ray_tracing_gltf/shaders/raytrace.rchit | 6 +- ray_tracing_gltf/shaders/raytrace.rgen | 27 +- ray_tracing_gltf/shaders/vert_shader.vert | 62 +- ray_tracing_indirect_scissor/hello_vulkan.cpp | 184 +-- ray_tracing_indirect_scissor/hello_vulkan.h | 87 +- ray_tracing_indirect_scissor/main.cpp | 10 +- .../shaders/frag_shader.frag | 46 +- .../shaders/host_device.h | 130 ++ .../shaders/raycommon.glsl | 13 - .../shaders/raytrace.rchit | 56 +- .../shaders/raytrace.rgen | 45 +- .../shaders/raytrace.rmiss | 11 +- .../shaders/vert_shader.vert | 56 +- .../shaders/wavefront.glsl | 35 +- ray_tracing_instances/README.md | 23 +- ray_tracing_instances/hello_vulkan.cpp | 159 ++- ray_tracing_instances/hello_vulkan.h | 49 +- ray_tracing_instances/main.cpp | 19 +- .../shaders/frag_shader.frag | 46 +- ray_tracing_instances/shaders/host_device.h | 117 ++ ray_tracing_instances/shaders/raytrace.rchit | 52 +- ray_tracing_instances/shaders/raytrace.rgen | 28 +- ray_tracing_instances/shaders/raytrace.rmiss | 9 +- .../shaders/vert_shader.vert | 56 +- ray_tracing_instances/shaders/wavefront.glsl | 35 +- ray_tracing_intersection/README.md | 119 +- ray_tracing_intersection/hello_vulkan.cpp | 196 +-- ray_tracing_intersection/hello_vulkan.h | 67 +- ray_tracing_intersection/main.cpp | 10 +- .../shaders/frag_shader.frag | 46 +- .../shaders/host_device.h | 132 +++ .../shaders/raycommon.glsl | 15 - .../shaders/raytrace.rchit | 53 +- .../shaders/raytrace.rgen | 28 +- .../shaders/raytrace.rint | 2 +- .../shaders/raytrace.rmiss | 9 +- .../shaders/raytrace2.rchit | 45 +- .../shaders/vert_shader.vert | 56 +- .../shaders/wavefront.glsl | 35 +- ray_tracing_jitter_cam/README.md | 34 +- ray_tracing_jitter_cam/hello_vulkan.cpp | 167 ++- ray_tracing_jitter_cam/hello_vulkan.h | 50 +- ray_tracing_jitter_cam/main.cpp | 4 +- .../shaders/frag_shader.frag | 46 +- ray_tracing_jitter_cam/shaders/host_device.h | 118 ++ .../shaders/passthrough.vert | 1 - ray_tracing_jitter_cam/shaders/raytrace.rchit | 52 +- ray_tracing_jitter_cam/shaders/raytrace.rgen | 43 +- ray_tracing_jitter_cam/shaders/raytrace.rmiss | 9 +- .../shaders/vert_shader.vert | 56 +- ray_tracing_jitter_cam/shaders/wavefront.glsl | 35 +- ray_tracing_manyhits/README.md | 171 ++- ray_tracing_manyhits/hello_vulkan.cpp | 161 ++- ray_tracing_manyhits/hello_vulkan.h | 51 +- ray_tracing_manyhits/main.cpp | 21 +- ray_tracing_manyhits/shaders/frag_shader.frag | 46 +- ray_tracing_manyhits/shaders/host_device.h | 117 ++ ray_tracing_manyhits/shaders/raytrace.rchit | 52 +- ray_tracing_manyhits/shaders/raytrace.rgen | 28 +- ray_tracing_manyhits/shaders/raytrace.rmiss | 9 +- ray_tracing_manyhits/shaders/raytrace2.rchit | 10 +- ray_tracing_manyhits/shaders/vert_shader.vert | 56 +- ray_tracing_manyhits/shaders/wavefront.glsl | 36 +- ray_tracing_motionblur/CMakeLists.txt | 80 ++ ray_tracing_motionblur/README.md | 260 ++++ ray_tracing_motionblur/hello_vulkan.cpp | 1048 +++++++++++++++++ ray_tracing_motionblur/hello_vulkan.h | 156 +++ ray_tracing_motionblur/images/motionblur.png | Bin 0 -> 95520 bytes .../images/rotary_disc_shutter.png | Bin 0 -> 114990 bytes ray_tracing_motionblur/main.cpp | 319 +++++ .../shaders/frag_shader.frag | 99 ++ ray_tracing_motionblur/shaders/host_device.h | 118 ++ .../shaders/passthrough.vert | 34 + ray_tracing_motionblur/shaders/post.frag | 37 + ray_tracing_motionblur/shaders/random.glsl | 53 + ray_tracing_motionblur/shaders/raycommon.glsl | 23 + ray_tracing_motionblur/shaders/raytrace.rchit | 145 +++ ray_tracing_motionblur/shaders/raytrace.rgen | 101 ++ ray_tracing_motionblur/shaders/raytrace.rmiss | 38 + .../shaders/raytraceShadow.rmiss | 28 + .../shaders/vert_shader.vert | 66 ++ ray_tracing_motionblur/shaders/wavefront.glsl | 48 + ray_tracing_rayquery/README.md | 50 +- ray_tracing_rayquery/hello_vulkan.cpp | 141 ++- ray_tracing_rayquery/hello_vulkan.h | 40 +- ray_tracing_rayquery/main.cpp | 10 +- ray_tracing_rayquery/shaders/frag_shader.frag | 52 +- ray_tracing_rayquery/shaders/host_device.h | 113 ++ ray_tracing_rayquery/shaders/vert_shader.vert | 56 +- ray_tracing_rayquery/shaders/wavefront.glsl | 35 +- ray_tracing_reflections/README.md | 33 +- ray_tracing_reflections/hello_vulkan.cpp | 161 ++- ray_tracing_reflections/hello_vulkan.h | 50 +- ray_tracing_reflections/main.cpp | 12 +- .../shaders/frag_shader.frag | 46 +- ray_tracing_reflections/shaders/host_device.h | 118 ++ .../shaders/raytrace.rchit | 54 +- ray_tracing_reflections/shaders/raytrace.rgen | 39 +- .../shaders/raytrace.rmiss | 9 +- .../shaders/vert_shader.vert | 56 +- .../shaders/wavefront.glsl | 35 +- ray_tracing_specialization/README.md | 69 +- ray_tracing_specialization/hello_vulkan.cpp | 162 ++- ray_tracing_specialization/hello_vulkan.h | 51 +- ray_tracing_specialization/main.cpp | 16 +- .../shaders/frag_shader.frag | 46 +- .../shaders/host_device.h | 118 ++ .../shaders/raytrace.rchit | 53 +- .../shaders/raytrace.rgen | 38 +- .../shaders/raytrace.rmiss | 9 +- .../shaders/vert_shader.vert | 56 +- .../shaders/wavefront.glsl | 35 +- 222 files changed, 9045 insertions(+), 5734 deletions(-) create mode 100644 media/scenes/cube_modif.obj create mode 100644 ray_tracing__advance/shaders/host_device.h create mode 100644 ray_tracing__before/images/base_pipeline.png create mode 100644 ray_tracing__before/images/vk_ray_tracing__before.png create mode 100644 ray_tracing__before/shaders/host_device.h create mode 100644 ray_tracing__simple/shaders/host_device.h create mode 100644 ray_tracing_advanced_compilation/shaders/host_device.h create mode 100644 ray_tracing_animation/shaders/host_device.h create mode 100644 ray_tracing_anyhit/shaders/host_device.h create mode 100644 ray_tracing_ao/shaders/host_device.h create mode 100644 ray_tracing_callable/shaders/host_device.h create mode 100644 ray_tracing_gltf/shaders/host_device.h create mode 100644 ray_tracing_indirect_scissor/shaders/host_device.h create mode 100644 ray_tracing_instances/shaders/host_device.h create mode 100644 ray_tracing_intersection/shaders/host_device.h create mode 100644 ray_tracing_jitter_cam/shaders/host_device.h create mode 100644 ray_tracing_manyhits/shaders/host_device.h create mode 100644 ray_tracing_motionblur/CMakeLists.txt create mode 100644 ray_tracing_motionblur/README.md create mode 100644 ray_tracing_motionblur/hello_vulkan.cpp create mode 100644 ray_tracing_motionblur/hello_vulkan.h create mode 100644 ray_tracing_motionblur/images/motionblur.png create mode 100644 ray_tracing_motionblur/images/rotary_disc_shutter.png create mode 100644 ray_tracing_motionblur/main.cpp create mode 100644 ray_tracing_motionblur/shaders/frag_shader.frag create mode 100644 ray_tracing_motionblur/shaders/host_device.h create mode 100644 ray_tracing_motionblur/shaders/passthrough.vert create mode 100644 ray_tracing_motionblur/shaders/post.frag create mode 100644 ray_tracing_motionblur/shaders/random.glsl create mode 100644 ray_tracing_motionblur/shaders/raycommon.glsl create mode 100644 ray_tracing_motionblur/shaders/raytrace.rchit create mode 100644 ray_tracing_motionblur/shaders/raytrace.rgen create mode 100644 ray_tracing_motionblur/shaders/raytrace.rmiss create mode 100644 ray_tracing_motionblur/shaders/raytraceShadow.rmiss create mode 100644 ray_tracing_motionblur/shaders/vert_shader.vert create mode 100644 ray_tracing_motionblur/shaders/wavefront.glsl create mode 100644 ray_tracing_rayquery/shaders/host_device.h create mode 100644 ray_tracing_reflections/shaders/host_device.h create mode 100644 ray_tracing_specialization/shaders/host_device.h diff --git a/.gitignore b/.gitignore index b263145..efd04b7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ **/spv/*.spv -/build/* \ No newline at end of file +/build/* +/out/build/x64-Debug +/.vs diff --git a/CMakeLists.txt b/CMakeLists.txt index 1605665..2ff7ace 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,6 +59,7 @@ add_subdirectory(ray_tracing_ao) add_subdirectory(ray_tracing_indirect_scissor) add_subdirectory(ray_tracing_specialization) add_subdirectory(ray_tracing_advanced_compilation) +add_subdirectory(ray_tracing_motionblur) #-------------------------------------------------------------------------------------------------- diff --git a/docs/vkrt_tuto_indirect_scissor.md.html b/docs/vkrt_tuto_indirect_scissor.md.html index 794a514..67e772d 100644 --- a/docs/vkrt_tuto_indirect_scissor.md.html +++ b/docs/vkrt_tuto_indirect_scissor.md.html @@ -671,42 +671,47 @@ write `m_lanternCount`. // 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 +// gl_InstanceCustomIndexEXT will be the index of the instance or lantern in m_instances or // m_lanterns respectively. +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { - assert(m_lanternCount == 0); - m_lanternCount = m_lanterns.size(); - - std::vector tlas; - tlas.reserve(m_objInstance.size() + m_lanternCount); - - // Add the OBJ instances. - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) - { - nvvk::RaytracingBuilderKHR::Instance rayInst; - rayInst.transform = m_objInstance[i].transform; // Position of the instance - rayInst.instanceCustomId = i; // gl_InstanceCustomIndexEXT - rayInst.blasId = m_objInstance[i].objIndex; - rayInst.hitGroupId = 0; // We will use the same hit group for all OBJ - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - tlas.emplace_back(rayInst); + assert(m_lanternCount == 0); + m_lanternCount = m_lanterns.size(); + + std::vector tlas; + tlas.reserve(m_instances.size() + m_lanternCount); + + // Add the OBJ instances. + for(const HelloVulkan::ObjInstance& inst : m_instances) + { + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 + rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + tlas.emplace_back(rayInst); + } + + // Add lantern instances. + for(int i = 0; i < static_cast(m_lanterns.size()); ++i) + { + VkAccelerationStructureInstanceKHR lanternInstance; + lanternInstance.transform = nvvk::toTransformMatrixKHR(nvmath::translation_mat4(m_lanterns[i].position)); + lanternInstance.instanceCustomIndex = i; + lanternInstance.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(uint32_t(m_lanternBlasId)); + lanternInstance.instanceShaderBindingTableRecordOffset = 1; // Next hit group is for lanterns. + lanternInstance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + lanternInstance.mask = 0xFF; + tlas.emplace_back(lanternInstance); + } + + m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } - - // 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_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); -} ```` The principle differences are: @@ -761,7 +766,7 @@ void HelloVulkan::createRtDescriptorSet() // ... // Lantern buffer (binding = 2) - m_rtDescSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + m_rtDescSetLayoutBind.addBinding(eLanterns, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); assert(m_lanternCount > 0); @@ -771,7 +776,7 @@ void HelloVulkan::createRtDescriptorSet() // ... - writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &lanternBufferInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, eLanterns, &lanternBufferInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } ```` @@ -858,7 +863,7 @@ screen. As this is no longer the case for scissor rectangles, we communicate the 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`. +Modify `m_pcRay` in `hello_vulkan.h`. ```` C // Push constant for ray trace pipeline. @@ -883,7 +888,7 @@ Modify `m_rtPushConstants` in `hello_vulkan.h`. // See m_lanternDebug. int32_t lanternDebug; - } m_rtPushConstants; + } m_pcRay; ```` We also update the GLSL push constant to match. Since the raygen shader now needs @@ -1391,14 +1396,14 @@ pass. There are minimal changes from before, we just have to ```` 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; + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pushConstant.lightPosition; + m_pcRay.lightIntensity = m_pushConstant.lightIntensity; + m_pcRay.lightType = m_pushConstant.lightType; + m_pcRay.lanternPassNumber = -1; // Global non-lantern pass + m_pcRay.screenX = m_size.width; + m_pcRay.screenY = m_size.height; + m_pcRay.lanternDebug = m_lanternDebug; ```` * Update the addresses of the raygen, miss, and hit group sections of the SBT @@ -1457,10 +1462,10 @@ is the first member of `LanternIndirectEntry`. ```` C // Set lantern pass number. - m_rtPushConstants.lanternPassNumber = i; + m_pcRay.lanternPassNumber = i; vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(RtPushConstant), &m_pcRay); VkDeviceAddress indirectDeviceAddress = diff --git a/docs/vkrt_tutorial.md.html b/docs/vkrt_tutorial.md.html index 756227d..fca4259 100644 --- a/docs/vkrt_tutorial.md.html +++ b/docs/vkrt_tutorial.md.html @@ -24,9 +24,7 @@ of Vulkan in general. The code verbosity of classical components such as swapcha reduced using [C++ API helpers](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk) and NVIDIA's [nvpro-samples](https://github.com/nvpro-samples/build_all) framework. This framework contains many advanced examples and best practices for Vulkan and OpenGL. We also use a helper for the creation of the ray tracing acceleration -structures, but we will document its contents extensively in this tutorial. The code is further simplified by using the -[Vulkan C++ API](https://github.com/KhronosGroup/Vulkan-Hpp), whose type safety and constructors reduce both its -verbosity and its potential for errors. +structures, but we will document its contents extensively in this tutorial. !!! Note Note For educational purposes all the code is contained in a very small set of files. @@ -79,12 +77,9 @@ You need a graphics card with support for the `VK_KHR_ray_tracing_pipeline` exte For NVIDIA graphics cards, you need a [Vulkan driver](https://developer.nvidia.com/vulkan-driver) released in 2021 or later. -The Vulkan SDK 1.2.161 and up which can be found under https://vulkan.lunarg.com/sdk/home will work with this project. -This version was tested with 1.2.176.1. +The [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) 1.2.161 and up will work with this project. +This version was tested with 1.2.182.0. -!!! Tip Visual Assist - To get auto-completion with Vulkan CPP, edit vulkan.hpp and change two places from:
- `namespace VULKAN_HPP_NAMESPACE` to `namespace vk` # Compiling & Running @@ -113,14 +108,14 @@ To be able to use ray tracing, we will need `VK_KHR_ACCELERATION_STRUCTURE` and Those extensions have also dependencies on other extension, therefore all the following extensions will need to be added. -```` C +~~~~ C // #VKRay: Activate the ray tracing extension VkPhysicalDeviceAccelerationStructureFeaturesKHR accelFeature{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR}; contextInfo.addDeviceExtension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, false, &accelFeature); // To build acceleration structures VkPhysicalDeviceRayTracingPipelineFeaturesKHR rtPipelineFeature{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR}; contextInfo.addDeviceExtension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, false, &rtPipelineFeature); // To use vkCmdTraceRaysKHR contextInfo.addDeviceExtension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); // Required by ray tracing pipeline -```` +~~~~ Behind the scenes, the helper is selecting a physical device supporting the required `VK_KHR_*` extensions, then placing the `VkPhysicalDevice*FeaturesKHR` structs on the `pNext` chain of `VkDeviceCreateInfo` before @@ -131,16 +126,16 @@ device's ray tracing capabilities. If you are curious, this is done in the Vulka !!! NOTE Loading function pointers As in OpenGL, when using extensions in Vulkan, you need to manually load in function pointers for extensions, using `vkGetInstanceProcAddr` and `vkGetDeviceProcAddr`. The `nvvk::Context` class that this sample depends on magically does - this for you, for the Vulkan C API by calling `load_VK_EXTENSION_SUBSET`. + this for you, for the Vulkan C API by calling [`load_VK_EXTENSIONS`](https://github.com/nvpro-samples/nvpro_core/blob/fd6f14c4ddcb6b2ec1e79462d372b32f3838b016/nvvk/extensions_vk.cpp#L2647). In the `HelloVulkan` class in `hello_vulkan.h`, add an initialization function and a member storing the capabilities of the GPU for ray tracing: -```` C +~~~~ C // #VKRay void initRayTracing(); VkPhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR}; -```` +~~~~ At the end of `hello_vulkan.cpp`, add the body of `initRayTracing()`, which will query the ray tracing capabilities of the GPU using this extension. In particular, it will obtain the maximum recursion depth, @@ -150,7 +145,7 @@ should in practice be kept to a minimum, favoring a loop formulation. This also needed in a later section for creating the shader binding table. -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // Initialize Vulkan ray tracing // #VKRay @@ -161,17 +156,17 @@ void HelloVulkan::initRayTracing() prop2.pNext = &m_rtProperties; vkGetPhysicalDeviceProperties2(m_physicalDevice, &prop2); } -```` +~~~~ ## main In `main.cpp`, in the `main()` function, we call the initialization method right after `helloVk.updateDescriptorSet();` -```` C +~~~~ C // #VKRay helloVk.initRayTracing(); -```` +~~~~ !!! Note: Exercise When running the program, you can put a breakpoint in the `initRayTracing()` method to inspect @@ -208,22 +203,22 @@ instance. For ray tracing the `ObjModel` and list of `ObjInstance`s will then na To simplify the ray tracing setup we use a helper class that acts as a container for one TLAS referencing an array of BLASes, with utility functions for building those acceleration structures. In the header file `hello_vulkan.h`, include the `raytrace_vkpp` helper -```` C +~~~~ C // #VKRay #include "nvvk/raytraceKHR_vk.hpp" -```` +~~~~ so that we can add that helper as a member in the `HelloVulkan` class, -```` C +~~~~ C nvvk::RaytracingBuilderKHR m_rtBuilder; -```` +~~~~ and initialize it at the end of `initRaytracing()`: -```` C +~~~~ C m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex); -```` +~~~~ !!! Note Memory Management The raytrace helper uses [`"nvvk/resourceallocator_vk.hpp"`](https://github.com/nvpro-samples/nvpro_core/blob/master/nvvk/resourceallocator_vk.hpp) @@ -245,9 +240,9 @@ multiple structures consumed by the AS builder. We are holding all those structu Add a new method to the `HelloVulkan` class: -```` C +~~~~ C auto objectToVkGeometryKHR(const ObjModel& model); -```` +~~~~ !!! Note Note The `objectToVkGeometryKHR()` function is returning `nvvk::RaytracingBuilderKHR::BlasInput` but we are using the C++ `auto` as it is @@ -281,7 +276,7 @@ objects. This should be concider only for large or complex static group of objec Note that we consider all objects opaque for now, and indicate this to the builder for potential optimization. (More specifically, this disables calls to the anyhit shader, described later). -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // Convert an OBJ model into the ray tracing geometry used to build the BLAS // @@ -295,7 +290,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -325,7 +320,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) return input; } -```` +~~~~ !!! Note Vertex Attributes In the above code, we took advantage of the fact that position is the first member of the `VertexObj` struct. @@ -342,15 +337,15 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) In the `HelloVulkan` class declaration, we can now add the `createBottomLevelAS()` method that will generate a `nvvk::RaytracingBuilderKHR::BlasInput` for each object, and trigger a BLAS build: -```` C +~~~~ C void createBottomLevelAS(); -```` +~~~~ The implementation loops over all the loaded models and fills in an array of `nvvk::RaytracingBuilderKHR::BlasInput` before triggering a build of all BLASes in a batch. The resulting acceleration structures will be stored within the helper in the order of construction, so that they can be directly referenced by index later. -```` C +~~~~ C void HelloVulkan::createBottomLevelAS() { // BLAS - Storing each primitive in a geometry @@ -365,7 +360,7 @@ void HelloVulkan::createBottomLevelAS() } m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } -```` +~~~~ ### Helper Details: RaytracingBuilder::buildBlas() @@ -386,7 +381,7 @@ The above data will be stored in a structure `BuildAccelerationStructure` to eas At the begining of the function, we are only initializing data that we will need later. -````C +~~~~C //-------------------------------------------------------------------------------------------------- // Create all the BLAS from the vector of BlasInput // - There will be one BLAS per input-vector entry @@ -402,7 +397,7 @@ void nvvk::RaytracingBuilderKHR::buildBlas(const std::vector& input, VkDeviceSize asTotalSize{0}; // Memory size of all allocated BLAS uint32_t nbCompactions{0}; // Nb of BLAS requesting compaction VkDeviceSize maxScratchSize{0}; // Largest scratch size -```` +~~~~ The next part is to populate the `BuildAccelerationStructure` for each BLAS, setting the reference to the geometry, the build range, the size of the memory needed for the build, and the size of the scratch buffer. @@ -411,7 +406,7 @@ Later, we will allocate a scratch buffer of this size. -````C +~~~~C // Preparing the information for the acceleration build commands. std::vector buildAs(nbBlas); for(uint32_t idx = 0; idx < nbBlas; idx++) @@ -439,17 +434,17 @@ for(uint32_t idx = 0; idx < nbBlas; idx++) maxScratchSize = std::max(maxScratchSize, buildAs[idx].sizeInfo.buildScratchSize); nbCompactions += hasFlag(buildAs[idx].buildInfo.flags, VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR); } -```` +~~~~ After looping over all BLAS, we have the largest scratch buffer size and we will create it. -```` C +~~~~ C // Allocate the scratch buffers holding the temporary data of the acceleration structure builder nvvk::Buffer scratchBuffer = m_alloc->createBuffer(maxScratchSize, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); VkBufferDeviceAddressInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, scratchBuffer.buffer}; VkDeviceAddress scratchAddress = vkGetBufferDeviceAddress(m_device, &bufferInfo); -```` +~~~~ The following section is for querying the real size of each BLAS. To know the size that the BLAS is really taking, we use queries of the type `VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR`. @@ -458,7 +453,7 @@ size returned by `vkGetAccelerationStructureBuildSizesKHR` has the size of the w the real space can be smaller, and it is possible to copy the acceleration structure to one that is using exactly what is needed. This could save over 50% of the device memory usage. -```` C +~~~~ C // Allocate a query pool for storing the needed size for every BLAS compaction. VkQueryPool queryPool{VK_NULL_HANDLE}; if(nbCompactions > 0) // Is compaction requested? @@ -469,7 +464,7 @@ if(nbCompactions > 0) // Is compaction requested? qpci.queryType = VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHR; vkCreateQueryPool(m_device, &qpci, nullptr, &queryPool); } -```` +~~~~ !!! Note Compaction To use compaction the BLAS flag must have VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR @@ -482,7 +477,7 @@ And if we request compaction, we will do it immediately, thus limiting the memor See below for the split of BLAS creation. The function `cmdCreateBlas` and `cmdCompactBlas` will be detailed later. -```` C +~~~~ C // Batching creation/compaction of BLAS to allow staying in restricted amount of memory std::vector indices; // Indices of the BLAS to create VkDeviceSize batchSize{0}; @@ -513,31 +508,31 @@ for(uint32_t idx = 0; idx < nbBlas; idx++) indices.clear(); } } -```` +~~~~ The created acceleration structure is kept in this class, such that it can be retrieved with the index of creation. -```` C +~~~~ C // Keeping all the created acceleration structures for(auto& b : buildAs) { m_blas.emplace_back(b.as); } -```` +~~~~ Finally we are cleaning up what we use. -```` C +~~~~ C // Clean up vkDestroyQueryPool(m_device, queryPool, nullptr); m_alloc->finalizeAndReleaseStaging(); m_alloc->destroy(scratchBuffer); m_cmdPool.deinit(); -```` +~~~~ #### cmdCreateBlas -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // Creating the bottom level acceleration structure for all indices of `buildAs` vector. // The array of BuildAccelerationStructure was created in buildBlas and the vector of @@ -549,22 +544,22 @@ void nvvk::RaytracingBuilderKHR::cmdCreateBlas(VkCommandBuffer VkDeviceAddress scratchAddress, VkQueryPool queryPool) { -```` +~~~~ First we reset the query to know the real size of the BLAS -````C +~~~~C if(queryPool) // For querying the compaction size vkResetQueryPool(m_device, queryPool, 0, static_cast(indices.size())); uint32_t queryCnt{0}; -```` +~~~~ This function is creating all the BLAS defined by the index chunk. -```` C +~~~~ C for(const auto& idx : indices) { -```` +~~~~ The creation of the BLAS consist in two steps: @@ -583,7 +578,7 @@ where `Vk*` handle allocation and memory binding is done in separate steps, an a to memory with one `vkCreateAccelerationStructureKHR` call. -```` C +~~~~ C // Actual allocation of buffer and acceleration structure. VkAccelerationStructureCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR}; createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; @@ -598,7 +593,7 @@ buildAs[idx].buildInfo.scratchData.deviceAddress = scratchAddress; // All build // Building the bottom-level-acceleration-structure vkCmdBuildAccelerationStructuresKHR(cmdBuf, 1, &buildAs[idx].buildInfo, &buildAs[idx].rangeInfo); -```` +~~~~ Note the barrier after each call to the build: this is necessary because we are reusing scratch space across builds, @@ -606,7 +601,7 @@ so we need to make sure the previous build is finished before starting the next scratch buffers, but that would have been memory intensive, and the device can only build one BLAS at a time, so it wouldn't be any faster. -```` C +~~~~ C // Since the scratch buffer is reused across builds, we need a barrier to ensure one build // is finished before starting the next one. VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; @@ -614,11 +609,11 @@ barrier.srcAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR; barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_READ_BIT_KHR; vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, 0, 1, &barrier, 0, nullptr, 0, nullptr); -```` +~~~~ Then we add the size query only if needed -```` C +~~~~ C if(queryPool) { // Add a query to find the 'real' amount of memory needed, use for compaction @@ -627,7 +622,7 @@ if(queryPool) } } } -```` +~~~~ Although this approach has the advantage of keeping all BLAS independent, building many BLAS efficiently would require allocating a larger scratch buffer and launching multiple builds simultaneously. @@ -641,7 +636,7 @@ it actually uses. We have to wait until all BLAS are built, to make a copy in th This is the reason why we used `m_cmdPool.submitAndWait(cmdBuf)` before calling this function. -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // Create and replace a new acceleration structure and buffer based on the size retrieved by the // Query. @@ -650,7 +645,7 @@ void nvvk::RaytracingBuilderKHR::cmdCompactBlas(VkCommandBuffer std::vector& buildAs, VkQueryPool queryPool) { -```` +~~~~ In broad terms, compaction works as follows: @@ -659,7 +654,7 @@ In broad terms, compaction works as follows: * Copy the previous acceleration structure to the new allocated one * Destroy previous acceleration structure. -```` C +~~~~ C uint32_t queryCtn{0}; std::vector cleanupAS; // previous AS to destroy @@ -689,7 +684,7 @@ for(auto idx : indices) vkCmdCopyAccelerationStructureKHR(cmdBuf, ©Info); } } -```` +~~~~ @@ -702,9 +697,9 @@ for(auto idx : indices) The TLAS is the entry point in the ray tracing scene description, and stores all the instances. Add a new method to the `HelloVulkan` class: -```` C +~~~~ C void createTopLevelAS(); -```` +~~~~ We represent an instance with `VkAccelerationStructureInstanceKHR`, which stores its transform matrix (`transform`) a reference of its corresponding BLAS (`blasId`) in the vector passed to `buildBlas`. It also contains an instance identifier that will @@ -731,33 +726,36 @@ for simplicity and independence on the winding of the input models. Once all the instance objects are created we trigger the TLAS build, directing the builder to prefer generating a TLAS optimized for tracing performance (rather than AS size, for example). -```` C +~~~~ C +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } -```` +~~~~ As usual in Vulkan, we need to explicitly destroy the objects we created by adding a call at the end of `HelloVulkan::destroyResources`: -```` C +~~~~ C // #VKRay m_rtBuilder.destroy(); -```` +~~~~ !!! Note getBlasDeviceAddress() `getBlasDeviceAddress()` returns the acceleration structure device address of the `blasId`. The id correspond to @@ -771,188 +769,163 @@ and builds a TLAS from a vector of `Instance` objects. We first set up a command buffer and copy the user's TLAS flags. -```` C +~~~~ C // Creating the top-level acceleration structure from the vector of Instance // - See struct of Instance // - The resulting TLAS will be stored in m_tlas // - update is to rebuild the Tlas with updated matrices - void buildTlas(const std::vector& instances, + void buildTlas(const std::vector& instances, VkBuildAccelerationStructureFlagsKHR flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR, bool update = false) { // Cannot call buildTlas twice except to update. - assert(m_tlas.as.accel == VK_NULL_HANDLE || update); + assert(m_tlas.accel == VK_NULL_HANDLE || update); + uint32_t countInstance = static_cast(instances.size()); + // Command buffer to create the TLAS nvvk::CommandPool genCmdBuf(m_device, m_queueIndex); VkCommandBuffer cmdBuf = genCmdBuf.createCommandBuffer(); - - m_tlas.flags = flags; -```` - -Next, we need to convert the helper `Instance`s into Vulkan instances. The most notable change is that -`blasId`, the index of BLASes referenced in `m_blas`, gets converted to a raw BLAS device address. - -```` C - // Convert array of our Instances to an array native Vulkan instances. - std::vector geometryInstances; - geometryInstances.reserve(instances.size()); - for(const auto& inst : instances) - { - geometryInstances.push_back(instanceToVkGeometryInstanceKHR(inst)); - } -```` - -For convenience, the implementation of `instanceToVkGeometryInstanceKHR` is copied here: - -```` C - // Convert an Instance object into a VkAccelerationStructureInstanceKHR - VkAccelerationStructureInstanceKHR instanceToVkGeometryInstanceKHR(const Instance& instance) - { - assert(size_t(instance.blasId) < m_blas.size()); - BlasEntry& blas{m_blas[instance.blasId]}; - - VkAccelerationStructureDeviceAddressInfoKHR addressInfo{ - VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR}; - addressInfo.accelerationStructure = blas.as.accel; - VkDeviceAddress blasAddress = vkGetAccelerationStructureDeviceAddressKHR(m_device, &addressInfo); - - VkAccelerationStructureInstanceKHR gInst{}; - // The matrices for the instance transforms are row-major, instead of - // column-major in the rest of the application - nvmath::mat4f transp = nvmath::transpose(instance.transform); - // The gInst.transform value only contains 12 values, corresponding to a 4x3 - // matrix, hence saving the last row that is anyway always (0,0,0,1). Since - // the matrix is row-major, we simply copy the first 12 values of the - // original 4x4 matrix - memcpy(&gInst.transform, &transp, sizeof(gInst.transform)); - gInst.instanceCustomIndex = instance.instanceCustomId; - gInst.mask = instance.mask; - gInst.instanceShaderBindingTableRecordOffset = instance.hitGroupId; - gInst.flags = instance.flags; - gInst.accelerationStructureReference = blasAddress; - return gInst; - } -```` +~~~~ Next, we need to upload the Vulkan instances to the device. -```` C +~~~~ C + // Command buffer to create the TLAS + nvvk::CommandPool genCmdBuf(m_device, m_queueIndex); + VkCommandBuffer cmdBuf = genCmdBuf.createCommandBuffer(); + // Create a buffer holding the actual instance data (matrices++) for use by the AS builder - VkDeviceSize instanceDescsSizeInBytes = instances.size() * sizeof(VkAccelerationStructureInstanceKHR); + nvvk::Buffer instancesBuffer; // Buffer of instances containing the matrices and BLAS ids + instancesBuffer = m_alloc->createBuffer(cmdBuf, instances, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR); + NAME_VK(instancesBuffer.buffer); + VkBufferDeviceAddressInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, instancesBuffer.buffer}; + VkDeviceAddress instBufferAddr = vkGetBufferDeviceAddress(m_device, &bufferInfo); - // Allocate the instance buffer and copy its contents from host to device memory - if(update) - m_alloc->destroy(m_instBuffer); - m_instBuffer = m_alloc->createBuffer(cmdBuf, geometryInstances, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); - m_debug.setObjectName(m_instBuffer.buffer, "TLASInstances"); - VkBufferDeviceAddressInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO}; - bufferInfo.buffer = m_instBuffer.buffer; - VkDeviceAddress instanceAddress = vkGetBufferDeviceAddress(m_device, &bufferInfo); - - // Make sure the copy of the instance buffer are copied before triggering the - // acceleration structure build + // Make sure the copy of the instance buffer are copied before triggering the acceleration structure build VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_ACCELERATION_STRUCTURE_WRITE_BIT_KHR; - vkCmdPipelineBarrier(cmdBuf, - VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, - 0, 1, &barrier, 0, nullptr, 0, nullptr); -```` + vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR, + 0, 1, &barrier, 0, nullptr, 0, nullptr); +~~~~ -As in `buildBlas`, the instance data is passed as part of a union. Fill in that union (`topASGeometry.geometry`) now. +At this point, we have a command buffer (`cmdBuf`), a number of instances(`countInstance`) and the address of the buffer holding +all the `VkAccelerationStructureInstanceKHR`. With this information, we call a function that will build the TLAS. This function +will allocate a scratch buffer that we will need to destroy once all work is done. -```` C - // Create VkAccelerationStructureGeometryInstancesDataKHR - // This wraps a device pointer to the above uploaded instances. - VkAccelerationStructureGeometryInstancesDataKHR instancesVk{ - VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR}; - instancesVk.arrayOfPointers = VK_FALSE; - instancesVk.data.deviceAddress = instanceAddress; +~~~~C + // Creating the TLAS + nvvk::Buffer scratchBuffer; + cmdCreateTlas(cmdBuf, countInstance, instBufferAddr, scratchBuffer, flags, update, motion); - // Put the above into a VkAccelerationStructureGeometryKHR. We need to put the - // instances struct in a union and label it as instance data. - VkAccelerationStructureGeometryKHR topASGeometry{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR}; - topASGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; - topASGeometry.geometry.instances = instancesVk; -```` - -Once again query the needed memory for the TLAS and scratch space. - -```` C - // Find sizes - VkAccelerationStructureBuildGeometryInfoKHR buildInfo{ - VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR}; - buildInfo.flags = flags; - buildInfo.geometryCount = 1; - buildInfo.pGeometries = &topASGeometry; - buildInfo.mode = update - ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR - : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; - buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; - buildInfo.srcAccelerationStructure = VK_NULL_HANDLE; - - uint32_t count = (uint32_t)instances.size(); - VkAccelerationStructureBuildSizesInfoKHR sizeInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR}; - vkGetAccelerationStructureBuildSizesKHR( - m_device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildInfo, &count, &sizeInfo); -```` - -Allocate the TLAS, its memory, and the scratch buffer. - -```` C - // Create TLAS - if(update == false) - { - VkAccelerationStructureCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR}; - createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; - createInfo.size = sizeInfo.accelerationStructureSize; - - m_tlas.as = m_alloc->createAcceleration(createInfo); - m_debug.setObjectName(m_tlas.as.accel, "Tlas"); - } - - // Allocate the scratch memory - nvvk::Buffer scratchBuffer = - m_alloc->createBuffer(sizeInfo.buildScratchSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR - | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); - bufferInfo.buffer = scratchBuffer.buffer; - VkDeviceAddress scratchAddress = vkGetBufferDeviceAddress(m_device, &bufferInfo); -```` - -Finally, fill in the addresses to pass to the TLAS build command, indicate that we want the entire array of instances -to be built into a TLAS by filling in a suitable `VkAccelerationStructureBuildRangeInfoKHR`, build the TLAS, and clean -up scratch memory. - -```` - // Update build information - buildInfo.srcAccelerationStructure = update ? m_tlas.as.accel : VK_NULL_HANDLE; - buildInfo.dstAccelerationStructure = m_tlas.as.accel; - buildInfo.scratchData.deviceAddress = scratchAddress; - - // Build Offsets info: n instances - VkAccelerationStructureBuildRangeInfoKHR buildOffsetInfo{static_cast(instances.size()), 0, 0, 0}; - const VkAccelerationStructureBuildRangeInfoKHR* pBuildOffsetInfo = &buildOffsetInfo; - - // Build the TLAS - vkCmdBuildAccelerationStructuresKHR(cmdBuf, 1, &buildInfo, &pBuildOffsetInfo); - - genCmdBuf.submitAndWait(cmdBuf); // queueWaitIdle inside. + // Finalizing and destroying temporary data + genCmdBuf.submitAndWait(cmdBuf); // queueWaitIdle inside. m_alloc->finalizeAndReleaseStaging(); m_alloc->destroy(scratchBuffer); -```` + m_alloc->destroy(instancesBuffer); +} +~~~~ + +This lower function is the actual construction of the top-level-acceleration-structure. + +~~~~ C +//-------------------------------------------------------------------------------------------------- +// Low level of Tlas creation - see buildTlas +// +void nvvk::RaytracingBuilderKHR::cmdCreateTlas(VkCommandBuffer cmdBuf, + uint32_t countInstance, + VkDeviceAddress instBufferAddr, + nvvk::Buffer& scratchBuffer, + VkBuildAccelerationStructureFlagsKHR flags, + bool update, + bool motion) +{ +~~~~ + +The next part is filling the structures for building the TLAS. It is one geometry containing many instances. + +~~~~C + // Wraps a device pointer to the above uploaded instances. + VkAccelerationStructureGeometryInstancesDataKHR instancesVk{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR}; + instancesVk.data.deviceAddress = instBufferAddr; + + // Put the above into a VkAccelerationStructureGeometryKHR. We need to put the instances struct in a union and label it as instance data. + VkAccelerationStructureGeometryKHR topASGeometry{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR}; + topASGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; + topASGeometry.geometry.instances = instancesVk; + + // Find sizes + VkAccelerationStructureBuildGeometryInfoKHR buildInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR}; + buildInfo.flags = flags; + buildInfo.geometryCount = 1; + buildInfo.pGeometries = &topASGeometry; + buildInfo.mode = update ? VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR : VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + buildInfo.srcAccelerationStructure = VK_NULL_HANDLE; + + VkAccelerationStructureBuildSizesInfoKHR sizeInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR}; + vkGetAccelerationStructureBuildSizesKHR(m_device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, &buildInfo, + &countInstance, &sizeInfo); + +~~~~ + +We can create the acceleration structure, not building it yet. + +~~~~C + VkAccelerationStructureCreateInfoKHR createInfo{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR}; + createInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + createInfo.size = sizeInfo.accelerationStructureSize; + + m_tlas = m_alloc->createAcceleration(createInfo); + NAME_VK(m_tlas.accel); + NAME_VK(m_tlas.buffer.buffer); + +~~~~ + +Building the acceleration structure, also requires to create a scratch buffer. + +~~~~C + + // Allocate the scratch memory + scratchBuffer = m_alloc->createBuffer(sizeInfo.buildScratchSize, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + + VkBufferDeviceAddressInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, scratchBuffer.buffer}; + VkDeviceAddress scratchAddress = vkGetBufferDeviceAddress(m_device, &bufferInfo); + NAME_VK(scratchBuffer.buffer); + +~~~~ + +Finally, we can build the acceleration structure. + +~~~~C + // Update build information + buildInfo.srcAccelerationStructure = VK_NULL_HANDLE; + buildInfo.dstAccelerationStructure = m_tlas.accel; + buildInfo.scratchData.deviceAddress = scratchAddress; + + // Build Offsets info: n instances + VkAccelerationStructureBuildRangeInfoKHR buildOffsetInfo{countInstance, 0, 0, 0}; + const VkAccelerationStructureBuildRangeInfoKHR* pBuildOffsetInfo = &buildOffsetInfo; + + // Build the TLAS + vkCmdBuildAccelerationStructuresKHR(cmdBuf, 1, &buildInfo, &pBuildOffsetInfo); +} +~~~~ ## main In the `main` function, we can now add the creation of the geometry instances and acceleration structures right after initializing ray tracing: -```` C +~~~~ C // #VKRay helloVk.initRayTracing(); helloVk.createBottomLevelAS(); helloVk.createTopLevelAS(); -```` +~~~~ # Ray Tracing Descriptor Set @@ -972,29 +945,29 @@ buffer in which we store the output image. In the header `hello_vulkan.h`, we declare the objects related to this additional descriptor set: -```` C +~~~~ C void createRtDescriptorSet(); nvvk::DescriptorSetBindings m_rtDescSetLayoutBind; VkDescriptorPool m_rtDescPool; VkDescriptorSetLayout m_rtDescSetLayout; VkDescriptorSet m_rtDescSet; -```` +~~~~ The acceleration structure will be accessible by the Ray Generation shader, as we want to call `TraceRayEXT()` from this shader. Later in this document, we will also make it accessible from the Closest Hit shader, in order to send rays from there as well. The output image is the offscreen image used by the rasterization, and will be written only by the RayGen shader. -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // This descriptor set holds the Acceleration structure and the output image // void HelloVulkan::createRtDescriptorSet() { - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -1014,11 +987,11 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } -```` +~~~~ ## Additions to the Scene Descriptor Set @@ -1028,16 +1001,17 @@ compute ray directions, and the ClosestHit needs access to the materials, scene index buffers. Even though the vertex and index buffers will only be used by the ray tracing shaders we add them to this descriptor set as they semantically fit the Scene descriptor set. -```` C -// Camera matrices (binding = 0) -m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); -// Scene description (binding = 1) -m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, +~~~~ C +// Camera matrices +m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); +// Obj descriptions +m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); -// Textures (binding = 2) -m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, +// Textures +m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); -```` +~~~~ Originally the buffers containing the vertices and indices were only used by the rasterization pipeline. The ray tracing will need to use those buffers as storage buffers, so we add `VK_BUFFER_USAGE_STORAGE_BUFFER_BIT`; @@ -1047,7 +1021,7 @@ additionally, the buffers will be read by the acceleration structure builder, wh We update the usage of the buffers in `loadModel`: -```` C +~~~~ C VkBufferUsageFlags flag = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; VkBufferUsageFlags rayTracingFlags = // used also for building acceleration structures flag | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; @@ -1055,7 +1029,7 @@ model.vertexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_vertices, VK_BUFFER model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); -```` +~~~~ !!! Note: Array of Buffers Each model (OBJ) was constructed with a buffer of vertices, indices, and materials. Therefore the @@ -1069,13 +1043,13 @@ As with the rasterization descriptor set, the ray tracing descriptor set needs t This typically happens when resizing the window, as the output image is recreated and needs to be re-linked to the descriptor set. The update is performed in a new method of the `HelloVulkan` class: -```` C +~~~~ C void updateRtDescriptorSet(); -```` +~~~~ The implementation is straightforward, just update the output image reference: -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // Writes the output image to the descriptor set // - Required when changing resolution @@ -1084,10 +1058,10 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } -```` +~~~~ !!! Note Note We are using [`nvvk::DescriptorSetBindings`](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#class-nvvkdescriptorsetbindings) @@ -1096,25 +1070,25 @@ void HelloVulkan::updateRtDescriptorSet() We can then add the update call to the `onResize()` method to link it to the resizing event: -```` C +~~~~ C updateRtDescriptorSet(); -```` +~~~~ The resources created in this section need to be destroyed when closing the application by adding the following to `destroyResources`: -```` C +~~~~ C vkDestroyDescriptorPool(m_device, m_rtDescPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_rtDescSetLayout, nullptr); -```` +~~~~ ## main In the `main` function, we create the descriptor set after the other ray tracing calls: -```` C +~~~~ C helloVk.createRtDescriptorSet(); -```` +~~~~ # Ray Tracing Pipeline @@ -1227,26 +1201,35 @@ The `shaders` folder now contains 3 more files: In the header file, let's add the definition of the ray tracing pipeline building method, and the storage members of the pipeline: -```` C +~~~~ C void createRtPipeline(); std::vector m_rtShaderGroups; VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; -```` +~~~~ The pipeline will also use push constants to store global uniform values, namely the background color and -the light source information: +the light source information. Since we are setting the information on host and using it on device, this +structure will be set in `shaders/host_device.h`. -```` C -struct RtPushConstant +~~~~ C +// Push constant structure for the ray tracer +struct PushConstantRay { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; -} m_rtPushConstants; -```` + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; +~~~~ + +In `HelloVulkan` class, add a member for the push constant + +~~~~ C +// Push constant for ray tracer +PushConstantRay m_pcRay{}; +~~~~ Our implementation of the ray tracing pipeline generation starts by adding the ray generation and miss shader stages, followed by the closest hit shader. Note that this order is arbitrary, as the extension allows the developer to set up @@ -1258,7 +1241,7 @@ indices within this vector will be used as unique identifiers for the shaders. T same entry point "main". Then we create a `vkCreateShaderModule` from the pre-compiled shader and defined which stage it correspond to. -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // Pipeline for the ray tracer: all shaders, raygen, chit, miss // @@ -1293,7 +1276,7 @@ void HelloVulkan::createRtPipeline() stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rchit.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; stages[eClosestHit] = stage; -```` +~~~~ These identifiers are stored in the `VkRayTracingShaderGroupCreateInfoKHR` structure. This structure first specifies a `type`, which represents the kind of @@ -1302,7 +1285,7 @@ type is `VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR`, and only the `generalSha `VK_SHADER_UNUSED_KHR`. This is also the case for the callable shaders, not used in this tutorial. In our layout the ray generation comes first (0), followed by the miss shader (1). -```` C +~~~~ C // Shader groups VkRayTracingShaderGroupCreateInfoKHR group{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; group.anyHitShader = VK_SHADER_UNUSED_KHR; @@ -1320,7 +1303,7 @@ generation comes first (0), followed by the miss shader (1). group.generalShader = eMiss; m_rtShaderGroups.push_back(group); -```` +~~~~ As detailed before, intersections are managed by 3 kinds of shaders: the intersection shader computes the ray-geometry intersections, the any-hit shader is run for every potential intersection, and the closest hit shader is applied to the @@ -1334,13 +1317,13 @@ shader, letting the system use a built-in pass-through shader. Therefore, we als member to the index `2` (`chit`), since the `stages` vector already contains the ray generation and miss shaders. -```` C +~~~~ C // closest hit shader group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; group.generalShader = VK_SHADER_UNUSED_KHR; group.closestHitShader = eClosestHit; m_rtShaderGroups.push_back(group); -```` +~~~~ Note that if the geometry were not triangles, we would have set the `type` to `VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR`, and would have to define an intersection shader. @@ -1348,38 +1331,38 @@ define an intersection shader. After creating the shader groups, we need to setup the pipeline layout that will describe how the pipeline will access external data: -```` C +~~~~ C VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo; -```` +~~~~ We first add the push constant range to allow the ray tracing shaders to access the global uniform values: -```` C +~~~~ C // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstant; -```` +~~~~ As described earlier, the pipeline uses two descriptor sets: `set=0` is specific to the ray tracing pipeline (TLAS and output image), and `set=1` is shared with the rasterization (scene data): -```` C +~~~~ C // Descriptor sets: one specific to ray tracing, and one shared with the rasterization pipeline std::vector rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout}; pipelineLayoutCreateInfo.setLayoutCount = static_cast(rtDescSetLayouts.size()); pipelineLayoutCreateInfo.pSetLayouts = rtDescSetLayouts.data(); -```` +~~~~ The pipeline layout information is now complete, allowing us to create the layout itself. -```` C +~~~~ C vkCreatePipelineLayout(m_device, &pipelineLayoutCreateInfo, nullptr, &m_rtPipelineLayout); -```` +~~~~ The creation of the ray tracing pipeline is different from the classical graphics pipeline. In the graphics pipeline we simply need to fill in the fixed set of programmable stages (vertex, fragment, etc.). The ray tracing pipeline can @@ -1387,22 +1370,22 @@ contain an arbitrary number of stages depending on the number of active shaders We first provide all the stages that will be used: -```` C +~~~~ C // Assemble the shader stages and recursion depth info into the ray tracing pipeline VkRayTracingPipelineCreateInfoKHR rayPipelineInfo{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR}; rayPipelineInfo.stageCount = static_cast(stages.size()); // Stages are shaders rayPipelineInfo.pStages = stages.data(); -```` +~~~~ Then, we indicate how the shaders can be assembled into groups. A ray generation or miss shader is a group by itself, but hit groups can comprise up to 3 shaders (intersection, any hit, closest hit). -```` C +~~~~ C // In this case, m_rtShaderGroups.size() == 3: we have one raygen group, // one miss shader group, and one hit group. rayPipelineInfo.groupCount = static_cast(m_rtShaderGroups.size()); rayPipelineInfo.pGroups = m_rtShaderGroups.data(); -```` +~~~~ The ray generation and closest hit shaders can trace rays, making the ray tracing a potentially recursive process. To allow the underlying RTX layer to optimize the pipeline we indicate the maximum recursion depth used by our shaders. For @@ -1410,36 +1393,36 @@ the simplistic shaders we currently have, we set this depth to 1, meaning that w recursion at all (i.e. a hit shader calling `TraceRayEXT()`). Note that it is preferable to keep the recursion level as low as possible, replacing it by a loop formulation instead. -```` C +~~~~ C rayPipelineInfo.maxPipelineRayRecursionDepth = 1; // Ray depth rayPipelineInfo.layout = m_rtPipelineLayout; vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline); -```` +~~~~ Once the pipeline has been created we discard the shader modules: -```` C +~~~~ C for(auto& s : stages) vkDestroyShaderModule(m_device, s.module, nullptr); } -```` +~~~~ The pipeline layout and the pipeline itself also have to be cleaned up upon closing, hence we add this to `destroyResources`: -```` C +~~~~ C vkDestroyPipeline(m_device, m_rtPipeline, nullptr); vkDestroyPipelineLayout(m_device, m_rtPipelineLayout, nullptr); -```` +~~~~ ## main In the `main` function, we call the pipeline construction after the other ray tracing calls: -```` C +~~~~ C helloVk.createRtPipeline(); -```` +~~~~ # Shader Binding Table @@ -1483,15 +1466,15 @@ The buffer will have the following structure, which will later be used when call We first add the declarations of the SBT creation method and the SBT buffer itself in the `HelloVulkan` class: -```` C +~~~~ C void createRtShaderBindingTable(); nvvkBuffer m_rtSBTBuffer; -```` +~~~~ In this function, we start by computing the size of the binding table from the number of groups and the aligned handle size so that we can allocate the SBT buffer. -```` C +~~~~ C //-------------------------------------------------------------------------------------------------- // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer @@ -1506,14 +1489,14 @@ void HelloVulkan::createRtShaderBindingTable() uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); // Bytes needed for the SBT. uint32_t sbtSize = groupCount * groupSizeAligned; -```` +~~~~ We then fetch the handles to the shader groups of the pipeline, and let the allocator allocate the device memory and copy the handles into the SBT. Note that SBT buffer need the `VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR` flag and since we will need the address of SBT buffer, therefore the buffer need also the `VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT` flag. -```` C +~~~~ C // 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); @@ -1540,13 +1523,13 @@ of SBT buffer, therefore the buffer need also the `VK_BUFFER_USAGE_SHADER_DEVICE m_alloc.finalizeAndReleaseStaging(); } -```` +~~~~ As with other resources, we destroy the SBT in `destroyResources`: -```` C +~~~~ C m_alloc.destroy(m_rtSBTBuffer); -```` +~~~~ !!! Warning Size and Alignment Gotcha Pay close attention to the calculation of `groupSizeAligned` (the stride used for array entries). @@ -1580,21 +1563,21 @@ As with other resources, we destroy the SBT in `destroyResources`: In the `main` function, we now add the construction of the Shader Binding Table: -```` C +~~~~ C helloVk.createRtShaderBindingTable(); -```` +~~~~ # Ray Tracing Let's create a function that will record commands to call the ray trace shaders. First, add the declaration to the header -```` C +~~~~ C void raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor); -```` +~~~~ We first bind the pipeline and its layout, and set the push constants that will be available throughout the pipeline: -```` C +~~~~ C m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } @@ -1604,22 +1587,21 @@ m_alloc.finalizeAndReleaseStaging(); // void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) { -m_debug.beginLabel(cmdBuf, "Ray trace"); -// Initializing 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_debug.beginLabel(cmdBuf, "Ray trace"); + // Initializing push constant values + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; - -std::vector descSets{m_rtDescSet, m_descSet}; -vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); -vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipelineLayout, 0, - (uint32_t)descSets.size(), descSets.data(), 0, nullptr); -vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, - VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); -```` + std::vector descSets{m_rtDescSet, m_descSet}; + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipelineLayout, 0, + (uint32_t)descSets.size(), descSets.data(), 0, nullptr); + vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, + VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, + 0, sizeof(PushConstantRay), &m_pcRay); +~~~~ Since the structure of the Shader Binding Table is up to the developer, we need to indicate the ray tracing pipeline how to interpret it. In particular we compute the offsets in the SBT where the ray generation shader, miss shaders and hit @@ -1635,21 +1617,20 @@ The location for each array of the SBT is passed as a `VkStridedDeviceAddressReg * The size in bytes of the entire array -```` C +~~~~ C // Size of a program identifier uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); uint32_t groupStride = groupSize; -VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO}; -info.buffer = m_rtSBTBuffer.buffer; -VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); +VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; +VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); using Stride = VkStridedDeviceAddressRegionKHR; std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 1}, // miss Stride{sbtAddress + 2u * groupSize, groupStride, groupSize * 1}, // hit Stride{0u, 0u, 0u}}; // callable -```` +~~~~ !!! NOTE Separate Arrays For this simple example, as we are not storing user data in the SBT, each array of the SBT has the same stride. @@ -1662,13 +1643,13 @@ type: ray generation, miss shaders, hit groups, and callable shaders (outside th three parameters are equivalent to the grid size of a compute launch, and represent the total number of threads. Since we want to trace one ray per pixel, the grid size has the width and height of the output image, and a depth of 1. -```` C +~~~~ C vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], m_size.width, m_size.height, 1); m_debug.endLabel(cmdBuf); } -```` +~~~~ !!! TIP Raygen shader selection If you built a pipeline with multiple raygen shaders, the raygen shader can be selected by changing the @@ -1691,21 +1672,21 @@ pipeline and the shader binding table. Let's try to make images from this. In the `main` function, we will define a local variable to switch between rasterization and ray tracing. Add the following right after the ray tracing initialization calls: -```` C +~~~~ C bool useRaytracer = true; -```` +~~~~ In the same function, we will add a UI checkbox to make that switch at runtime. Right after the line `ImGui::ColorEdit3(`, we add -```` C +~~~~ C ImGui::Checkbox("Ray Tracer mode", &useRaytracer); // Switch between raster and ray tracing -```` +~~~~ A few lines below, you can find a block containing the `helloVk.rasterize` call. Since our application will now have two render modes, we replace that block by -```` C +~~~~ C // Rendering Scene if(useRaytracer) { @@ -1717,7 +1698,7 @@ else helloVk.rasterize(cmdBuf); vkCmdEndRenderPass(cmdBuf); } -```` +~~~~ Note that the ray tracing behaves more like a compute shader than a graphics task, and is then outside of a render pass. @@ -1728,60 +1709,37 @@ Raster | | Ray Trace :-----------------------------:|:---:|:--------------------------------: ![](Images/resultRasterCube.png width="350px") | <-> | ![](Images/resultRaytraceEmptyCube.png width="350px") -# Camera Setup +# Camera Matrices -In the context of rasterization, the vertices of the objects are projected from their world-space position into a -$[0,1]\times[0,1]\times[0,1]$ cube, before being rasterized on the XY plane. For ray tracing, we need to initialize some -rays at the camera position, and intersect the geometry in world space. To achieve this, we need to store the inverse -view and projection matrices in the `CameraMatrices` at the beginning of the `hello_vulkan.cpp` file: +The matrices of the camera are stored in a uniform buffer and updated in the function `updateUniformBuffer`. +This matrices are also needed for ray tracing, therefore we need to change the usage stage flag to include +ray tracing. -```` C -struct CameraMatrices -{ - nvmath::mat4f view; - nvmath::mat4f proj; - nvmath::mat4f viewInverse; - // #VKRay - nvmath::mat4f projInverse; -}; -```` - -Since the camera matrices will be used by the RayGen, see next sub section, the descriptorSet need to also have -the usage flag to include that stage. This was done in section Additions to the Scene Descriptor Set - -## updateUniformBuffer - -The computation of the matrix inverses is done in `updateUniformBuffer`, after setting the `hostUBO.proj` matrix: - -```` C -// #VKRay -hostUBO.projInverse = nvmath::invert(hostUBO.proj); -```` - -We also have to indicate that the UBO will be used in the raytracing shaders. -```` C +~~~~ C auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; -```` +~~~~ ## Ray generation (raytrace.rgen) +We need to include new files. Since the `#include` directive is a GLSL extension, we will add: + +~~~~ C++ +#extension GL_GOOGLE_include_directive : enable +~~~~ + It is now time to enrich the ray generation shader to allow it to trace rays. We will first add a new binding to allow the shader to access the camera matrices. -```` C -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; -```` +~~~~ C +#include "host_device.h" + +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +~~~~ + !!! Note: Binding - The buffer of camera uses `binding = 0` as described in `createDescriptorSetLayout()`. The + The buffer of camera uses `binding = 0` as described in `host_device.h`. The `set = 1` comes from the fact that it is the second descriptor set passed to - `pipelineLayoutCreateInfo.setPSetLayouts`. + `pipelineLayoutCreateInfo.pSetLayouts` in `HelloVulkan::createRtPipeline()`. When tracing a ray, the hit or miss shaders need to be able to return some information to the shader program that invoked the ray tracing. This is done through the use of a payload, identified by the `rayPayloadEXT` qualifier. @@ -1798,52 +1756,50 @@ struct hitPayload }; ~~~~ -We now modify `raytrace.rgen` to include this new file. Note that the `#include` directive is a GLSL extension, which -we also enable: +We now modify `raytrace.rgen` to include this new file. ~~~~ C++ -#extension GL_GOOGLE_include_directive : enable #include "raycommon.glsl" ~~~~ The payload, identified with `rayPayloadEXT` is then our `hitPayload` structure. -```` C +~~~~ C layout(location = 0) rayPayloadEXT hitPayload prd; -```` +~~~~ The `main` function of the shader then starts by computing the floating-point pixel coordinates, normalized between 0 and 1. The `gl_LaunchIDEXT` contains the integer coordinates of the pixel being rendered, while `gl_LaunchSizeEXT` corresponds to the image size provided when calling `traceRayEXT`. -```` C +~~~~ C void main() { const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); const vec2 inUV = pixelCenter/vec2(gl_LaunchSizeEXT.xy); vec2 d = inUV * 2.0 - 1.0; -```` +~~~~ From the pixel coordinates, we can apply the inverse transformation of the view and projection matrices of the camera to obtain the origin and direction of the ray. -```` C - 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); -```` +~~~~ C + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); +~~~~ In addition, we provide some flags for the ray: first. a flag indicating that all geometry will be considered opaque, as we also indicated when creating the acceleration structures. We also indicate the minimum and maximum distance of the potential intersections along the ray. Those distances can be useful to reduce the ray tracing costs if intersections before or after a given point do not matter. A typical use case is for computing ambient occlusion. -```` C +~~~~ C uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; float tMax = 10000.0; -```` +~~~~ We now trace the ray itself by calling `traceRayEXT`. This takes as arguments @@ -1874,7 +1830,7 @@ We now trace the ray itself by calling `traceRayEXT`. This takes as arguments which can be particularly useful for recursive ray tracers. -```` C +~~~~ C traceRayEXT(topLevelAS, // acceleration structure rayFlags, // rayFlags 0xFF, // cullMask @@ -1887,14 +1843,14 @@ We now trace the ray itself by calling `traceRayEXT`. This takes as arguments tMax, // ray max range 0 // payload (location = 0) ); -```` +~~~~ Finally, we write the resulting payload into the output image. -```` C +~~~~ C imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.0)); } -```` +~~~~ Raster | | Ray Trace :-----------------------------:|:---:|:--------------------------------: @@ -1935,7 +1891,7 @@ To share the clear color of the rasterization with the ray tracer, we will chang return the clear value passed as a push constant. While the `Constants` struct contains more members, here we use the fact that `clearColor` is the first member in the struct, and do not even declare the subsequent members. -```` C +~~~~ C #extension GL_GOOGLE_include_directive : enable #include "raycommon.glsl" @@ -1950,7 +1906,7 @@ void main() { prd.hitValue = clearColor.xyz * 0.8; } -```` +~~~~ !!! Note: The color of the background is slightly darker to differentiate the two renderers. @@ -1972,48 +1928,36 @@ the vertex and index buffers directly in the closest hit shader, via the scene d We first include the payload definition and the OBJ-Wavefront structures -```` C +~~~~ C #extension GL_EXT_scalar_block_layout : enable #extension GL_GOOGLE_include_directive : enable #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_EXT_buffer_reference2 : require #include "raycommon.glsl" #include "wavefront.glsl" -```` +~~~~ Then we describe the resources according to the descriptor set layout -```` C +~~~~ C layout(location = 0) rayPayloadInEXT hitPayload prd; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -```` +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; -In the Hit shader we need all the members of the push constant block: - -```` C -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; -```` +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +~~~~ In the `main` function, the `gl_InstanceCustomIndexEXT` tells which object was hit, and the `gl_PrimitiveID` allows us to find the vertices of the triangle hit by the ray: -```` C +~~~~ C void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -2026,61 +1970,60 @@ void main() Vertex v0 = vertices.v[ind.x]; Vertex v1 = vertices.v[ind.y]; Vertex v2 = vertices.v[ind.z]; -```` +~~~~ -Using the hit point's barycentric coordinates, we can interpolate the normal: - -```` C +Computing the barycentric coordinates is done the following way +~~~~ C 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))); -```` +~~~~ The world-space position could be calculated in two ways, the first one being to use the information from the hit shader. But this could have precision issues if the hit point is very far. -```` C +~~~~ C vec3 worldPos = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; -```` +~~~~ -Another solution, more precise, consists in computing the position by interpolation, as for the normal +Another solution, more precise, consists in computing the position by interpolation. +We are using the state materices provided on the hit. Those matrices are compute +using the information provided we we set the TLAS and BLAS. Note that all our BLASes +didn't apply any transformation, only the instances. -```` C - // 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)); -```` +~~~~ C +// Computing the coordinates of the hit position +const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; +const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space +~~~~ + +We can do the same thing for the normal + +~~~~C +// Computing the normal at hit position +const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; +const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space +~~~~ The light source specified in the constants can then be used to compute the dot product of the normal with the lighting direction, giving a simple diffuse lighting effect: -```` C +~~~~ C // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } - else // Directional light + else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } - - float dotNL = max(dot(normal, L), 0.2); - - prd.hitValue = vec3(dotNL); -} -```` +~~~~ ![](Images/resultRaytraceLightGreyCube.png width="350px") @@ -2096,9 +2039,9 @@ These materials define their basic reflectance properties using simple color coe The buffer containing the materials has already been created for rasterization, and has also been added into the ray tracing descriptor set. Add the binding of the array of texture samplers: -```` C -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -```` +~~~~ C +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; +~~~~ The declaration of the material is the same as that used for the rasterizer and is defined in `wavefront.glsl`. @@ -2107,18 +2050,18 @@ The `Vertex` structure contains a material index, which we will use to find the We first remove these lines at the end of `main()` -```` C +~~~~ C float dotNL = max(dot(normal, L), 0.2); prd.hitValue = vec3(dotNL); -```` +~~~~ and fetch the material definition instead: -```` C +~~~~ C // Material of the object int matIdx = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIdx]; -```` +~~~~ !!! Note Note There is one buffer of materials per object, and each material can be access via the index. @@ -2127,7 +2070,7 @@ and fetch the material definition instead: From that material definition, we use the diffuse and specular reflectances to compute diffuse lighting. This code also supports textures to modulate the surface albedo. -```` C +~~~~ C // Diffuse vec3 diffuse = computeDiffuse(mat, L, normal); if(mat.textureId >= 0) @@ -2140,13 +2083,13 @@ supports textures to modulate the surface albedo. // Specular vec3 specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); -```` +~~~~ The final lighting is then computed as -```` C +~~~~ C prd.hitValue = vec3(lightIntensity * (diffuse + specular)); -```` +~~~~ ![](Images/resultRaytraceLightMatCube.png width="350px") @@ -2155,17 +2098,17 @@ The final lighting is then computed as The OBJ model is loaded in `main.cpp` by calling `helloVk.loadModel`. Let's load something more interesting than a cube: -```` C +~~~~ 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)); -```` +~~~~ Since that model is larger, we can change the `CameraManip.setLookat` call to -```` C +~~~~ C CameraManip.setLookat(nvmath::vec3f(4, 4, 4), nvmath::vec3f(0, 1, 0), nvmath::vec3f(0, 1, 0)); -```` +~~~~ ![](Images/resultRaytraceLightMatMedieval.png) @@ -2189,7 +2132,7 @@ the GLSL file. In the body of `createRtPipeline`, we need to define the new miss shader right after the previous miss shader: -```` C +~~~~ C enum StageIndices { eRaygen, @@ -2198,36 +2141,36 @@ enum StageIndices eClosestHit, eShaderGroupCount }; -```` +~~~~ And create the stage -```` C +~~~~ C // The second miss shader is invoked when a shadow ray misses the geometry. It simply indicates that no occlusion has been found stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytraceShadow.rmiss.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; stages[eMiss2] = stage; -```` +~~~~ After pushing the miss shader `missSM`, we also push the miss shader for the shadow rays: -```` C +~~~~ C // Shadow Miss group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; group.generalShader = eMiss2; m_rtShaderGroups.push_back(group); -```` +~~~~ The pipeline now has to allow shooting rays from the closest hit program, which requires increasing the recursion level to 2: -```` C +~~~~ C // The ray tracing process can shoot rays from the camera, and a shadow ray can be shot from the // hit points of the camera rays, hence a recursion level of 2. This number should be kept as low // as possible for performance reasons. Even recursive ray tracing should be flattened into a loop // in the ray generation to avoid deep recursion. rayPipelineInfo.maxPipelineRayRecursionDepth = 2; // Ray depth -```` +~~~~ !!! WARNING Recursion Limit @@ -2240,12 +2183,12 @@ The pipeline now has to allow shooting rays from the closest hit program, which 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 + ~~~~ 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`. @@ -2273,37 +2216,36 @@ Therefore, we have to change `HelloVulkan::raytrace` to adjust the the closest h This also points out that in real-world applications the SBT should be embedded so that it can handle those offsets automatically. -```` C +~~~~ C Stride{sbtAddress + 3u * groupSize, groupStride, groupSize * 1}, // hit - Jump over the raygen and 2 miss shaders -```` +~~~~ ## `createRtDescriptorSet` For each resource entry in the descriptor set, we indicated which shader stage would be able to use it. Since shadow rays will be traced from the closest hit shader, we add `VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR` to the acceleration structure binding: -```` C - // Top-level acceleration structure, usable by both the ray generation and the closest hit (to - // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, +~~~~ C + // Top-level acceleration structure, usable by both the ray generation and the closest hit (to shoot shadow rays) + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS -```` +~~~~ ## `raytrace.rchit` The closest hit shader now needs to be aware of the acceleration structure to be able to shoot rays: -```` C -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -```` +~~~~ C +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +~~~~ Those rays will also carry a payload, which will need to be defined at a different location from the payload of the current ray. In this case, the payload will be a simple Boolean value indicating whether an occluder has been found or not: -```` C +~~~~ C layout(location = 1) rayPayloadEXT bool isShadowed; -```` +~~~~ In the `main` function, instead of simply setting our payload to `prd.hitValue = c;`, we will initiate a new ray. To select the shadow miss shader, we will pass `missIndex=1` instead of `0` to `traceRayEXT()`. The payload location @@ -2324,7 +2266,7 @@ Shadow rays only need to be cast if the light is in front of the surface, and sp if we are in shadow (since the light source won't be visible from the shading point). The code that previously computed the specular term will then look like this: -```` C +~~~~ C vec3 specular = vec3(0); float attenuation = 1; @@ -2361,13 +2303,13 @@ the specular term will then look like this: specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); } } -```` +~~~~ The final payload value can then be adjusted depending on the result of the shadow ray: -```` C +~~~~ C prd.hitValue = vec3(lightIntensity * attenuation * (diffuse + specular)); -```` +~~~~ ![](Images/resultRaytraceShadowMedieval.png) diff --git a/media/scenes/cube.obj b/media/scenes/cube.obj index 3ed8da9..a5a7381 100644 --- a/media/scenes/cube.obj +++ b/media/scenes/cube.obj @@ -1,7 +1,3 @@ -# NVIDIA Iray Viewer OBJ exporter - -#Error: Object scnCamera of type ELEMENT_TYPE_CAMERA cannot be exported - mtllib cube.mtl o cube diff --git a/media/scenes/cube_modif.obj b/media/scenes/cube_modif.obj new file mode 100644 index 0000000..00d2ec6 --- /dev/null +++ b/media/scenes/cube_modif.obj @@ -0,0 +1,41 @@ +mtllib cube.mtl + +o cube +v -0.5 -0.5 -0.5 +v -0.5 -0.5 0.5 +v -0.5 0.5 -0.5 +v -0.5 0.5 0.5 +v 0.5 -0.5 -0.5 +v 0.5 -0.5 0.5 +v 1.0 1.0 -1.0 +v 1.0 1.0 1.0 + + +vn -1 0 0 +vn 0 0 1 +vn 1 0 0 +vn 0 0 -1 +vn 0 -1 0 +vn 0 1 0 + + +vt 0 0 0 +vt 1 0 0 +vt 1 1 0 +vt 0 1 0 + + +f 1/1/1 2/2/1 4/3/1 +f 1/1/1 4/3/1 3/4/1 +f 2/1/2 6/2/2 8/3/2 +f 2/1/2 8/3/2 4/4/2 +f 6/1/3 5/2/3 7/3/3 +f 6/1/3 7/3/3 8/4/3 +f 5/1/4 1/2/4 3/3/4 +f 5/1/4 3/3/4 7/4/4 +f 5/1/5 6/2/5 2/3/5 +f 5/1/5 2/3/5 1/4/5 +f 3/1/6 4/2/6 8/3/6 +f 3/1/6 8/3/6 7/4/6 + + diff --git a/media/scenes/cube_multi.obj b/media/scenes/cube_multi.obj index 0a44775..150eb24 100644 --- a/media/scenes/cube_multi.obj +++ b/media/scenes/cube_multi.obj @@ -1,7 +1,3 @@ -# NVIDIA Iray Viewer OBJ exporter - -#Error: Object scnCamera of type ELEMENT_TYPE_CAMERA cannot be exported - mtllib cube_multi.mtl o cube diff --git a/media/scenes/sphere.obj b/media/scenes/sphere.obj index 4458bf6..c696a9c 100644 --- a/media/scenes/sphere.obj +++ b/media/scenes/sphere.obj @@ -1,7 +1,3 @@ -# NVIDIA Iray Viewer OBJ exporter - -#Error: Object scnCamera of type ELEMENT_TYPE_CAMERA cannot be exported - mtllib sphere.mtl o sphere_3 diff --git a/ray_tracing__advance/hello_vulkan.cpp b/ray_tracing__advance/hello_vulkan.cpp index 7bd4320..9180bbe 100644 --- a/ray_tracing__advance/hello_vulkan.cpp +++ b/ray_tracing__advance/hello_vulkan.cpp @@ -42,18 +42,6 @@ extern std::vector defaultSearchPaths; - -// 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 @@ -76,16 +64,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -101,7 +90,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -121,18 +110,19 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); // Storing implicit obj (binding = 3) - m_descSetLayoutBind.addBinding(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + m_descSetLayoutBind.addBinding(eImplicits, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); @@ -150,11 +140,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -162,7 +152,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); VkDescriptorBufferInfo dbiImplDesc{m_implObjects.implBuf.buffer, 0, VK_WHOLE_SIZE}; writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 3, &dbiImplDesc)); @@ -177,7 +167,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstants)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -237,30 +227,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -270,9 +265,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -281,15 +276,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -375,8 +370,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); m_alloc.destroy(m_implObjects.implBuf); m_alloc.destroy(m_implObjects.implMatBuf); @@ -418,15 +413,16 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_graphicsPipeline); vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - uint32_t nbInst = static_cast(m_objInstance.size() - 1); // Remove the implicit object + auto nbInst = static_cast(m_instances.size() - 1); // Remove the implicit object for(uint32_t i = 0; i < nbInst; ++i) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstants.instanceId = i; // Telling which instance is drawn + auto& inst = m_instances[i]; + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstants), &m_pushConstants); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -462,7 +458,7 @@ void HelloVulkan::initOffscreen() void HelloVulkan::initRayTracing() { m_raytrace.createBottomLevelAS(m_objModel, m_implObjects); - m_raytrace.createTopLevelAS(m_objInstance, m_implObjects); + m_raytrace.createTopLevelAS(m_instances, m_implObjects); m_raytrace.createRtDescriptorSet(m_offscreen.colorTexture().descriptor.imageView); m_raytrace.createRtPipeline(m_descSetLayout); } @@ -473,10 +469,10 @@ void HelloVulkan::initRayTracing() void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) { updateFrame(); - if(m_pushConstants.frame >= m_maxFrames) + if(m_pcRaster.frame >= m_maxFrames) return; - m_raytrace.raytrace(cmdBuf, clearColor, m_descSet, m_size, m_pushConstants); + m_raytrace.raytrace(cmdBuf, clearColor, m_descSet, m_size, m_pcRaster); } //-------------------------------------------------------------------------------------------------- @@ -497,12 +493,12 @@ void HelloVulkan::updateFrame() refCamMatrix = m; refFov = fov; } - m_pushConstants.frame++; + m_pcRaster.frame++; } void HelloVulkan::resetFrame() { - m_pushConstants.frame = -1; + m_pcRaster.frame = -1; } @@ -560,9 +556,13 @@ void HelloVulkan::createImplictBuffers() m_debug.setObjectName(m_implObjects.implBuf.buffer, "implicitObj"); m_debug.setObjectName(m_implObjects.implMatBuf.buffer, "implicitMat"); - // Adding an instance to hold the buffer address of implicit materials + + // Adding an extra instance to get access to the material buffers + ObjDesc objDesc{}; + objDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_implObjects.implMatBuf.buffer); + m_objDesc.emplace_back(objDesc); + ObjInstance instance{}; - instance.objIndex = static_cast(m_objModel.size()); - instance.materials = nvvk::getBufferDeviceAddress(m_device, m_implObjects.implMatBuf.buffer); - m_objInstance.emplace_back(instance); + instance.objIndex = static_cast(m_objModel.size()); + m_instances.emplace_back(instance); } diff --git a/ray_tracing__advance/hello_vulkan.h b/ray_tracing__advance/hello_vulkan.h index 8a628f5..86b467a 100644 --- a/ray_tracing__advance/hello_vulkan.h +++ b/ray_tracing__advance/hello_vulkan.h @@ -22,6 +22,7 @@ #include "nvvk/appbase_vk.hpp" #include "nvvk/debug_util_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -65,7 +66,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -76,11 +77,22 @@ public: Raytracer& raytracer() { return m_raytrace; } - ObjPushConstants m_pushConstants; + // Information pushed at each draw call + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + {1, 0, 0}, // lightDirection; + {cos(deg2rad(12.5f))}, // lightSpotCutoff; + {cos(deg2rad(17.5f))}, // lightSpotOuterCutoff; + 100.f, // light intensity + 0 // light type + }; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances // Graphic pipeline @@ -91,18 +103,18 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - int m_maxFrames{10}; + int m_maxFrames{500}; void resetFrame(); void updateFrame(); - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene - nvvk::DebugUtil m_debug; // Utility to name objects - Allocator m_alloc; // Allocator for buffer, images, acceleration structures + Allocator m_alloc; // Allocator for buffer, images, acceleration structures + nvvk::DebugUtil m_debug; // Utility to name objects // #Post Offscreen m_offscreen; diff --git a/ray_tracing__advance/main.cpp b/ray_tracing__advance/main.cpp index 52719b9..2424501 100644 --- a/ray_tracing__advance/main.cpp +++ b/ray_tracing__advance/main.cpp @@ -59,35 +59,37 @@ void renderUI(HelloVulkan& helloVk) changed |= ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - changed |= ImGui::RadioButton("Point", &helloVk.m_pushConstants.lightType, 0); + auto& pc = helloVk.m_pcRaster; + + changed |= ImGui::RadioButton("Point", &pc.lightType, 0); ImGui::SameLine(); - changed |= ImGui::RadioButton("Spot", &helloVk.m_pushConstants.lightType, 1); + changed |= ImGui::RadioButton("Spot", &pc.lightType, 1); ImGui::SameLine(); - changed |= ImGui::RadioButton("Infinite", &helloVk.m_pushConstants.lightType, 2); + changed |= ImGui::RadioButton("Infinite", &pc.lightType, 2); - if(helloVk.m_pushConstants.lightType < 2) + if(pc.lightType < 2) { - changed |= ImGui::SliderFloat3("Light Position", &helloVk.m_pushConstants.lightPosition.x, -20.f, 20.f); + changed |= ImGui::SliderFloat3("Light Position", &pc.lightPosition.x, -20.f, 20.f); } - if(helloVk.m_pushConstants.lightType > 0) + if(pc.lightType > 0) { - changed |= ImGui::SliderFloat3("Light Direction", &helloVk.m_pushConstants.lightDirection.x, -1.f, 1.f); + changed |= ImGui::SliderFloat3("Light Direction", &pc.lightDirection.x, -1.f, 1.f); } - if(helloVk.m_pushConstants.lightType < 2) + if(pc.lightType < 2) { - changed |= ImGui::SliderFloat("Light Intensity", &helloVk.m_pushConstants.lightIntensity, 0.f, 500.f); + changed |= ImGui::SliderFloat("Light Intensity", &pc.lightIntensity, 0.f, 500.f); } - if(helloVk.m_pushConstants.lightType == 1) + if(pc.lightType == 1) { - float dCutoff = rad2deg(acos(helloVk.m_pushConstants.lightSpotCutoff)); - float dOutCutoff = rad2deg(acos(helloVk.m_pushConstants.lightSpotOuterCutoff)); + float dCutoff = rad2deg(acos(pc.lightSpotCutoff)); + float dOutCutoff = rad2deg(acos(pc.lightSpotOuterCutoff)); changed |= ImGui::SliderFloat("Cutoff", &dCutoff, 0.f, 45.f); changed |= ImGui::SliderFloat("OutCutoff", &dOutCutoff, 0.f, 45.f); dCutoff = dCutoff > dOutCutoff ? dOutCutoff : dCutoff; - helloVk.m_pushConstants.lightSpotCutoff = cos(deg2rad(dCutoff)); - helloVk.m_pushConstants.lightSpotOuterCutoff = cos(deg2rad(dOutCutoff)); + pc.lightSpotCutoff = cos(deg2rad(dCutoff)); + pc.lightSpotOuterCutoff = cos(deg2rad(dOutCutoff)); } } @@ -196,20 +198,19 @@ int main(int argc, char** argv) std::mt19937 gen(rd()); // Standard mersenne_twister_engine seeded with rd() std::normal_distribution dis(2.0f, 2.0f); std::normal_distribution disn(0.5f, 0.2f); - int wusonIndex = static_cast(helloVk.m_objModel.size() - 1); + auto wusonIndex = static_cast(helloVk.m_objModel.size() - 1); for(int n = 0; n < 50; ++n) { - ObjInstance inst = helloVk.m_objInstance[wusonIndex]; + ObjInstance inst; inst.objIndex = wusonIndex; - inst.txtOffset = 0; float scale = fabsf(disn(gen)); nvmath::mat4f mat = nvmath::translation_mat4(nvmath::vec3f{dis(gen), 0.f, dis(gen) + 6}); // mat = mat * nvmath::rotation_mat4_x(dis(gen)); - mat = mat * nvmath::scale_mat4(nvmath::vec3f(scale)); - inst.transform = mat; - inst.transformIT = nvmath::transpose(nvmath::invert((inst.transform))); - helloVk.m_objInstance.push_back(inst); + mat = mat * nvmath::scale_mat4(nvmath::vec3f(scale)); + inst.transform = mat; + + helloVk.m_instances.push_back(inst); } // Creation of implicit geometry @@ -238,7 +239,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing__advance/obj.hpp b/ray_tracing__advance/obj.hpp index 9cd4e5b..762fa80 100644 --- a/ray_tracing__advance/obj.hpp +++ b/ray_tracing__advance/obj.hpp @@ -20,6 +20,7 @@ #pragma once #include "obj_loader.h" + // The OBJ model struct ObjModel { @@ -31,31 +32,12 @@ struct ObjModel nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; -// Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices{0}; - VkDeviceAddress indices{0}; - VkDeviceAddress materials{0}; - VkDeviceAddress materialIndices{0}; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; -// Information pushed at each draw call -struct ObjPushConstants -{ - nvmath::vec3f lightPosition{10.f, 15.f, 8.f}; - float lightIntensity{100.f}; - nvmath::vec3f lightDirection{-1, -1, -1}; - float lightSpotCutoff{cos(deg2rad(12.5f))}; - float lightSpotOuterCutoff{cos(deg2rad(17.5f))}; - int instanceId{0}; // To retrieve the transformation matrix - int lightType{0}; // 0: point, 1: infinite - int frame{0}; -}; enum EObjType { diff --git a/ray_tracing__advance/raytrace.cpp b/ray_tracing__advance/raytrace.cpp index e0200b7..e7d959c 100644 --- a/ray_tracing__advance/raytrace.cpp +++ b/ray_tracing__advance/raytrace.cpp @@ -69,7 +69,7 @@ auto Raytracer::objectToVkGeometryKHR(const ObjModel& model) VkDeviceAddress indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); triangles.indexType = VK_INDEX_TYPE_UINT32; @@ -163,26 +163,26 @@ void Raytracer::createTopLevelAS(std::vector& instances, ImplInst& tlas.reserve(instances.size()); for(uint32_t i = 0; i < nbObj; i++) { - VkAccelerationStructureInstanceKHR rayInst; + VkAccelerationStructureInstanceKHR rayInst{}; rayInst.transform = nvvk::toTransformMatrixKHR(instances[i].transform); // Position of the instance rayInst.instanceCustomIndex = instances[i].objIndex; // gl_InstanceCustomIndexEXT rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(instances[i].objIndex); - rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 + rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects tlas.emplace_back(rayInst); } // Add the blas containing all implicit if(!implicitObj.objImpl.empty()) { - VkAccelerationStructureInstanceKHR rayInst; + VkAccelerationStructureInstanceKHR rayInst{}; rayInst.transform = nvvk::toTransformMatrixKHR(implicitObj.transform); // Position of the instance - rayInst.instanceCustomIndex = nbObj; // Same for material index + rayInst.instanceCustomIndex = instances[nbObj].objIndex; rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(static_cast(implicitObj.blasId)); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 1; // We will use the same hit group for all objects (the second one) - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } @@ -196,9 +196,9 @@ void Raytracer::createRtDescriptorSet(const VkImageView& outputImage) { using vkDSLB = VkDescriptorSetLayoutBinding; - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); m_rtDescSetLayout = m_rtDescSetLayoutBind.createLayout(m_device); @@ -217,8 +217,8 @@ void Raytracer::createRtDescriptorSet(const VkImageView& outputImage) 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -230,7 +230,7 @@ void Raytracer::createRtDescriptorSet(const VkImageView& outputImage) void Raytracer::updateRtDescriptorSet(const VkImageView& outputImage) { VkDescriptorImageInfo imageInfo{{}, outputImage, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -362,7 +362,7 @@ void Raytracer::createRtPipeline(VkDescriptorSetLayout& sceneDescLayout) // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR | VK_SHADER_STAGE_CALLABLE_BIT_KHR, - 0, sizeof(RtPushConstants)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -408,18 +408,18 @@ void Raytracer::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor, VkDescriptorSet& sceneDescSet, VkExtent2D& size, - ObjPushConstants& sceneConstants) + PushConstantRaster& sceneConstants) { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing push constant values - m_rtPushConstants.clearColor = clearColor; - m_rtPushConstants.lightPosition = sceneConstants.lightPosition; - m_rtPushConstants.lightIntensity = sceneConstants.lightIntensity; - m_rtPushConstants.lightDirection = sceneConstants.lightDirection; - m_rtPushConstants.lightSpotCutoff = sceneConstants.lightSpotCutoff; - m_rtPushConstants.lightSpotOuterCutoff = sceneConstants.lightSpotOuterCutoff; - m_rtPushConstants.lightType = sceneConstants.lightType; - m_rtPushConstants.frame = sceneConstants.frame; + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = sceneConstants.lightPosition; + m_pcRay.lightIntensity = sceneConstants.lightIntensity; + m_pcRay.lightDirection = sceneConstants.lightDirection; + m_pcRay.lightSpotCutoff = sceneConstants.lightSpotCutoff; + m_pcRay.lightSpotOuterCutoff = sceneConstants.lightSpotOuterCutoff; + m_pcRay.lightType = sceneConstants.lightType; + m_pcRay.frame = sceneConstants.frame; std::vector descSets{m_rtDescSet, sceneDescSet}; @@ -429,7 +429,7 @@ void Raytracer::raytrace(const VkCommandBuffer& cmdBuf, vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR | VK_SHADER_STAGE_CALLABLE_BIT_KHR, - 0, sizeof(RtPushConstants), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); auto& regions = m_sbtWrapper.getRegions(); diff --git a/ray_tracing__advance/raytrace.hpp b/ray_tracing__advance/raytrace.hpp index b7ce768..a78fbe1 100644 --- a/ray_tracing__advance/raytrace.hpp +++ b/ray_tracing__advance/raytrace.hpp @@ -24,6 +24,8 @@ #include "nvvk/sbtwrapper_vk.hpp" #include "obj.hpp" +#include "shaders/host_device.h" + class Raytracer { public: @@ -41,7 +43,7 @@ public: const nvmath::vec4f& clearColor, VkDescriptorSet& sceneDescSet, VkExtent2D& size, - ObjPushConstants& sceneConstants); + PushConstantRaster& sceneConstants); private: nvvk::ResourceAllocator* m_alloc{nullptr}; // Allocator for buffer, images, acceleration structures @@ -62,15 +64,6 @@ private: VkPipeline m_rtPipeline; nvvk::Buffer m_rtSBTBuffer; - struct RtPushConstants - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - nvmath::vec3f lightDirection{-1, -1, -1}; - float lightSpotCutoff{deg2rad(12.5f)}; - float lightSpotOuterCutoff{deg2rad(17.5f)}; - int lightType{0}; - int frame{0}; - } m_rtPushConstants; + + PushConstantRay m_pcRay{}; }; diff --git a/ray_tracing__advance/shaders/frag_shader.frag b/ray_tracing__advance/shaders/frag_shader.frag index d0bec87..18e613d 100644 --- a/ray_tracing__advance/shaders/frag_shader.frag +++ b/ray_tracing__advance/shaders/frag_shader.frag @@ -29,91 +29,86 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - uint instanceId; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 LightDir; float lightIntensity; // Point light - if(pushC.lightType == 0) + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRaster.lightIntensity / (lightDistance * lightDistance); LightDir = normalize(lDir); } - else if(pushC.lightType == 1) + else if(pcRaster.lightType == 1) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRaster.lightIntensity / (lightDistance * lightDistance); LightDir = normalize(lDir); - float theta = dot(LightDir, normalize(-pushC.lightDirection)); - float epsilon = pushC.lightSpotCutoff - pushC.lightSpotOuterCutoff; - float spotIntensity = clamp((theta - pushC.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); + float theta = dot(LightDir, normalize(-pcRaster.lightDirection)); + float epsilon = pcRaster.lightSpotCutoff - pcRaster.lightSpotOuterCutoff; + float spotIntensity = clamp((theta - pcRaster.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); lightIntensity *= spotIntensity; } else // Directional light { - LightDir = normalize(-pushC.lightDirection); + LightDir = normalize(-pcRaster.lightDirection); lightIntensity = 1.0; } // Diffuse vec3 diffuse = computeDiffuse(mat, LightDir, N); + if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } + // Specular - vec3 specular = computeSpecular(mat, viewDir, LightDir, N); + vec3 specular = computeSpecular(mat, i_viewDir, LightDir, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing__advance/shaders/host_device.h b/ray_tracing__advance/shaders/host_device.h new file mode 100644 index 0000000..e169a97 --- /dev/null +++ b/ray_tracing__advance/shaders/host_device.h @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2, // Access to textures + eImplicits = 3 // Implicit objects +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + vec3 lightDirection; + float lightSpotCutoff; + float lightSpotOuterCutoff; + float lightIntensity; + int lightType; + int frame; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + uint objIndex; + vec3 lightDirection; + float lightSpotCutoff; + float lightSpotOuterCutoff; + float lightIntensity; + int lightType; + int frame; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing__advance/shaders/light_inf.rcall b/ray_tracing__advance/shaders/light_inf.rcall index e92a591..d87c038 100644 --- a/ray_tracing__advance/shaders/light_inf.rcall +++ b/ray_tracing__advance/shaders/light_inf.rcall @@ -20,24 +20,21 @@ #version 460 core #extension GL_EXT_ray_tracing : enable #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "host_device.h" layout(location = 3) callableDataInEXT rayLight cLight; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - int lightType; + PushConstantRay pcRay; }; void main() { cLight.outLightDistance = 10000000; cLight.outIntensity = 1.0; - cLight.outLightDir = normalize(-lightDirection); + cLight.outLightDir = normalize(-pcRay.lightDirection); } diff --git a/ray_tracing__advance/shaders/light_point.rcall b/ray_tracing__advance/shaders/light_point.rcall index dd9d7ca..f9b3069 100644 --- a/ray_tracing__advance/shaders/light_point.rcall +++ b/ray_tracing__advance/shaders/light_point.rcall @@ -20,25 +20,23 @@ #version 460 core #extension GL_EXT_ray_tracing : enable #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "host_device.h" layout(location = 3) callableDataInEXT rayLight cLight; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - int lightType; + PushConstantRay pcRay; }; + void main() { - vec3 lDir = lightPosition - cLight.inHitPosition; + vec3 lDir = pcRay.lightPosition - cLight.inHitPosition; cLight.outLightDistance = length(lDir); - cLight.outIntensity = lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); + cLight.outIntensity = pcRay.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); cLight.outLightDir = normalize(lDir); } diff --git a/ray_tracing__advance/shaders/light_spot.rcall b/ray_tracing__advance/shaders/light_spot.rcall index 3198f19..0d33568 100644 --- a/ray_tracing__advance/shaders/light_spot.rcall +++ b/ray_tracing__advance/shaders/light_spot.rcall @@ -20,29 +20,26 @@ #version 460 core #extension GL_EXT_ray_tracing : enable #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "host_device.h" layout(location = 3) callableDataInEXT rayLight cLight; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - int lightType; + PushConstantRay pcRay; }; void main() { - vec3 lDir = lightPosition - cLight.inHitPosition; + vec3 lDir = pcRay.lightPosition - cLight.inHitPosition; cLight.outLightDistance = length(lDir); - cLight.outIntensity = lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); + cLight.outIntensity = pcRay.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); cLight.outLightDir = normalize(lDir); - float theta = dot(cLight.outLightDir, normalize(-lightDirection)); - float epsilon = lightSpotCutoff - lightSpotOuterCutoff; - float spotIntensity = clamp((theta - lightSpotOuterCutoff) / epsilon, 0.0, 1.0); + float theta = dot(cLight.outLightDir, normalize(-pcRay.lightDirection)); + float epsilon = pcRay.lightSpotCutoff - pcRay.lightSpotOuterCutoff; + float spotIntensity = clamp((theta - pcRay.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); cLight.outIntensity *= spotIntensity; } diff --git a/ray_tracing__advance/shaders/raytrace.rahit b/ray_tracing__advance/shaders/raytrace.rahit index ac65427..cd513f2 100644 --- a/ray_tracing__advance/shaders/raytrace.rahit +++ b/ray_tracing__advance/shaders/raytrace.rahit @@ -36,13 +36,13 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; // clang-format on void main() { // Object of this instance - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); diff --git a/ray_tracing__advance/shaders/raytrace.rchit b/ray_tracing__advance/shaders/raytrace.rchit index b3da007..ed73d49 100644 --- a/ray_tracing__advance/shaders/raytrace.rchit +++ b/ray_tracing__advance/shaders/raytrace.rchit @@ -22,7 +22,6 @@ #extension GL_EXT_nonuniform_qualifier : enable #extension GL_EXT_scalar_block_layout : enable #extension GL_GOOGLE_include_directive : enable - #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_EXT_buffer_reference2 : require @@ -39,22 +38,13 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; + +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; // clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - int lightType; -} -pushC; layout(location = 3) callableDataEXT rayLight cLight; @@ -62,7 +52,7 @@ layout(location = 3) callableDataEXT rayLight cLight; void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -81,45 +71,44 @@ void main() // 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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfoIT * vec4(normal, 0.0))); - + normal = normalize(vec3(normal * gl_WorldToObjectEXT)); // 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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + worldPos = vec3(gl_ObjectToWorldEXT * vec4(worldPos, 1.0)); cLight.inHitPosition = worldPos; //#define DONT_USE_CALLABLE #if defined(DONT_USE_CALLABLE) // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - cLight.inHitPosition; + vec3 lDir = pcRay.lightPosition - cLight.inHitPosition; float lightDistance = length(lDir); - cLight.outIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + cLight.outIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); cLight.outLightDir = normalize(lDir); cLight.outLightDistance = lightDistance; } - else if(pushC.lightType == 1) + else if(pcRay.lightType == 1) { - vec3 lDir = pushC.lightPosition - cLight.inHitPosition; + vec3 lDir = pcRay.lightPosition - cLight.inHitPosition; cLight.outLightDistance = length(lDir); - cLight.outIntensity = pushC.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); + cLight.outIntensity = pcRay.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); cLight.outLightDir = normalize(lDir); - float theta = dot(cLight.outLightDir, normalize(-pushC.lightDirection)); - float epsilon = pushC.lightSpotCutoff - pushC.lightSpotOuterCutoff; - float spotIntensity = clamp((theta - pushC.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); + float theta = dot(cLight.outLightDir, normalize(-pcRay.lightDirection)); + float epsilon = pcRay.lightSpotCutoff - pcRay.lightSpotOuterCutoff; + float spotIntensity = clamp((theta - pcRay.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); cLight.outIntensity *= spotIntensity; } else // Directional light { - cLight.outLightDir = normalize(-pushC.lightDirection); + cLight.outLightDir = normalize(-pcRay.lightDirection); cLight.outIntensity = 1.0; cLight.outLightDistance = 10000000; } #else - executeCallableEXT(pushC.lightType, 3); + executeCallableEXT(pcRay.lightType, 3); #endif // Material of the object @@ -131,7 +120,7 @@ void main() vec3 diffuse = computeDiffuse(mat, cLight.outLightDir, normal); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -183,6 +172,5 @@ void main() prd.rayDir = rayDir; } - prd.hitValue = vec3(cLight.outIntensity * attenuation * (diffuse + specular)); } diff --git a/ray_tracing__advance/shaders/raytrace.rgen b/ray_tracing__advance/shaders/raytrace.rgen index 3fbddc8..97a4d37 100644 --- a/ray_tracing__advance/shaders/raytrace.rgen +++ b/ray_tracing__advance/shaders/raytrace.rgen @@ -20,42 +20,27 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "random.glsl" #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; - -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - int lightType; - int frame; -} -pushC; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on const int NBSAMPLES = 5; void main() { // Initialize the random number - uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pushC.frame * NBSAMPLES); + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pcRay.frame * NBSAMPLES); prd.seed = seed; vec3 hitValues = vec3(0); @@ -67,7 +52,7 @@ void main() float r2 = rnd(seed); // Subpixel jitter: send the ray through a different position inside the pixel // each time, to provide antialiasing. - vec2 subpixel_jitter = pushC.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); + vec2 subpixel_jitter = pcRay.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + subpixel_jitter; @@ -75,9 +60,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsNoneEXT; float tMin = 0.001; @@ -120,9 +105,9 @@ void main() prd.hitValue = hitValues / NBSAMPLES; // Do accumulation over time - if(pushC.frame >= 0) + if(pcRay.frame >= 0) { - float a = 1.0f / float(pushC.frame + 1); + float a = 1.0f / float(pcRay.frame + 1); vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, prd.hitValue, a), 1.f)); } diff --git a/ray_tracing__advance/shaders/raytrace.rint b/ray_tracing__advance/shaders/raytrace.rint index 8c163c7..4e654c0 100644 --- a/ray_tracing__advance/shaders/raytrace.rint +++ b/ray_tracing__advance/shaders/raytrace.rint @@ -30,7 +30,7 @@ hitAttributeEXT vec3 HitAttribute; -layout(binding = 3, set = 1, scalar) buffer allImpl_ +layout(set = 1, binding = eImplicits, scalar) buffer allImpl_ { Implicit i[]; } @@ -77,6 +77,7 @@ float hitAabb(const Aabb aabb, const Ray r) void main() { + Ray ray; ray.origin = gl_WorldRayOriginEXT; ray.direction = gl_WorldRayDirectionEXT; diff --git a/ray_tracing__advance/shaders/raytrace.rmiss b/ray_tracing__advance/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing__advance/shaders/raytrace.rmiss +++ b/ray_tracing__advance/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing__advance/shaders/raytrace2.rahit b/ray_tracing__advance/shaders/raytrace2.rahit index 38ce667..4200bff 100644 --- a/ray_tracing__advance/shaders/raytrace2.rahit +++ b/ray_tracing__advance/shaders/raytrace2.rahit @@ -36,8 +36,8 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 3, set = 1, scalar) buffer allImplicits_ {Implicit i[];} allImplicits; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eImplicits, scalar) buffer allImplicits_ {Implicit i[];} allImplicits; // clang-format on void main() @@ -45,7 +45,7 @@ void main() // Material of the object Implicit impl = allImplicits.i[gl_PrimitiveID]; - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; Materials materials = Materials(objResource.materialAddress); WaveFrontMaterial mat = materials.m[impl.matId]; diff --git a/ray_tracing__advance/shaders/raytrace2.rchit b/ray_tracing__advance/shaders/raytrace2.rchit index 70f440f..187f02f 100644 --- a/ray_tracing__advance/shaders/raytrace2.rchit +++ b/ray_tracing__advance/shaders/raytrace2.rchit @@ -41,22 +41,12 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 3, set = 1, scalar) buffer allImplicits_ {Implicit i[];} allImplicits; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eImplicits, scalar) buffer allImplicits_ {Implicit i[];} allImplicits; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; // clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - int lightType; -} -pushC; layout(location = 3) callableDataEXT rayLight cLight; @@ -92,10 +82,10 @@ void main() } cLight.inHitPosition = worldPos; - executeCallableEXT(pushC.lightType, 3); + executeCallableEXT(pcRay.lightType, 3); // Material of the object - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; Materials materials = Materials(objResource.materialAddress); WaveFrontMaterial mat = materials.m[impl.matId]; diff --git a/ray_tracing__advance/shaders/vert_shader.vert b/ray_tracing__advance/shaders/vert_shader.vert index a46389c..40baa80 100644 --- a/ray_tracing__advance/shaders/vert_shader.vert +++ b/ray_tracing__advance/shaders/vert_shader.vert @@ -26,41 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - uint instanceId; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -70,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing__advance/shaders/wavefront.glsl b/ray_tracing__advance/shaders/wavefront.glsl index 76149d4..b326f8a 100644 --- a/ray_tracing__advance/shaders/wavefront.glsl +++ b/ray_tracing__advance/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing__before/README.md b/ray_tracing__before/README.md index 4c1fb6d..3eac54e 100644 --- a/ray_tracing__before/README.md +++ b/ray_tracing__before/README.md @@ -1,8 +1,142 @@ # NVIDIA Vulkan Ray Tracing Tutorial -This example is a simple OBJ viewer in Vulkan, without any ray tracing functionality. -It is the starting point of the ray tracing tutorial, the source of the application in which ray tracing will be added. +This sample is a simple OBJ viewer in Vulkan, without any ray tracing functionality. +It is the starting point of the [ray tracing tutorial](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/), +the source of the application where ray tracing will be added. +![](images/vk_ray_tracing__before.png) -## [**Start Ray Tracing Tutorial**](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/) +Before starting the tutorial and adding what is necessary to enable ray tracing, here is a brief description of how this basic example was created. +## Structure -![resultRaytraceShadowMedieval](../docs/Images/resultRasterCube.png) +* main.cpp : creates a Vulkan context, call for loading OBJ files, drawing loop +* hello_vulkan.[cpp|h]: example class that loads and store OBJ in Vulkan buffers, load textures, creates pipelines and render the scene +* [nvvk](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk): library of independent Vulkan helpers. + +## Overview + +The following describes the function calls in main(). The helpers will not be described here, but following the link should give more details on how they work. + +### Window and Vulkan Setup + +* Creates a window using [GLFW](https://www.glfw.org/). +* Creates a Vulkan context using the [`nvvk::Context`](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#context_vkhpp) + helper. The result is a `VkInstance`, `VkDevice`, `VkPhisicalDevice`, the queues and the extensions initialized. + * The window was created, but not the surface to draw in this window. This is done using the information from `VkInstance` and `GLFWwindow`. The + `VkSurfaceKHR` will be used to find the proper queue. + +### Sample Setup + +* Hello vulkan setup: + * [base] Keep a copy of `VkInstance`, `VkDevice`, `VkPhysicalDevice` and graphics Queue Index. + * [base] Create a command pool + * Initialize the [Vulkan resource allocator](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#resourceallocator_vkhpp) (nvvk::alloc). +* Create Swapchain [base]: + * Using the base class method and the help of [`nvvk::Swapchain`](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#swapchain_vkhpp), + initialize the swapchain for rendering onto the surface. + * Finds the surface color and depth format. + * Create *n* `VkFence` for synchronization. +* Create depth buffer [base]: depth buffer image with the format query in the swapchain +* Create render pass [base]: + * Simple render pass, with two attachments: color and depth + * Using swapchain color and depth format. +* Create frame buffers [base]: + * Create *n images* of the number returned by the swapchain + * Create as many frame buffers and attach the images +* Initialize GUI [base]: we are using [Dear ImGui](https://github.com/ocornut/imgui) and + this is doing the initialization for Vulkan. +* Loading [Wavefront .obj file](https://en.wikipedia.org/wiki/Wavefront_.obj_file): + * This is a very simple format, but it allows us to make manual modifications in the file, to load several .obj files and to combine them to obtain a more complex scene. We can also easily create several instances of the loaded objects. + * Loading the .obj is done through the [ObjLoader](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/blob/3e399adf0a3e991795fbdf91e0e6c23b9f492bb8/common/obj_loader.cpp#L26) which is using [tinyobjloader](https://github.com/tinyobjloader/tinyobjloader). + * All vertices are in the form of: position, normal, color, texture coordinates + * Every 3 vertex indices are creating a triangle. + * Every triangle has an index of the material it is using. + * There are M materials and T textures. + * We allocate buffers and staging the transfer to the GPU using our resource allocator. + * We create an instance of the loaded model. + * :warning: We keep the addresses of the created buffers and append this to a vector of `ObjDesc`, which allows easy access to the model information from a shader. + +### Offscreen image & Graphic Pipeline + +We have a swapchain and window frame buffers, but we will not render the scene directly to them. Instead, we will render to an RGBA32F image, and then render that image to a full screen triangle using a tone mapper. We could have rendered directly to one of the swapchain framebuffers and applied a tone mapper, but this separation will be very useful for rendering G-Buffers and doing something similar with ray tracing. + +* Create offscreen render: + * Create a color and depth image, format are VK_FORMAT_R32G32B32A32_SFLOAT and best depth for the physical device using [`nvvk::findDepthFormat`](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#renderpasses_vkhpp). + * Create a render pass to render using those images [`nvvk::createRenderPass`](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#renderpasses_vkhpp). + * Create a frame buffer, to use as a target when rendering offline. +* Create descriptor set layout: + * Using the [`nvvk::DescriptorSetBindings`](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#class-nvvkdescriptorsetbindings), describing the resources that will be used in the vertex and fragment shader. +* Create graphic pipeline: + * Using [`nvvk::GraphicsPipelineGeneratorCombined`](https://github.com/nvpro-samples/nvpro_core/tree/master/nvvk#class-nvvkgraphicspipelinegeneratorcombined) + * Load both vertex and fragment SpirV shaders + * Describe the vertex attributes: pos, nrm, color, uv +* Create uniform buffer: + * The uniform buffer is holding global scene information and is updated at each frame. + * The information in this sample consist of camera matrices and will be + updated in the drawing loop using `updateUniformBuffer()`. +* Create Obj description buffer: + * When loading .obj, we stored the addresses of the OBJ buffers. + * If we have loaded many .obj, the array will be as large as the number of obj loaded. + * Each instance has the index of the OBJ, therefore the information of the model will be easily retrievable in the shader. + * This function creates the buffer holding the vector of `ObjDesc`. +* Update descriptor set: + * The descriptor set has been created, but here we are writing the information, meaning all the buffers and textures we have created. + +At this point, the OBJs are loaded and their information is uploaded to the GPU. The graphic pipeline and the information describing the resources are also created. + +### Post Pipeline + +We need to create a "post pipeline". This is specific to the raster of this sample. As mentioned before, we render in a full screen triangle, the result of the raster and apply a tonemapper. This step will be rendered directly into the framebuffer of the swapchain. + +![](images/base_pipeline.png) + +* Create post descriptor: + * Adding the image input (result of rendering) +* Create post pipeline: + * Passtrough vertex shader (full screen triangle) + * Fragment tone-mapper shader +* Update post descriptor: + * Writing the image (RGB32F) address + +At this point we are ready to start the rendering loop. + +* Setup Glfw callback: + * Our base class has many functions to react on Glfw, such as change of window size, mouse and keyboard inputs. + * All callback functions are virtual, which allow to override the base functionality, but for most there isn't anything to do. + * Except for `onResize`, since we render to an intermediate image (RGB32F), this image will need to be re-created with the new size and the post pipeline will need the address of this image. Therefore, there is an overload of this function. + +### Rendering Loop + +* Render indefinitely until Glfw receive a close action +* Glfw pulling events +* Imgui new frame and rendering (not drawing) +* Prepare Frame + * This is part of the swapchain, we can have a double or triple buffer and + this call will be waiting (fence) to make sure that one swapchain frame buffer + is available. +* Get current frame + * Depending if we have a double or triple buffer, it will return: 0 or 1, or 0, 1 or 2. + * Resources in the base class exist in that amount: color images, frame buffer, command buffer, fence +* Get the command buffer for the current frame (better to re-use command buffers than creating new ones) +* Update the uniform buffer: + * Push the current camera matrices + +#### Offscreen + +* Start offscreen rendering pass + * We will be rendering (raster) in the RGBA32F image using the offscreen render pass and frame buffer. +* Rasterize + * This is rasterizing the entire scene, all instances of each object. + +#### Final + +Start another rendering, this time in the swapchain frame buffer + +* Use the base class render pass +* Use the swapchain frame buffer +* Render a full-screen triangle with the raster image and tone-mapping it. +* Actually rendering Imgui in Vulkan +* Submit the frame for execution and display + +### Clean up + +The rest is clean up all allocations diff --git a/ray_tracing__before/hello_vulkan.cpp b/ray_tracing__before/hello_vulkan.cpp index 28448d0..6ae2aa3 100644 --- a/ray_tracing__before/hello_vulkan.cpp +++ b/ray_tracing__before/hello_vulkan.cpp @@ -40,14 +40,6 @@ extern std::vector defaultSearchPaths; -// Holding the camera matrices -struct CameraMatrices -{ - nvmath::mat4f view; - nvmath::mat4f proj; - nvmath::mat4f viewInverse; -}; - //-------------------------------------------------------------------------------------------------- // Keep the handle on the device // Initialize the tool to do all our allocations: buffers, images @@ -67,14 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT; // Ensure that the modified UBO is not visible to previous frames. @@ -90,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -110,12 +105,15 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT); + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); m_descSetLayout = m_descSetLayoutBind.createLayout(m_device); @@ -131,11 +129,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -143,7 +141,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -155,7 +153,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -213,30 +211,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | flag); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -246,9 +249,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -257,15 +260,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -351,8 +354,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -397,14 +400,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); diff --git a/ray_tracing__before/hello_vulkan.h b/ray_tracing__before/hello_vulkan.h index 66e10de..5e149eb 100644 --- a/ray_tracing__before/hello_vulkan.h +++ b/ray_tracing__before/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" //-------------------------------------------------------------------------------------------------- // Simple rasterizer of OBJ objects @@ -41,7 +42,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -59,32 +60,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -94,8 +90,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -104,7 +100,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); diff --git a/ray_tracing__before/images/base_pipeline.png b/ray_tracing__before/images/base_pipeline.png new file mode 100644 index 0000000000000000000000000000000000000000..19c8a85b3bdcda22fbb1790fc6abb89c9f51fb6a GIT binary patch literal 16631 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
k5pY4I~3TlvcgPJEvYyxKbs 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 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing__before/shaders/host_device.h b/ray_tracing__before/shaders/host_device.h new file mode 100644 index 0000000..a8377f2 --- /dev/null +++ b/ray_tracing__before/shaders/host_device.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing__before/shaders/vert_shader.vert b/ray_tracing__before/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing__before/shaders/vert_shader.vert +++ b/ray_tracing__before/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing__before/shaders/wavefront.glsl b/ray_tracing__before/shaders/wavefront.glsl index 76149d4..b326f8a 100644 --- a/ray_tracing__before/shaders/wavefront.glsl +++ b/ray_tracing__before/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing__simple/hello_vulkan.cpp b/ray_tracing__simple/hello_vulkan.cpp index 58783ea..4b0877f 100644 --- a/ray_tracing__simple/hello_vulkan.cpp +++ b/ray_tracing__simple/hello_vulkan.cpp @@ -39,18 +39,6 @@ extern std::vector defaultSearchPaths; - -// 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 @@ -70,16 +58,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +84,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,13 +104,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -138,11 +128,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -150,7 +140,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -162,7 +152,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -222,30 +212,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -255,9 +250,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -266,15 +261,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -360,8 +355,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -415,14 +410,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -612,7 +607,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -661,19 +656,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -684,11 +682,10 @@ void HelloVulkan::createTopLevelAS() // void HelloVulkan::createRtDescriptorSet() { - // Top-level acceleration structure, usable by both the ray generation and the closest hit (to - // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + // Top-level acceleration structure, usable by both the ray generation and the closest hit (to shoot shadow rays) + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -708,8 +705,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -722,7 +719,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -794,7 +791,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -866,7 +863,7 @@ void HelloVulkan::createRtShaderBindingTable() VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Map the SBT buffer and write in the handles. void* mapped = m_alloc.map(m_rtSBTBuffer); @@ -887,11 +884,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); @@ -899,14 +895,15 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); // Size of a program identifier uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); uint32_t groupStride = groupSize; - VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, m_rtSBTBuffer.buffer}; + VkDeviceAddress sbtAddress = vkGetBufferDeviceAddress(m_device, &info); using Stride = VkStridedDeviceAddressRegionKHR; std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen diff --git a/ray_tracing__simple/hello_vulkan.h b/ray_tracing__simple/hello_vulkan.h index 6bb3a41..65b1332 100644 --- a/ray_tracing__simple/hello_vulkan.h +++ b/ray_tracing__simple/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -44,7 +45,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -62,32 +63,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices{0}; - VkDeviceAddress indices{0}; - VkDeviceAddress materials{0}; - VkDeviceAddress materialIndices{0}; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -97,8 +93,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -107,7 +103,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -150,11 +146,6 @@ public: VkPipeline m_rtPipeline; nvvk::Buffer m_rtSBTBuffer; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{}; }; diff --git a/ray_tracing__simple/main.cpp b/ray_tracing__simple/main.cpp index 8e161f8..738e4ab 100644 --- a/ray_tracing__simple/main.cpp +++ b/ray_tracing__simple/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -164,7 +164,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing__simple/shaders/frag_shader.frag b/ray_tracing__simple/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing__simple/shaders/frag_shader.frag +++ b/ray_tracing__simple/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing__simple/shaders/host_device.h b/ray_tracing__simple/shaders/host_device.h new file mode 100644 index 0000000..a8377f2 --- /dev/null +++ b/ray_tracing__simple/shaders/host_device.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing__simple/shaders/raytrace.rchit b/ray_tracing__simple/shaders/raytrace.rchit index 2b523a7..2eb634e 100644 --- a/ray_tracing__simple/shaders/raytrace.rchit +++ b/ray_tracing__simple/shaders/raytrace.rchit @@ -39,25 +39,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +66,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +97,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +109,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -147,7 +137,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing__simple/shaders/raytrace.rgen b/ray_tracing__simple/shaders/raytrace.rgen index ebae40a..4b8edad 100644 --- a/ray_tracing__simple/shaders/raytrace.rgen +++ b/ray_tracing__simple/shaders/raytrace.rgen @@ -20,21 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "host_device.h" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + void main() { @@ -42,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; diff --git a/ray_tracing__simple/shaders/raytrace.rmiss b/ray_tracing__simple/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing__simple/shaders/raytrace.rmiss +++ b/ray_tracing__simple/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing__simple/shaders/vert_shader.vert b/ray_tracing__simple/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing__simple/shaders/vert_shader.vert +++ b/ray_tracing__simple/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing__simple/shaders/wavefront.glsl b/ray_tracing__simple/shaders/wavefront.glsl index 76149d4..b326f8a 100644 --- a/ray_tracing__simple/shaders/wavefront.glsl +++ b/ray_tracing__simple/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_advanced_compilation/README.md b/ray_tracing_advanced_compilation/README.md index ed7e2de..780981b 100644 --- a/ray_tracing_advanced_compilation/README.md +++ b/ray_tracing_advanced_compilation/README.md @@ -9,30 +9,31 @@ The simplest way of defining ray tracing pipelines is by using monolithic `VkRay This sample introduces the [VK_KHR_pipeline_library](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_pipeline_library.html) extension to create shader libraries that can be separately compiled and reused in ray tracing pipelines. The compilation of the final pipeline is carried out on multiple threads using the [VK_KHR_deferred_host_operations](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_deferred_host_operations.html) extension. -The code below is based on the [`ray_tracing_specialization`](../ray_tracing_specialization) sample, which introduces compile-time variations of a hit shader. - +The code below is based on the [`ray_tracing_specialization`](../ray_tracing_specialization) sample, which introduces compile-time variations of a hit shader. ## Pipeline Library - Using monolithic pipeline definitions all stages are compiled for each pipeline, regardless of potential reuse. The shader groups are then referenced in the Shader Binding Table using their indices: ![](images/regular_pipeline.png) Pipeline libraries are `VkPipeline` objects that cannot be bound directly. Instead, they can be compiled once and linked into as many pipelines as necessary. The Shader Binding Table of the resulting pipeline references the shader groups of the library as if they had been appended to the groups and stages in the main `VkRayTracingPipelineCreateInfo` ![](images/library.png) -We start by adding the device extension in main() +We start by adding the device extension in main() + ~~~~ C contextInfo.addDeviceExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME); ~~~~ Following by adding a new member in the `HelloVulkan` class: + ~~~~ C // Ray tracing shader library VkPipeline m_rtShaderLibrary; ~~~~ In `HelloVulkan::createRtPipeline()` the `StageIndices` enumeration describes the indices of the stages defined in the pipeline creation structure. The hit groups will be moved to our library, hence we remove them from the enumeration: + ~~~~ C enum StageIndices { @@ -44,16 +45,20 @@ In `HelloVulkan::createRtPipeline()` the `StageIndices` enumeration describes th ~~~~ The shader modules will be referenced partly in the main pipeline, and partly in the pipeline library. To ensure proper deletion of the modules after use, we will store their handles in + ~~~~ C // Store the created modules for later cleanup std::vector modules; ~~~~ + Then, after each call to `nvvk::createShaderModule` we store the resulting module: + ~~~~ C modules.push_back(stage.module); ~~~~ The specialization constants sample creates one shader module per specialization. Instead, we load that module once and reuse it for each specialization. Those specializations are then stored in the stages of the pipeline library: + ~~~~ C // Hit Group - Closest Hit // Create many variation of the closest hit @@ -72,6 +77,7 @@ The specialization constants sample creates one shader module per specialization ~~~~ Similarly, the hit groups will be stored in the library by replacing the storage of the hit groups in `m_rtShaderGroups` by: + ~~~~ C // Shader groups for the pipeline library containing the closest hit shaders std::vector libraryShaderGroups; @@ -92,9 +98,11 @@ Similarly, the hit groups will be stored in the library by replacing the storage libraryShaderGroups.push_back(libraryGroup); } ~~~~ -It is important to note that the stage indices are local to the pipeline library, regardless of where they will be used in the final pipeline. Those indices will be later offset depending on the contents of the pipeline. + +It is important to note that the stage indices are local to the pipeline library, regardless of where they will be used in the final pipeline. Those indices will be later offset depending on the contents of the pipeline. Once the groups and stages are defined we can create the pipeline library. After the creation of the ray tracing pipeline layout, we define the base of the library creation information: + ~~~~ C // Creation of the pipeline library object VkRayTracingPipelineCreateInfoKHR pipelineLibraryInfo{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR}; @@ -105,9 +113,11 @@ Once the groups and stages are defined we can create the pipeline library. After // As for the interface the maximum recursion depth must also be consistent across the pipeline pipelineLibraryInfo.maxPipelineRayRecursionDepth = 2; ~~~~ -Pipeline libraries are technically independent from the pipeline they will be linked into. However, linking can only be achieved by enforcing strong consistency constraints, such as having the same pipeline layout and maximum recursion depth. If the recursion depth differs the compilation of the final pipeline will fail. + +Pipeline libraries are technically independent from the pipeline they will be linked into. However, linking can only be achieved by enforcing strong consistency constraints, such as having the same pipeline layout and maximum recursion depth. If the recursion depth differs the compilation of the final pipeline will fail. In addition, the pipeline libraries need to have the same pipeline interface. This interface defines the maximum amount of data passed across stages: + ~~~~ C // Pipeline libraries need to define an interface, defined by the maximum hit attribute size (typically 2 for // the built-in triangle intersector) and the maximum payload size (3 floating-point values in this sample). @@ -119,6 +129,7 @@ In addition, the pipeline libraries need to have the same pipeline interface. Th ~~~~ Finally we provide the stage and shader groups information to the library creation information, and create the pipeline library in the same way as any other pipeline: + ~~~~ C // Shader groups and stages for the library pipelineLibraryInfo.groupCount = static_cast(libraryShaderGroups.size()); @@ -129,7 +140,9 @@ Finally we provide the stage and shader groups information to the library creati // Creation of the pipeline library vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &pipelineLibraryInfo, nullptr, &m_rtShaderLibrary); ~~~~ + The pipeline library is now created, but the application cannot run yet: we still need to indicate that the final pipeline will link with our library. Before calling `vkCreateRayTracingPipelinesKHR` for the final pipeline, we insert the reference to the library: + ~~~~ C // The library will be linked into the final pipeline by specifying its handle and shared interface VkPipelineLibraryCreateInfoKHR inputLibrary{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; @@ -142,6 +155,7 @@ The pipeline library is now created, but the application cannot run yet: we stil The pipeline is now built from the specified shader groups and stages as well as the library containing the hit groups. The groups and stages are linked together using indices, and indices are local to each library. To avoid collisions the pipeline creation will consider the stages of the libraries as if they had been appended to the stage list of the original `VkRayTracingPipelineCreateInfoKHR`, in the order in which the libraries are defined in `pLibraries`. Therefore, the Shader Binding Table needs to be updated accordingly, by making the wrapper aware of the contents of the library: + ~~~~ C // The Shader Binding Table is built accounting for the entire pipeline, including the // stages contained in the library. Passing the library information allows the wrapper @@ -150,6 +164,7 @@ Therefore, the Shader Binding Table needs to be updated accordingly, by making t ~~~~ At the end of the function we destroy the shader modules using our vector of modules instead of iterating over the stages of the main pipeline: + ~~~~ C // Destroy all the created modules, for both libraries and main pipeline for(auto& m : modules) @@ -157,12 +172,13 @@ At the end of the function we destroy the shader modules using our vector of mod ~~~~ The pipeline library has the same lifetime as the pipeline that uses it. The final step of this Section is the destruction of the library in the `HelloVulkan::destroy()` method: + ~~~~ C // Pipeline libraries have the same lifetime as the pipelines that uses them vkDestroyPipeline(m_device, m_rtShaderLibrary, nullptr); ~~~~ -As an exercise, it is possible to create another library containing the other shader stages, and link those libraries together into the pipeline. +As an exercise, it is possible to create another library containing the other shader stages, and link those libraries together into the pipeline. ## Parallel Compilation Using Deferred Host Operations @@ -173,11 +189,13 @@ Ray tracing pipelines are often complex, and can benefit from multithreaded comp ![Deferred Host Operations use app-provided threads to parallelize the compilation](images/deferred_host_operations.png) We start by including the support of C++ threading using `std::async` at the beginning of the source file: + ~~~~ C #include ~~~~ In this sample we will distribute the compilation of the final ray tracing pipeline using a `VkDeferredOperation`, created just before calling `vkCreateRayTracingPipelinesKHR`: + ~~~~ C // Deferred operations allow the driver to parallelize the pipeline compilation on several threads // Create a deferred operation @@ -187,14 +205,17 @@ In this sample we will distribute the compilation of the final ray tracing pipel ~~~~ Then we modify the pipeline creation to indicate we defer the operation: + ~~~~ C // The pipeline creation is called with the deferred operation. Instead of blocking until // the compilation is done, the call returns immediately vkCreateRayTracingPipelinesKHR(m_device, deferredOperation, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline); ~~~~ -Instead of immediately launching the compilation and blocking execution until completion, this call will return immediately with value `VK_OPERATION_DEFERRED_KHR` if deferred operations are supported by the system. + +Instead of immediately launching the compilation and blocking execution until completion, this call will return immediately with value `VK_OPERATION_DEFERRED_KHR` if deferred operations are supported by the system. Threading control is left to the application. Therefore, our application will allocate a number of threads for compilation: + ~~~~ C // The compilation will be split into a maximum of 8 threads, or the maximum supported by the // driver for that operation @@ -203,6 +224,7 @@ Threading control is left to the application. Therefore, our application will al ~~~~ We then launch those threads using `std::async`: + ~~~~ C std::vector> joins; for(uint32_t i = 0; i < threadCount; i++) @@ -222,9 +244,11 @@ We then launch those threads using `std::async`: })); } ~~~~ + Each thread executes a blocking function taking care of a subset of the compilation. When a thread has finished its task, the pipeline compilation may be complete (`VK_SUCCESS`) or there may be no more work for this thread. In this case one could consider executing more work using those threads, such as compiling another pipeline. Since there is only one pipeline to compile, we wait for all threads to finish and check whether the pipeline compilation succeeded: + ~~~~ C // Wait for all threads to finish for(auto& f : joins) @@ -237,17 +261,17 @@ Since there is only one pipeline to compile, we wait for all threads to finish a ~~~~ Once the compilation is finished we can destroy the deferred operation: + ~~~~ C // Destroy the deferred operation vkDestroyDeferredOperationKHR(m_device, deferredOperation, nullptr); ~~~~ -Congratulations! The ray tracing pipeline is now built using explicit stages and a pipeline library, and the final compilation is executed on multiple threads. As an exercise, the pipeline library described at the beginning of this tutorial can also be compiled in parallel. +Congratulations! The ray tracing pipeline is now built using explicit stages and a pipeline library, and the final compilation is executed on multiple threads. As an exercise, the pipeline library described at the beginning of this tutorial can also be compiled in parallel. This approach can be extended to compile multiple pipelines sharing some components using multiple threads: ![](images/high_level_advanced_compilation.png) ## References - * [VK_KHR_pipeline_library](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_pipeline_library.html) - * [VK_KHR_deferred_host_operations](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_deferred_host_operations.html) - +* [VK_KHR_pipeline_library](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_pipeline_library.html) +* [VK_KHR_deferred_host_operations](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_deferred_host_operations.html) diff --git a/ray_tracing_advanced_compilation/hello_vulkan.cpp b/ray_tracing_advanced_compilation/hello_vulkan.cpp index 8fa3a1e..50b4f3c 100644 --- a/ray_tracing_advanced_compilation/hello_vulkan.cpp +++ b/ray_tracing_advanced_compilation/hello_vulkan.cpp @@ -43,17 +43,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -73,16 +62,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -98,7 +88,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -118,13 +108,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 3) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -141,11 +132,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -153,7 +144,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -165,7 +156,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -225,30 +216,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -258,9 +254,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -269,15 +265,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -363,8 +359,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -420,14 +416,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -618,7 +614,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -667,19 +663,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -692,9 +691,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -714,8 +713,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -728,7 +727,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -767,7 +766,7 @@ public: private: std::vector spec_values; std::vector spec_entries; - VkSpecializationInfo spec_info; + VkSpecializationInfo spec_info{}; }; @@ -825,10 +824,10 @@ void HelloVulkan::createRtPipeline() modules.push_back(stage.module); // Store the hit groups for compilation in a separate pipeline library object std::vector libraryStages{}; - for(uint32_t s = 0; s < (uint32_t)specializations.size(); s++) + for(auto& specialization : specializations) { stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; - stage.pSpecializationInfo = specializations[s].getSpecialization(); + stage.pSpecializationInfo = specialization.getSpecialization(); libraryStages.push_back(stage); } @@ -876,7 +875,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -1008,11 +1007,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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.specialization = m_pushConstant.specialization; + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); @@ -1020,7 +1018,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); auto& regions = m_sbtWrapper.getRegions(); vkCmdTraceRaysKHR(cmdBuf, ®ions[0], ®ions[1], ®ions[2], ®ions[3], m_size.width, m_size.height, 1); diff --git a/ray_tracing_advanced_compilation/hello_vulkan.h b/ray_tracing_advanced_compilation/hello_vulkan.h index 093f21b..b2302f3 100644 --- a/ray_tracing_advanced_compilation/hello_vulkan.h +++ b/ray_tracing_advanced_compilation/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -45,7 +46,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -63,33 +64,28 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite - int specialization{7}; // all in use + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; + // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -99,8 +95,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ instances nvvk::Buffer m_bufReference; // Buffer references of the OBJ std::vector m_textures; // vector of all textures of the scene @@ -110,7 +106,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -151,15 +147,9 @@ public: VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; nvvk::SBTWrapper m_sbtWrapper; - // Ray tracing shader library + // Push constant for ray tracer VkPipeline m_rtShaderLibrary; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - int specialization{7}; - } m_rtPushConstants; + + PushConstantRay m_pcRay{{}, {}, 0, 0, 7}; }; diff --git a/ray_tracing_advanced_compilation/main.cpp b/ray_tracing_advanced_compilation/main.cpp index 9ca5b37..4faee4b 100644 --- a/ray_tracing_advanced_compilation/main.cpp +++ b/ray_tracing_advanced_compilation/main.cpp @@ -56,24 +56,24 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } // Specialization - ImGui::SliderInt("Specialization", &helloVk.m_pushConstant.specialization, 0, 7); - int s = helloVk.m_pushConstant.specialization; + ImGui::SliderInt("Specialization", &helloVk.m_pcRay.specialization, 0, 7); + int s = helloVk.m_pcRay.specialization; int a = ((s >> 2) % 2) == 1; int b = ((s >> 1) % 2) == 1; int c = ((s >> 0) % 2) == 1; ImGui::Checkbox("Use Diffuse", (bool*)&a); ImGui::Checkbox("Use Specular", (bool*)&b); ImGui::Checkbox("Trace shadow", (bool*)&c); - helloVk.m_pushConstant.specialization = (a << 2) + (b << 1) + c; + helloVk.m_pcRay.specialization = (a << 2) + (b << 1) + c; } ////////////////////////////////////////////////////////////////////////// @@ -175,7 +175,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_advanced_compilation/shaders/frag_shader.frag b/ray_tracing_advanced_compilation/shaders/frag_shader.frag index 7a01408..0930980 100644 --- a/ray_tracing_advanced_compilation/shaders/frag_shader.frag +++ b/ray_tracing_advanced_compilation/shaders/frag_shader.frag @@ -29,62 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { - // Object of this instance - int objId = sceneDesc.i[pushC.instanceId].objId; - // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -92,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_advanced_compilation/shaders/host_device.h b/ray_tracing_advanced_compilation/shaders/host_device.h new file mode 100644 index 0000000..e7e235e --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/host_device.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int specialization; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_advanced_compilation/shaders/raytrace.rchit b/ray_tracing_advanced_compilation/shaders/raytrace.rchit index a933e4f..66d634c 100644 --- a/ray_tracing_advanced_compilation/shaders/raytrace.rchit +++ b/ray_tracing_advanced_compilation/shaders/raytrace.rchit @@ -39,30 +39,21 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; layout(constant_id = 0) const int USE_DIFFUSE = 1; layout(constant_id = 1) const int USE_SPECULAR = 1; layout(constant_id = 2) const int TRACE_SHADOW = 1; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; // clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int specialization; -} -pushC; - void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -78,32 +69,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -115,10 +103,10 @@ void main() vec3 diffuse = vec3(0); if(USE_DIFFUSE == 1) { - diffuse = computeDiffuse(mat, L, normal); + diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -128,7 +116,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { if(TRACE_SHADOW == 1) { @@ -163,7 +151,7 @@ void main() // Specular if(USE_SPECULAR == 1) { - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } } diff --git a/ray_tracing_advanced_compilation/shaders/raytrace.rgen b/ray_tracing_advanced_compilation/shaders/raytrace.rgen index 655e6d0..c2ba199 100644 --- a/ray_tracing_advanced_compilation/shaders/raytrace.rgen +++ b/ray_tracing_advanced_compilation/shaders/raytrace.rgen @@ -20,31 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int specialization; -} -pushC; void main() { @@ -52,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; @@ -63,7 +53,7 @@ void main() traceRayEXT(topLevelAS, // acceleration structure rayFlags, // rayFlags 0xFF, // cullMask - pushC.specialization, // sbtRecordOffset + pcRay.specialization, // sbtRecordOffset 0, // sbtRecordStride 0, // missIndex origin.xyz, // ray origin diff --git a/ray_tracing_advanced_compilation/shaders/raytrace.rmiss b/ray_tracing_advanced_compilation/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_advanced_compilation/shaders/raytrace.rmiss +++ b/ray_tracing_advanced_compilation/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_advanced_compilation/shaders/vert_shader.vert b/ray_tracing_advanced_compilation/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_advanced_compilation/shaders/vert_shader.vert +++ b/ray_tracing_advanced_compilation/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_advanced_compilation/shaders/wavefront.glsl b/ray_tracing_advanced_compilation/shaders/wavefront.glsl index 76149d4..b326f8a 100644 --- a/ray_tracing_advanced_compilation/shaders/wavefront.glsl +++ b/ray_tracing_advanced_compilation/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_animation/README.md b/ray_tracing_animation/README.md index 95a84f9..6e66a3e 100644 --- a/ray_tracing_animation/README.md +++ b/ray_tracing_animation/README.md @@ -8,8 +8,8 @@ This is an extension of the Vulkan ray tracing [tutorial](https://nvpro-samples. We will implement two animation methods: only the transformation matrices, and animating the geometry itself. - ## Animating the Matrices + This first example shows how we can update the matrices used for instances in the TLAS. ### Creating a Scene @@ -24,26 +24,30 @@ and the acceleration structure does not deal with this well. helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths), nvmath::scale_mat4(nvmath::vec3f(2.f, 1.f, 2.f))); helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths)); - HelloVulkan::ObjInstance inst = helloVk.m_objInstance.back(); + uint32_t wusonId = 1; + nvmath::mat4f identity{1}; for(int i = 0; i < 20; i++) - helloVk.m_objInstance.push_back(inst); -~~~~ + helloVk.m_instances.push_back({identity, wusonId}); +~~~~ ### Animation Function + We want to have all of the Wuson models running in a circle, and we will first modify the rasterizer to handle this. Animating the transformation matrices will be done entirely on the CPU, and we will copy the computed transformation to the GPU. In the next example, the animation will be done on the GPU using a compute shader. Add the declaration of the animation to the `HelloVulkan` class. -~~~~ C++ + +~~~~ C++ void animationInstances(float time); -~~~~ +~~~~ The first part computes the transformations for all of the Wuson models, placing each one behind another. + ~~~~ C++ void HelloVulkan::animationInstances(float time) { - const int32_t nbWuson = static_cast(m_objInstance.size() - 1); + const int32_t nbWuson = static_cast(m_instances.size() - 1); const float deltaAngle = 6.28318530718f / static_cast(nbWuson); const float wusonLength = 3.f; const float radius = wusonLength / (2.f * sin(deltaAngle / 2.0f)); @@ -52,106 +56,73 @@ void HelloVulkan::animationInstances(float time) for(int i = 0; i < nbWuson; i++) { int wusonIdx = i + 1; - ObjInstance& inst = m_objInstance[wusonIdx]; - inst.transform = nvmath::rotation_mat4_y(i * deltaAngle + offset) + auto& transform = m_instances[wusonIdx].transform; + transform = nvmath::rotation_mat4_y(i * deltaAngle + offset) * nvmath::translation_mat4(radius, 0.f, 0.f); - inst.transformIT = nvmath::transpose(nvmath::invert(inst.transform)); } -~~~~ - -Next, we update the buffer that describes the scene, which is used by the rasterizer to set each object's position, and also by the ray tracer to compute shading normals. -~~~~ C++ - // Update the buffer - VkDeviceSize bufferSize = m_objInstance.size() * sizeof(ObjInstance); - nvvk::Buffer stagingBuffer = - m_alloc.createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); - // Copy data to staging buffer - auto* gInst = m_alloc.map(stagingBuffer); - memcpy(gInst, m_objInstance.data(), bufferSize); - m_alloc.unmap(stagingBuffer); - // Copy staging buffer to the Scene Description buffer - nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex); - VkCommandBuffer cmdBuf = genCmdBuf.createCommandBuffer(); - - VkBufferCopy region{0, 0, bufferSize}; - vkCmdCopyBuffer(cmdBuf, stagingBuffer.buffer, m_sceneDesc.buffer, 1, ®ion); - - m_debug.endLabel(cmdBuf); - genCmdBuf.submitAndWait(cmdBuf); - m_alloc.destroy(stagingBuffer); -} ~~~~ - **Note:** - We could have used `cmdBuf.updateBuffer(m_sceneDesc.buffer, 0, m_objInstance)` to - update the buffer, but this function only works for buffers with less than 65,536 bytes. If we had 2000 Wuson models, this - call wouldn't work. - ### Loop Animation + In `main()`, just before the main loop, add a variable to hold the start time. We will use this time in our animation function. ~~~~ C++ auto start = std::chrono::system_clock::now(); -~~~~ +~~~~ Inside the `while` loop, just before calling `appBase.prepareFrame()`, invoke the animation function. ~~~~ C++ std::chrono::duration diff = std::chrono::system_clock::now() - start; helloVk.animationInstances(diff.count()); -~~~~ +~~~~ If you run the application, the Wuson models will be running in a circle when using the rasterizer, but they will still be at their original positions in the ray traced version. We will need to update the TLAS for this. - ## Update TLAS Since we want to update the transformation matrices in the TLAS, we need to keep some of the objects used to create it. -First, move the vector of `nvvk::RaytracingBuilder::Instance` objects from `HelloVulkan::createTopLevelAS()` to the -`HelloVulkan` class. +First, move the vector of `nvvk::RaytracingBuilder::Instance` objects from `HelloVulkan::createTopLevelAS()` to the +`HelloVulkan` class. + ~~~~ C++ std::vector m_tlas; -~~~~ +~~~~ -Make sure to rename it to `m_tlas`, instead of `tlas`. +Make sure to rename it to `m_tlas`, instead of `tlas`. -One important point is that we need to set the TLAS build flags to allow updates, by adding the`VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR` flag. +One important point is that we need to set the TLAS build flags to allow updates, by adding the`VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR` flag. This is absolutely needed, since otherwise the TLAS cannot be updated. ~~~~ C++ +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { - m_tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + m_tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - 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; - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 + rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects m_tlas.emplace_back(rayInst); } m_rtFlags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR | VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR; m_rtBuilder.buildTlas(m_tlas, m_rtFlags); } -~~~~ - -Back in `HelloVulkan::animationInstances()`, we need to copy the new computed transformation -matrices to the vector of `nvvk::RaytracingBuilder::Instance` objects. - -In the `for` loop, add at the end - -~~~~ C++ - nvvk::RaytracingBuilder::Instance& tinst = m_tlas[wusonIdx]; - tinst.transform = inst.transform; ~~~~ -The last point is to call the update at the end of the function. +Back in `HelloVulkan::animationInstances()`, we need to update the TLAS by calling +`buildTlas` with the update to `true`. ~~~~ C++ m_rtBuilder.buildTlas(m_tlas, m_rtFlags, true); @@ -167,9 +138,9 @@ differences are: * The `VkAccelerationStructureBuildGeometryInfoKHR` mode will be set to `VK_BUILD_ACCELERATION_STRUCTURE_MODE_UPDATE_KHR` * We will **not** create the acceleration structure, but reuse it. -* The source and destination of `VkAccelerationStructureCreateInfoKHR` will both use the previously created acceleration structure. +* The source and destination of `VkAccelerationStructureCreateInfoKHR` will both use the previously created acceleration structure. -What is happening is the buffer containing all matrices will be updated and the `vkCmdBuildAccelerationStructuresKHR` will update the acceleration in place. +What is happening is the buffer containing all matrices will be updated and the `vkCmdBuildAccelerationStructuresKHR` will update the acceleration in place. ## BLAS Animation @@ -180,19 +151,22 @@ In the previous chapter, we updated the transformation matrices. In this one we In this chapter, we will animate a sphere. In `main.cpp`, set up the scene like this: ~~~~ C++ - helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths), + helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true), nvmath::scale_mat4(nvmath::vec3f(2.f, 1.f, 2.f))); - helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths)); - HelloVulkan::ObjInstance inst = helloVk.m_objInstance.back(); + helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths, true)); + uint32_t wusonId = 1; + nvmath::mat4f identity{1}; for(int i = 0; i < 5; i++) - helloVk.m_objInstance.push_back(inst); - helloVk.loadModel(nvh::findFile("media/scenes/sphere.obj", defaultSearchPaths)); + { + helloVk.m_instances.push_back({identity, wusonId}); + } + helloVk.loadModel(nvh::findFile("media/scenes/sphere.obj", defaultSearchPaths, true)); ~~~~ Because we now have a new instance, we have to adjust the calculation of the number of Wuson models in `HelloVulkan::animationInstances()`. ~~~~ C++ - const int32_t nbWuson = static_cast(m_objInstance.size() - 2); + const int32_t nbWuson = static_cast(m_instances.size() - 2); // All except sphere and plane ~~~~ ### Compute Shader @@ -279,7 +253,7 @@ Finally, destroy the resources in `HelloVulkan::destroyResources()`: vkDestroyDescriptorSetLayout(m_device, m_compDescSetLayout, nullptr); ~~~~ -### `anim.comp` +### `anim.comp` The compute shader will be simple. We need to add a new shader file, `anim.comp`, to the `shaders` filter in the solution. @@ -347,7 +321,8 @@ The implementation only pushes the current time and calls the compute shader (`d ~~~~ C++ void HelloVulkan::animationObject(float time) { - ObjModel& model = m_objModel[2]; + const uint32_t sphereId = 2; + ObjModel& model = m_objModel[sphereId]; updateCompDescriptors(model.vertexBuffer); @@ -378,17 +353,16 @@ In the rendering loop, **before** the call to `animationInstances`, call the obj helloVk.animationObject(diff.count()); ~~~~ -**Note:** Always update the TLAS when BLAS are modified. This will make sure that the TLAS knows about the new bounding box sizes. - -**Note:** At this point, the object should be animated when using the rasterizer, but should still be immobile when using the ray tracer. +**:warning: Note:** Always update the TLAS when BLAS are modified. This will make sure that the TLAS knows about the new bounding box sizes. +**:warning: Note:** At this point, the object should be animated when using the rasterizer, but should still be immobile when using the ray tracer. ## Update BLAS In `nvvk::RaytracingBuilder` in `raytrace_vkpp.hpp`, we can add a function to update a BLAS whose vertex buffer was previously updated. This function is very similar to the one used for instances, but in this case, there is no buffer transfer to do. ~~~~ C++ - //-------------------------------------------------------------------------------------------------- +//-------------------------------------------------------------------------------------------------- // Refit BLAS number blasIdx from updated buffer contents. // void nvvk::RaytracingBuilderKHR::updateBlas(uint32_t blasIdx, BlasInput& blas, VkBuildAccelerationStructureFlagsKHR flags) @@ -415,12 +389,11 @@ void nvvk::RaytracingBuilderKHR::updateBlas(uint32_t blasIdx, BlasInput& blas, V // Allocate the scratch buffer and setting the scratch info nvvk::Buffer scratchBuffer = - m_alloc->createBuffer(sizeInfo.buildScratchSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR - | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + m_alloc->createBuffer(sizeInfo.buildScratchSize, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); VkBufferDeviceAddressInfo bufferInfo{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO}; bufferInfo.buffer = scratchBuffer.buffer; buildInfos.scratchData.deviceAddress = vkGetBufferDeviceAddress(m_device, &bufferInfo); - + NAME_VK(scratchBuffer.buffer); std::vector pBuildOffset(blas.asBuildOffsetInfo.size()); for(size_t i = 0; i < blas.asBuildOffsetInfo.size(); i++) @@ -440,7 +413,7 @@ void nvvk::RaytracingBuilderKHR::updateBlas(uint32_t blasIdx, BlasInput& blas, V } ~~~~ -The previous function (`updateBlas`) uses geometry information stored in `m_blas`. +The previous function (`updateBlas`) uses geometry information stored in `m_blas`. To be able to re-use this information, we need to keep the structure of `nvvk::RaytracingBuilderKHR::Blas` objects used for its creation. @@ -450,8 +423,8 @@ Move the `nvvk::RaytracingBuilderKHR::Blas` vector from `HelloVulkan::createBott std::vector m_blas; ~~~~ -As with the TLAS, the BLAS needs to allow updates. We will also enable the -`VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR` flag, which indicates that the given +As with the TLAS, the BLAS needs to allow updates. We will also enable the +`VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR` flag, which indicates that the given acceleration structure build should prioritize build time over trace performance. ~~~~ C++ @@ -474,7 +447,8 @@ void HelloVulkan::createBottomLevelAS() Finally, we can add a line at the end of `HelloVulkan::animationObject()` to update the BLAS. ~~~~ C++ -m_rtBuilder.updateBlas(2, m_blas[2], VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR); +m_rtBuilder.updateBlas(sphereId, m_blas[sphereId], + VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR); ~~~~ ![](images/animation2.gif) diff --git a/ray_tracing_animation/hello_vulkan.cpp b/ray_tracing_animation/hello_vulkan.cpp index b2d52c4..e36b035 100644 --- a/ray_tracing_animation/hello_vulkan.cpp +++ b/ray_tracing_animation/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,13 +105,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 3) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -138,11 +129,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -150,7 +141,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -162,7 +153,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -222,30 +213,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -255,9 +251,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -266,15 +262,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -360,8 +356,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -422,14 +418,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -620,7 +616,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -669,17 +665,20 @@ void HelloVulkan::createBottomLevelAS() | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; m_tlas.emplace_back(rayInst); } @@ -694,9 +693,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -716,8 +715,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -730,7 +729,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -801,7 +800,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -849,10 +848,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -861,7 +860,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); auto& regions = m_sbtWrapper.getRegions(); @@ -873,9 +872,12 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c ////////////////////////////////////////////////////////////////////////// // #VK_animation +//-------------------------------------------------------------------------------------------------- +// Making the Wuson running in circle +// void HelloVulkan::animationInstances(float time) { - const auto nbWuson = static_cast(m_objInstance.size() - 2); + const auto nbWuson = static_cast(m_instances.size() - 2); // All except sphere and plane const float deltaAngle = 6.28318530718f / static_cast(nbWuson); const float wusonLength = 3.f; const float radius = wusonLength / (2.f * sin(deltaAngle / 2.0f)); @@ -883,40 +885,25 @@ void HelloVulkan::animationInstances(float time) for(int i = 0; i < nbWuson; i++) { - int wusonIdx = i + 1; - ObjInstance& inst = m_objInstance[wusonIdx]; - inst.transform = nvmath::rotation_mat4_y(i * deltaAngle + offset) * nvmath::translation_mat4(radius, 0.f, 0.f); - inst.transformIT = nvmath::transpose(nvmath::invert(inst.transform)); + int wusonIdx = i + 1; + auto& transform = m_instances[wusonIdx].transform; + transform = nvmath::rotation_mat4_y(i * deltaAngle + offset) * nvmath::translation_mat4(radius, 0.f, 0.f); VkAccelerationStructureInstanceKHR& tinst = m_tlas[wusonIdx]; - tinst.transform = nvvk::toTransformMatrixKHR(inst.transform); + tinst.transform = nvvk::toTransformMatrixKHR(transform); } - // Update the buffer - VkDeviceSize bufferSize = m_objInstance.size() * sizeof(ObjInstance); - nvvk::Buffer stagingBuffer = - m_alloc.createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); - // Copy data to staging buffer - auto* gInst = m_alloc.map(stagingBuffer); - memcpy(gInst, m_objInstance.data(), bufferSize); - m_alloc.unmap(stagingBuffer); - // Copy staging buffer to the Scene Description buffer - nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex); - VkCommandBuffer cmdBuf = genCmdBuf.createCommandBuffer(); - - VkBufferCopy region{0, 0, bufferSize}; - vkCmdCopyBuffer(cmdBuf, stagingBuffer.buffer, m_sceneDesc.buffer, 1, ®ion); - - m_debug.endLabel(cmdBuf); - genCmdBuf.submitAndWait(cmdBuf); - m_alloc.destroy(stagingBuffer); - + // Updating the top level acceleration structure m_rtBuilder.buildTlas(m_tlas, m_rtFlags, true); } +//-------------------------------------------------------------------------------------------------- +// Animating the sphere vertices using a compute shader +// void HelloVulkan::animationObject(float time) { - ObjModel& model = m_objModel[2]; + const uint32_t sphereId = 2; + ObjModel& model = m_objModel[sphereId]; updateCompDescriptors(model.vertexBuffer); @@ -929,7 +916,8 @@ void HelloVulkan::animationObject(float time) vkCmdDispatch(cmdBuf, model.nbVertices, 1, 1); genCmdBuf.submitAndWait(cmdBuf); - m_rtBuilder.updateBlas(2, m_blas[2], VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR); + m_rtBuilder.updateBlas(sphereId, m_blas[sphereId], + VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR | VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR); } ////////////////////////////////////////////////////////////////////////// diff --git a/ray_tracing_animation/hello_vulkan.h b/ray_tracing_animation/hello_vulkan.h index 5bf4a7b..fc645e8 100644 --- a/ray_tracing_animation/hello_vulkan.h +++ b/ray_tracing_animation/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -45,7 +46,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -63,32 +64,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -98,8 +94,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -108,7 +104,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -150,16 +146,11 @@ public: VkPipeline m_rtPipeline; nvvk::SBTWrapper m_sbtWrapper; - std::vector m_tlas; + std::vector m_tlas; std::vector m_blas; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{}; // #VK_animation void animationInstances(float time); diff --git a/ray_tracing_animation/main.cpp b/ray_tracing_animation/main.cpp index 99cb618..1d91765 100644 --- a/ray_tracing_animation/main.cpp +++ b/ray_tracing_animation/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -88,6 +88,7 @@ int main(int argc, char** argv) 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)); @@ -159,16 +160,19 @@ int main(int argc, char** argv) helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true), nvmath::scale_mat4(nvmath::vec3f(2.f, 1.f, 2.f))); helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths, true)); - HelloVulkan::ObjInstance inst = helloVk.m_objInstance.back(); + uint32_t wusonId = 1; + nvmath::mat4f identity{1}; for(int i = 0; i < 5; i++) - helloVk.m_objInstance.push_back(inst); + { + helloVk.m_instances.push_back({identity, wusonId}); + } helloVk.loadModel(nvh::findFile("media/scenes/sphere.obj", defaultSearchPaths, true)); helloVk.createOffscreenRender(); helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_animation/shaders/frag_shader.frag b/ray_tracing_animation/shaders/frag_shader.frag index 139aca6..0930980 100644 --- a/ray_tracing_animation/shaders/frag_shader.frag +++ b/ray_tracing_animation/shaders/frag_shader.frag @@ -29,60 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { - // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -90,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_animation/shaders/host_device.h b/ray_tracing_animation/shaders/host_device.h new file mode 100644 index 0000000..a8377f2 --- /dev/null +++ b/ray_tracing_animation/shaders/host_device.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_animation/shaders/raytrace.rchit b/ray_tracing_animation/shaders/raytrace.rchit index 2b523a7..2eb634e 100644 --- a/ray_tracing_animation/shaders/raytrace.rchit +++ b/ray_tracing_animation/shaders/raytrace.rchit @@ -39,25 +39,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +66,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +97,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +109,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -147,7 +137,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_animation/shaders/raytrace.rgen b/ray_tracing_animation/shaders/raytrace.rgen index ebae40a..4802cd0 100644 --- a/ray_tracing_animation/shaders/raytrace.rgen +++ b/ray_tracing_animation/shaders/raytrace.rgen @@ -20,21 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + void main() { @@ -42,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; diff --git a/ray_tracing_animation/shaders/raytrace.rmiss b/ray_tracing_animation/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_animation/shaders/raytrace.rmiss +++ b/ray_tracing_animation/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_animation/shaders/vert_shader.vert b/ray_tracing_animation/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_animation/shaders/vert_shader.vert +++ b/ray_tracing_animation/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_animation/shaders/wavefront.glsl b/ray_tracing_animation/shaders/wavefront.glsl index 76149d4..b326f8a 100644 --- a/ray_tracing_animation/shaders/wavefront.glsl +++ b/ray_tracing_animation/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_anyhit/README.md b/ray_tracing_anyhit/README.md index f489313..3741dca 100644 --- a/ray_tracing_anyhit/README.md +++ b/ray_tracing_anyhit/README.md @@ -30,7 +30,6 @@ This shader starts like `raytrace.chit`, but uses less information. ~~~~ C++ #version 460 #extension GL_EXT_ray_tracing : require -#extension GL_EXT_nonuniform_qualifier : enable #extension GL_EXT_scalar_block_layout : enable #extension GL_GOOGLE_include_directive : enable @@ -47,11 +46,11 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; // clang-format on ~~~~ - **Note:** + **:warning: Note:** You can find the source of `random.glsl` in the Antialiasing Tutorial [here](../ray_tracing_jitter_cam/README.md#toc1.1). @@ -62,7 +61,7 @@ opaque, we simply return, which means that the hit will be accepted. void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); @@ -139,8 +138,8 @@ add the Any Hit stage index and push back the shader module to the stages. In `createDescriptorSetLayout()`, we need to allow the Any Hit shader to access the scene description buffer ~~~~ C++ - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Obj descriptions + m_descSetLayoutBind.addBinding(eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); ~~~~ @@ -392,7 +391,7 @@ is added. We are skipping the closest hit shader in the trace call, so we can ig ~~~~ - **Note:** Re-Run + **:warning: Note:** Re-Run Everything should work as before, but now it does it right. diff --git a/ray_tracing_anyhit/hello_vulkan.cpp b/ray_tracing_anyhit/hello_vulkan.cpp index b3de3fc..99672d5 100644 --- a/ray_tracing_anyhit/hello_vulkan.cpp +++ b/ray_tracing_anyhit/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,14 +105,15 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - // Textures (binding = 3) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -139,11 +130,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -151,7 +142,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -163,7 +154,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -223,30 +214,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -256,9 +252,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -267,15 +263,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -361,8 +357,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -416,14 +412,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -614,7 +610,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -663,19 +659,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -688,9 +687,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -710,8 +709,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -724,7 +723,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -816,7 +815,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -901,15 +900,15 @@ void HelloVulkan::createRtShaderBindingTable() void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) { updateFrame(); - if(m_rtPushConstants.frame >= m_maxFrames) + if(m_pcRay.frame >= m_maxFrames) return; m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -918,7 +917,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); // Size of a program identifier @@ -959,10 +958,10 @@ void HelloVulkan::updateFrame() refCamMatrix = m; refFov = fov; } - m_rtPushConstants.frame++; + m_pcRay.frame++; } void HelloVulkan::resetFrame() { - m_rtPushConstants.frame = -1; + m_pcRay.frame = -1; } diff --git a/ray_tracing_anyhit/hello_vulkan.h b/ray_tracing_anyhit/hello_vulkan.h index b4758fe..e3a567b 100644 --- a/ray_tracing_anyhit/hello_vulkan.h +++ b/ray_tracing_anyhit/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -44,7 +45,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -62,32 +63,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices{0}; - VkDeviceAddress indices{0}; - VkDeviceAddress materials{0}; - VkDeviceAddress materialIndices{0}; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -97,8 +93,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -107,7 +103,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -152,12 +148,6 @@ public: nvvk::Buffer m_rtSBTBuffer; int m_maxFrames{10000}; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - int frame{0}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{}; }; diff --git a/ray_tracing_anyhit/main.cpp b/ray_tracing_anyhit/main.cpp index 7ff464b..f6b474b 100644 --- a/ray_tracing_anyhit/main.cpp +++ b/ray_tracing_anyhit/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -166,7 +166,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_anyhit/shaders/frag_shader.frag b/ray_tracing_anyhit/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_anyhit/shaders/frag_shader.frag +++ b/ray_tracing_anyhit/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_anyhit/shaders/host_device.h b/ray_tracing_anyhit/shaders/host_device.h new file mode 100644 index 0000000..926e29d --- /dev/null +++ b/ray_tracing_anyhit/shaders/host_device.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int frame; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_anyhit/shaders/raytrace.rahit b/ray_tracing_anyhit/shaders/raytrace.rahit index 31f9e99..2831c6a 100644 --- a/ray_tracing_anyhit/shaders/raytrace.rahit +++ b/ray_tracing_anyhit/shaders/raytrace.rahit @@ -35,13 +35,13 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; // clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); diff --git a/ray_tracing_anyhit/shaders/raytrace.rchit b/ray_tracing_anyhit/shaders/raytrace.rchit index d370d22..2cc3fc1 100644 --- a/ray_tracing_anyhit/shaders/raytrace.rchit +++ b/ray_tracing_anyhit/shaders/raytrace.rchit @@ -39,25 +39,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +66,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +97,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +109,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -149,7 +139,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_anyhit/shaders/raytrace.rgen b/ray_tracing_anyhit/shaders/raytrace.rgen index 31fc449..8f2578f 100644 --- a/ray_tracing_anyhit/shaders/raytrace.rgen +++ b/ray_tracing_anyhit/shaders/raytrace.rgen @@ -20,39 +20,26 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #include "random.glsl" #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; - -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int frame; -} -pushC; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on const int NBSAMPLES = 10; void main() { // Initialize the random number - uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pushC.frame); + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pcRay.frame); vec3 hitValues = vec3(0); @@ -63,21 +50,21 @@ void main() // Subpixel jitter: send the ray through a different position inside the pixel // each time, to provide antialiasing. - vec2 subpixel_jitter = pushC.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); + vec2 subpixel_jitter = pcRay.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + subpixel_jitter; const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsNoneEXT; float tMin = 0.001; float tMax = 10000.0; - prd.seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pushC.frame); + prd.seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pcRay.frame); traceRayEXT(topLevelAS, // acceleration structure rayFlags, // rayFlags @@ -97,9 +84,9 @@ void main() prd.hitValue = hitValues / NBSAMPLES; // Do accumulation over time - if(pushC.frame > 0) + if(pcRay.frame > 0) { - float a = 1.0f / float(pushC.frame + 1); + float a = 1.0f / float(pcRay.frame + 1); vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, prd.hitValue, a), 1.f)); } diff --git a/ray_tracing_anyhit/shaders/raytrace.rmiss b/ray_tracing_anyhit/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_anyhit/shaders/raytrace.rmiss +++ b/ray_tracing_anyhit/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_anyhit/shaders/raytrace_rahit.glsl b/ray_tracing_anyhit/shaders/raytrace_rahit.glsl index 001063e..2f2e38e 100644 --- a/ray_tracing_anyhit/shaders/raytrace_rahit.glsl +++ b/ray_tracing_anyhit/shaders/raytrace_rahit.glsl @@ -39,13 +39,13 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; // clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); diff --git a/ray_tracing_anyhit/shaders/vert_shader.vert b/ray_tracing_anyhit/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_anyhit/shaders/vert_shader.vert +++ b/ray_tracing_anyhit/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_anyhit/shaders/wavefront.glsl b/ray_tracing_anyhit/shaders/wavefront.glsl index 76149d4..b326f8a 100644 --- a/ray_tracing_anyhit/shaders/wavefront.glsl +++ b/ray_tracing_anyhit/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_ao/README.md b/ray_tracing_ao/README.md index 62a3df1..b9187b7 100644 --- a/ray_tracing_ao/README.md +++ b/ray_tracing_ao/README.md @@ -1,6 +1,5 @@ # G-Buffer and Ambient Occlusion - Tutorial - ![](images/ray_tracing_ao.png) ## Tutorial ([Setup](../docs/setup.md)) @@ -10,17 +9,16 @@ This is an extension of the Vulkan ray tracing [tutorial](https://nvpro-samples. This extension to the tutorial is showing how G-Buffers from the fragment shader, can be used in a compute shader to cast ambient occlusion rays using ray queries [(GLSL_EXT_ray_query)](https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_query.txt). -We are using some previous extensions of the tutorial to create this one. +We are using some previous extensions of the tutorial to create this one. + * The usage of `ray query` is from [ray_tracing_rayquery](../ray_tracing_rayquery) * The notion of accumulated frames, is comming from [ray_tracing_jitter_cam](../ray_tracing_jitter_cam) * The creation and dispatch of compute shader was inspired from [ray_tracing_animation](../ray_tracing_animation) - - ## Workflow -The fragment shader no longer just writes to an RGBA buffer for the colored image, but also writes to a G-buffer the position and normal for each fragment. -Then a compute shader takes the G-buffer and sends random ambient occlusion rays into the hemisphere formed by position and normal. +The fragment shader no longer just writes to an RGBA buffer for the colored image, but also writes to a G-buffer the position and normal for each fragment. +Then a compute shader takes the G-buffer and sends random ambient occlusion rays into the hemisphere formed by position and normal. ![](images/Hemisphere_sampling.png) @@ -32,11 +30,9 @@ The following are the buffers are they can be seen in [NSight Graphics](https:// ![](images/buffers.png) - - ## G-Buffer -The framework was already writing to G-Buffers, but was writing to a single `VK_FORMAT_R32G32B32A32_SFLOAT` buffer. In the function `HelloVulkan::createOffscreenRender()`, we will add the creation of two new buffers. One `VK_FORMAT_R32G32B32A32_SFLOAT` to store the position and normal and one `VK_FORMAT_R32_SFLOAT` for the ambient occlusion. +The framework was already writing to G-Buffers, but was writing to a single `VK_FORMAT_R32G32B32A32_SFLOAT` buffer. In the function `HelloVulkan::createOffscreenRender()`, we will add the creation of two new buffers. One `VK_FORMAT_R32G32B32A32_SFLOAT` to store the position and normal and one `VK_FORMAT_R32_SFLOAT` for the ambient occlusion. ~~~~ C++ // The G-Buffer (rgba32f) - position(xyz) / normal(w-compressed) @@ -77,7 +73,7 @@ The render pass for the fragment shader will need two color buffers, therefore w std::vector attachments = {m_offscreenColor.descriptor.imageView, m_gBuffer.descriptor.imageView, m_offscreenDepth.descriptor.imageView}; -``` +``` ### Renderpass @@ -87,7 +83,7 @@ This means that the renderpass in `main()` will have to be modified as well. The std::array clearValues{}; ``` -Since the clear value will be re-used by the offscreen (3 attachments) and the post/UI (2 attachments), we will set the clear values in each section. +Since the clear value will be re-used by the offscreen (3 attachments) and the post/UI (2 attachments), we will set the clear values in each section. ``` // Offscreen render pass @@ -110,21 +106,20 @@ We are omitting the code to compress and decompress the XYZ normal to and from a ``` // Outgoing -layout(location = 0) out vec4 outColor; -layout(location = 1) out vec4 outGbuffer; - +layout(location = 0) out vec4 o_color; +layout(location = 1) out vec4 o_gbuffer; ... - outGbuffer.rgba = vec4(worldPos, uintBitsToFloat(CompressUnitVec(N))); + o_gbuffer.rgba = vec4(worldPos, uintBitsToFloat(CompressUnitVec(N))); ``` ## Ray Tracing -As for the [ray_tracing_rayquery](../ray_tracing_rayquery) sample, we use the VK_KHR_acceleration_structure extension to generate the ray tracing acceleration structure, while the ray tracing itself is carried out in a compute shader. This section remains unchanged compared to the rayquery example. +As for the [ray_tracing_rayquery](../ray_tracing_rayquery) sample, we use the VK_KHR_acceleration_structure extension to generate the ray tracing acceleration structure, while the ray tracing itself is carried out in a compute shader. This section remains unchanged compared to the rayquery example. -## Compute Shader +## Compute Shader -The compute shader will take the G-Buffer containing the position and normal and will randomly shot rays in the hemisphere defined by the normal. +The compute shader will take the G-Buffer containing the position and normal and will randomly shot rays in the hemisphere defined by the normal. ### Descriptor @@ -147,7 +142,8 @@ void HelloVulkan::createCompDescriptors() ~~~ ### Descriptor Update -The function `updateCompDescriptors()` is done separately from the descriptor, because it can happen that some resources + +The function `updateCompDescriptors()` is done separately from the descriptor, because it can happen that some resources are re-created, therefore their address isn't valid and we need to set those values back to the decriptors. For example, when resizing the window and the G-Buffer and AO buffer are resized. @@ -169,16 +165,14 @@ void HelloVulkan::updateCompDescriptors() vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } -~~~~ - +~~~~ ### Pipeline -The creation of the pipeline is identical to the animation tutorial, but we will push a structure to the pushConstant +The creation of the pipeline is identical to the animation tutorial, but we will push a structure to the pushConstant instead of a single float. - -The information we will push, will allow us to play with the AO algorithm. +The information we will push, will allow us to play with the AO algorithm. ~~~~ C++ struct AoControl @@ -191,13 +185,12 @@ struct AoControl }; ~~~~ - ### Dispatch Compute -The first thing we are doing in the `runCompute` is to call `updateFrame()` (see [jitter cam](../ray_tracing_jitter_cam)). +The first thing we are doing in the `runCompute` is to call `updateFrame()` (see [jitter cam](../ray_tracing_jitter_cam)). This sets the current frame index, which allows us to accumulate AO samples over time. -Next, we are adding a `VkImageMemoryBarrier` to be sure the G-Buffer image is ready to be read from the compute shader. +Next, we are adding a `VkImageMemoryBarrier` to be sure the G-Buffer image is ready to be read from the compute shader. ~~~~ C++ // Adding a barrier to be sure the fragment has finished writing to the G-Buffer @@ -215,7 +208,7 @@ Next, we are adding a `VkImageMemoryBarrier` to be sure the G-Buffer image is re VK_DEPENDENCY_DEVICE_GROUP_BIT, 0, nullptr, 0, nullptr, 1, &imgMemBarrier); ~~~~ -Folowing is the call to dispatch the compute shader +Folowing is the call to dispatch the compute shader ~~~~ C++ // Preparing for the compute shader @@ -231,7 +224,7 @@ Folowing is the call to dispatch the compute shader vkCmdDispatch(cmdBuf, (m_size.width + (GROUP_SIZE - 1)) / GROUP_SIZE, (m_size.height + (GROUP_SIZE - 1)) / GROUP_SIZE, 1); ~~~~ -Then we are adding a final barrier to make sure the compute shader is done +Then we are adding a final barrier to make sure the compute shader is done writing the AO so that the fragment shader (post) can use it. ~~~~ C++ @@ -247,7 +240,7 @@ writing the AO so that the fragment shader (post) can use it. The following functions were added to tell which frame we are rendering. The function `updateFrame()` is called only once per frame, and we are doing this in runCompute()/ -And the `resetFrame()` should be called whenever the image is changed, like in `onResize()` or +And the `resetFrame()` should be called whenever the image is changed, like in `onResize()` or after modifying the GUI related to the AO. ~~~~ C++ @@ -275,14 +268,15 @@ void HelloVulkan::resetFrame() { m_frame = -1; } -~~~~ +~~~~ -## Compute Shader +## Compute Shader (code) The compute shader, which can be found under [ao.comp](shaders/ao.comp) is using the [ray query](https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_query.txt) extension. The first thing in `main()` is to check if the invocation is not exceeding the size of the image. -~~~~ + +~~~~C void main() { float occlusion = 0.0; @@ -291,34 +285,33 @@ void main() // Check if not outside boundaries if(gl_GlobalInvocationID.x >= size.x || gl_GlobalInvocationID.y >= size.y) return; -~~~~ +~~~~ -The seed of the random number sequence is initialized using the TEA algorithm, while the random number themselves will be generated using PCG. +The seed of the random number sequence is initialized using the TEA algorithm, while the random number themselves will be generated using PCG. This is a fine when many random numbers are generated from this seed, but tea isn't a random -number generator and if you use only one sample per pixel, you will see correlation and the AO will not look fine because it won't -sample uniformly the entire hemisphere. This could be resolved if the seed was kept over frame, but for this example, we will use +number generator and if you use only one sample per pixel, you will see correlation and the AO will not look fine because it won't +sample uniformly the entire hemisphere. This could be resolved if the seed was kept over frame, but for this example, we will use this simple technique. -~~~~ +~~~~C // Initialize the random number uint seed = tea(size.x * gl_GlobalInvocationID.y + gl_GlobalInvocationID.x, frame_number); ~~~~ Secondly, we are retrieving the position and normal stored in the fragment shader. -~~~~ +~~~~C // Retrieving position and normal vec4 gBuffer = imageLoad(inImage, ivec2(gl_GlobalInvocationID.xy)); ~~~~ The G-Buffer was cleared and we will sample the hemisphere only if a fragment was rendered. In `w` -we stored the compressed normal, which is nonzero only if a normal was actually stored into the pixel. +we stored the compressed normal, which is nonzero only if a normal was actually stored into the pixel. Note that while this compression introduces some level of quantization, it does not result in visible artifacts in this example. The `OffsetRay` can be found in [raycommon.glsl](shaders/raycommon.glsl), and was taken from [Ray Tracing Gems, Ch. 6](http://www.realtimerendering.com/raytracinggems/unofficial_RayTracingGems_v1.7.pdf). This is a convenient way to avoid finding manually the appropriate minimum offset. - -~~~~ +~~~~C // Shooting rays only if a fragment was rendered if(gBuffer != vec4(0)) { @@ -333,15 +326,15 @@ The `OffsetRay` can be found in [raycommon.glsl](shaders/raycommon.glsl), and wa From the normal, we generate the basis (tangent and bitangent) which will be used for sampling. The function `compute_default_basis` is also in [raycommon.glsl](shaders/raycommon.glsl) -~~~~ +~~~~C // Finding the basis (tangent and bitangent) from the normal vec3 n, tangent, bitangent; compute_default_basis(normal, tangent, bitangent); -~~~~ +~~~~ Then we are sampling the hemisphere `rtao_samples` time, using a [cosine weighted sampling](https://people.cs.kuleuven.be/~philip.dutre/GI/) -~~~~ +~~~~C // Sampling hemiphere n-time for(int i = 0; i < rtao_samples; i++) { @@ -359,16 +352,16 @@ Then we are sampling the hemisphere `rtao_samples` time, using a [cosine weighte } ~~~~ -The function `TraceRay` is a very simple way to send a shadow ray using ray query. +The function `TraceRay` is a very simple way to send a shadow ray using ray query. For any type of shadow, we don't care which object we hit as long as the ray hit something before maximum length. For this, we can set the flag to `gl_RayFlagsTerminateOnFirstHitEXT`. -But there is a case where we may want to know the distance of the hit from the closest hit, in this case -the flag is set to `gl_RayFlagsNoneEXT`. +But there is a case where we may want to know the distance of the hit from the closest hit, in this case +the flag is set to `gl_RayFlagsNoneEXT`. -The function returns 0 if it didn't hit anything or a value between 0 and 1, depending on how close the +The function returns 0 if it didn't hit anything or a value between 0 and 1, depending on how close the hit was. It will return 1 if `rtao_distance_based == 0` -~~~~ +~~~~C //---------------------------------------------------------------------------- // Tracing a ray and returning the weight based on the distance of the hit // @@ -401,7 +394,7 @@ float TraceRay(in rayQueryEXT rayQuery, in vec3 origin, in vec3 direction) Similar to the camera jitter example, the result is stored at frame 0 and accumulate over time. -~~~~ +~~~~C // Writting out the AO if(frame_number == 0) { @@ -438,7 +431,7 @@ if(ImGui::CollapsingHeader("Ambient Occlusion")) We have also have added `AoControl aoControl;` somwhere in main() and passing the information to the execution of the compute shader. -~~~~ +~~~~C // Rendering Scene { vkCmdBeginRenderPass(cmdBuf, &offscreenRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); @@ -450,16 +443,16 @@ We have also have added `AoControl aoControl;` somwhere in main() and passing th ## Post shader -The post shader will combine the result of the fragment (color) and the result of the compute shader (ao). -In `createPostDescriptor` we will need to add the descriptor +The post shader will combine the result of the fragment (color) and the result of the compute shader (ao). +In `createPostDescriptor` we will need to add the descriptor -~~~~ +~~~~C m_postDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); ~~~~ And the equivalent in `updatePostDescriptorSet()` -~~~~ +~~~~C writes.push_back(m_postDescSetLayoutBind.makeWrite(m_postDescSet, 1, &m_aoBuffer.descriptor)); ~~~~ @@ -467,16 +460,15 @@ writes.push_back(m_postDescSetLayoutBind.makeWrite(m_postDescSet, 1, &m_aoBuffer Then in the fragment shader of the post process, we need to add the layout for the AO image -~~~~ +~~~~C layout(set = 0, binding = 1) uniform sampler2D aoTxt; ~~~~ -And the image will now be returned as the following +And the image will now be returned as the following -~~~~ +~~~~C vec4 color = texture(noisyTxt, uv); float ao = texture(aoTxt, uv).x; fragColor = pow(color * ao, vec4(gamma)); ~~~~ - diff --git a/ray_tracing_ao/hello_vulkan.cpp b/ray_tracing_ao/hello_vulkan.cpp index 92a6b73..929d7cd 100644 --- a/ray_tracing_ao/hello_vulkan.cpp +++ b/ray_tracing_ao/hello_vulkan.cpp @@ -40,15 +40,6 @@ extern std::vector defaultSearchPaths; -// Holding the camera matrices -struct CameraMatrices -{ - nvmath::mat4f view; - nvmath::mat4f proj; - nvmath::mat4f viewInverse; -}; - - //-------------------------------------------------------------------------------------------------- // Keep the handle on the device // Initialize the tool to do all our allocations: buffers, images @@ -68,14 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -91,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -111,12 +105,13 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); - // Textures (binding = 3) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT); + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT); + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT); m_descSetLayout = m_descSetLayoutBind.createLayout(m_device); @@ -132,11 +127,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -144,7 +139,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -156,7 +151,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -221,30 +216,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -254,9 +254,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -265,15 +265,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -359,8 +359,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -415,14 +415,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -441,10 +441,12 @@ void HelloVulkan::onResize(int /*w*/, int /*h*/) resetFrame(); } + ////////////////////////////////////////////////////////////////////////// // Post-processing ////////////////////////////////////////////////////////////////////////// + //-------------------------------------------------------------------------------------------------- // Creating an offscreen frame buffer and the associated render pass // @@ -649,7 +651,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -698,19 +700,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); diff --git a/ray_tracing_ao/hello_vulkan.h b/ray_tracing_ao/hello_vulkan.h index 35864e4..6365d69 100644 --- a/ray_tracing_ao/hello_vulkan.h +++ b/ray_tracing_ao/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -55,7 +56,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -73,32 +74,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -108,8 +104,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -118,7 +114,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); diff --git a/ray_tracing_ao/main.cpp b/ray_tracing_ao/main.cpp index 63e293d..6c5f1bc 100644 --- a/ray_tracing_ao/main.cpp +++ b/ray_tracing_ao/main.cpp @@ -57,12 +57,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -89,6 +89,7 @@ int main(int argc, char** argv) 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)); @@ -166,7 +167,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); // #VKRay helloVk.initRayTracing(); diff --git a/ray_tracing_ao/shaders/frag_shader.frag b/ray_tracing_ao/shaders/frag_shader.frag index 19be660..88f0e45 100644 --- a/ray_tracing_ao/shaders/frag_shader.frag +++ b/ray_tracing_ao/shaders/frag_shader.frag @@ -30,62 +30,58 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; -layout(location = 1) out vec4 outGbuffer; +layout(location = 0) out vec4 o_color; +layout(location = 1) out vec4 o_gbuffer; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; float lightDistance; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); lightDistance = d; } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition - vec3(0)); lightDistance = 10000; } @@ -95,7 +91,7 @@ void main() diffuse = vec3(1); // if(mat.textureId >= 0) // { - // int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + // int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; // uint txtId = txtOffset + mat.textureId; // vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; // diffuse *= diffuseTxt; @@ -104,9 +100,8 @@ void main() // Specular vec3 specular = vec3(0); //computeSpecular(mat, viewDir, L, N); lightIntensity = 1; + // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); - - - outGbuffer.rgba = vec4(worldPos, uintBitsToFloat(CompressUnitVec(N))); + o_color = vec4(lightIntensity * (diffuse + specular), 1); + o_gbuffer.rgba = vec4(i_worldPos, uintBitsToFloat(CompressUnitVec(N))); } diff --git a/ray_tracing_ao/shaders/host_device.h b/ray_tracing_ao/shaders/host_device.h new file mode 100644 index 0000000..a8377f2 --- /dev/null +++ b/ray_tracing_ao/shaders/host_device.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_ao/shaders/vert_shader.vert b/ray_tracing_ao/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_ao/shaders/vert_shader.vert +++ b/ray_tracing_ao/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_ao/shaders/wavefront.glsl b/ray_tracing_ao/shaders/wavefront.glsl index 76149d4..b326f8a 100644 --- a/ray_tracing_ao/shaders/wavefront.glsl +++ b/ray_tracing_ao/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_callable/hello_vulkan.cpp b/ray_tracing_callable/hello_vulkan.cpp index 03a5c17..75bef57 100644 --- a/ray_tracing_callable/hello_vulkan.cpp +++ b/ray_tracing_callable/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,13 +105,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 3) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -138,11 +129,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -150,7 +141,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -162,7 +153,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -222,30 +213,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -255,9 +251,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -266,15 +262,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -360,8 +356,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -415,14 +411,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -440,10 +436,12 @@ void HelloVulkan::onResize(int /*w*/, int /*h*/) updateRtDescriptorSet(); } + ////////////////////////////////////////////////////////////////////////// // Post-processing ////////////////////////////////////////////////////////////////////////// + //-------------------------------------------------------------------------------------------------- // Creating an offscreen frame buffer and the associated render pass // @@ -552,6 +550,7 @@ void HelloVulkan::createPostDescriptor() m_postDescSet = nvvk::allocateDescriptorSet(m_device, m_postDescPool, m_postDescSetLayout); } + //-------------------------------------------------------------------------------------------------- // Update the output // @@ -611,7 +610,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -660,19 +659,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -685,9 +687,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -707,8 +709,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -721,7 +723,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -731,7 +733,6 @@ void HelloVulkan::updateRtDescriptorSet() // void HelloVulkan::createRtPipeline() { - enum StageIndices { eRaygen, @@ -821,7 +822,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR | VK_SHADER_STAGE_CALLABLE_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -867,13 +868,13 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing push constant values - m_rtPushConstants.clearColor = clearColor; - m_rtPushConstants.lightPosition = m_pushConstant.lightPosition; - m_rtPushConstants.lightIntensity = m_pushConstant.lightIntensity; - m_rtPushConstants.lightDirection = m_pushConstant.lightDirection; - m_rtPushConstants.lightSpotCutoff = m_pushConstant.lightSpotCutoff; - m_rtPushConstants.lightSpotOuterCutoff = m_pushConstant.lightSpotOuterCutoff; - m_rtPushConstants.lightType = m_pushConstant.lightType; + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightDirection = m_pcRaster.lightDirection; + m_pcRay.lightSpotCutoff = m_pcRaster.lightSpotCutoff; + m_pcRay.lightSpotOuterCutoff = m_pcRaster.lightSpotOuterCutoff; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -883,7 +884,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR | VK_SHADER_STAGE_CALLABLE_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); auto& regions = m_sbtWrapper.getRegions(); diff --git a/ray_tracing_callable/hello_vulkan.h b/ray_tracing_callable/hello_vulkan.h index 8fdc61c..a1659f8 100644 --- a/ray_tracing_callable/hello_vulkan.h +++ b/ray_tracing_callable/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -45,7 +46,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -63,35 +64,30 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices{0}; - VkDeviceAddress indices{0}; - VkDeviceAddress materials{0}; - VkDeviceAddress materialIndices{0}; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // Information pushed at each draw call - struct ObjPushConstant - { - nvmath::vec3f lightPosition{10.f, 15.f, 8.f}; - float lightIntensity{100.f}; - nvmath::vec3f lightDirection{-1, -1, -1}; - float lightSpotCutoff{cos(deg2rad(12.5f))}; - float lightSpotOuterCutoff{cos(deg2rad(17.5f))}; - int instanceId{0}; // To retrieve the transformation matrix - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // model identity + {10.f, 15.f, 8.f}, // lightPosition + {0}, // instanceId to retrieve the transformation matrix + {100.f}, // lightIntensity + {-1.f, -1.f, -1.f}, // lightDirection + {cos(deg2rad(12.5f))}, // lightSpotCutoff + {cos(deg2rad(17.5f))}, // lightSpotOuterCutoff + {0} // lightType 0: point, 1: infinite }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -101,8 +97,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -111,7 +107,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -152,16 +148,7 @@ public: VkPipelineLayout m_rtPipelineLayout; VkPipeline m_rtPipeline; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity; - nvmath::vec3f lightDirection{-1, -1, -1}; - float lightSpotCutoff{deg2rad(12.5f)}; - float lightSpotOuterCutoff{deg2rad(17.5f)}; - int lightType; - } m_rtPushConstants; + PushConstantRay m_pcRay{}; nvvk::SBTWrapper m_sbtWrapper; }; diff --git a/ray_tracing_callable/main.cpp b/ray_tracing_callable/main.cpp index 72c73f5..e82e50e 100644 --- a/ray_tracing_callable/main.cpp +++ b/ray_tracing_callable/main.cpp @@ -54,30 +54,31 @@ static void onErrorCallback(int error, const char* description) void renderUI(HelloVulkan& helloVk) { ImGuiH::CameraWidget(); + ImGui::SetNextTreeNodeOpen(true, ImGuiCond_Once); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Spot", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Spot", &helloVk.m_pcRaster.lightType, 1); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 2); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.lightType, 2); - if(helloVk.m_pushConstant.lightType < 2) - ImGui::SliderFloat3("Light Position", &helloVk.m_pushConstant.lightPosition.x, -20.f, 20.f); - if(helloVk.m_pushConstant.lightType > 0) - ImGui::SliderFloat3("Light Direction", &helloVk.m_pushConstant.lightDirection.x, -1.f, 1.f); - if(helloVk.m_pushConstant.lightType < 2) - ImGui::SliderFloat("Light Intensity", &helloVk.m_pushConstant.lightIntensity, 0.f, 500.f); - if(helloVk.m_pushConstant.lightType == 1) + if(helloVk.m_pcRaster.lightType < 2) + ImGui::SliderFloat3("Light Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + if(helloVk.m_pcRaster.lightType > 0) + ImGui::SliderFloat3("Light Direction", &helloVk.m_pcRaster.lightDirection.x, -1.f, 1.f); + if(helloVk.m_pcRaster.lightType < 2) + ImGui::SliderFloat("Light Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 500.f); + if(helloVk.m_pcRaster.lightType == 1) { - float dCutoff = rad2deg(acos(helloVk.m_pushConstant.lightSpotCutoff)); - float dOutCutoff = rad2deg(acos(helloVk.m_pushConstant.lightSpotOuterCutoff)); + float dCutoff = rad2deg(acos(helloVk.m_pcRaster.lightSpotCutoff)); + float dOutCutoff = rad2deg(acos(helloVk.m_pcRaster.lightSpotOuterCutoff)); ImGui::SliderFloat("Cutoff", &dCutoff, 0.f, 45.f); ImGui::SliderFloat("OutCutoff", &dOutCutoff, 0.f, 45.f); dCutoff = dCutoff > dOutCutoff ? dOutCutoff : dCutoff; - helloVk.m_pushConstant.lightSpotCutoff = cos(deg2rad(dCutoff)); - helloVk.m_pushConstant.lightSpotOuterCutoff = cos(deg2rad(dOutCutoff)); + helloVk.m_pcRaster.lightSpotCutoff = cos(deg2rad(dCutoff)); + helloVk.m_pcRaster.lightSpotOuterCutoff = cos(deg2rad(dOutCutoff)); } } } @@ -88,6 +89,7 @@ void renderUI(HelloVulkan& helloVk) static int const SAMPLE_WIDTH = 1280; static int const SAMPLE_HEIGHT = 720; + //-------------------------------------------------------------------------------------------------- // Application Entry // @@ -180,7 +182,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_callable/shaders/frag_shader.frag b/ray_tracing_callable/shaders/frag_shader.frag index ddd4881..2ff6dd9 100644 --- a/ray_tracing_callable/shaders/frag_shader.frag +++ b/ray_tracing_callable/shaders/frag_shader.frag @@ -29,75 +29,68 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - uint instanceId; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 LightDir; float lightIntensity; // Point light - if(pushC.lightType == 0) + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRaster.lightIntensity / (lightDistance * lightDistance); LightDir = normalize(lDir); } - else if(pushC.lightType == 1) + else if(pcRaster.lightType == 1) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRaster.lightIntensity / (lightDistance * lightDistance); LightDir = normalize(lDir); - float theta = dot(LightDir, normalize(-pushC.lightDirection)); - float epsilon = pushC.lightSpotCutoff - pushC.lightSpotOuterCutoff; - float spotIntensity = clamp((theta - pushC.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); + float theta = dot(LightDir, normalize(-pcRaster.lightDirection)); + float epsilon = pcRaster.lightSpotCutoff - pcRaster.lightSpotOuterCutoff; + float spotIntensity = clamp((theta - pcRaster.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); lightIntensity *= spotIntensity; } else // Directional light { - LightDir = normalize(-pushC.lightDirection); + LightDir = normalize(-pcRaster.lightDirection); lightIntensity = 1.0; } @@ -106,15 +99,15 @@ void main() vec3 diffuse = computeDiffuse(mat, LightDir, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, LightDir, N); + vec3 specular = computeSpecular(mat, i_viewDir, LightDir, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_callable/shaders/host_device.h b/ray_tracing_callable/shaders/host_device.h new file mode 100644 index 0000000..9f118b6 --- /dev/null +++ b/ray_tracing_callable/shaders/host_device.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + vec3 lightDirection; + float lightSpotCutoff; + float lightSpotOuterCutoff; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + vec3 lightDirection; + float lightSpotCutoff; + float lightSpotOuterCutoff; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_callable/shaders/raytrace.rchit b/ray_tracing_callable/shaders/raytrace.rchit index 7b140fe..b268e4f 100644 --- a/ray_tracing_callable/shaders/raytrace.rchit +++ b/ray_tracing_callable/shaders/raytrace.rchit @@ -39,29 +39,20 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; + +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; // clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - int lightType; -} -pushC; layout(location = 0) callableDataEXT rayLight cLight; void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -77,48 +68,46 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space + cLight.inHitPosition = worldPos; //#define DONT_USE_CALLABLE #if defined(DONT_USE_CALLABLE) // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - cLight.inHitPosition; + vec3 lDir = pcRay.lightPosition - cLight.inHitPosition; float lightDistance = length(lDir); - cLight.outIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + cLight.outIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); cLight.outLightDir = normalize(lDir); cLight.outLightDistance = lightDistance; } - else if(pushC.lightType == 1) + else if(pcRay.lightType == 1) { - vec3 lDir = pushC.lightPosition - cLight.inHitPosition; + vec3 lDir = pcRay.lightPosition - cLight.inHitPosition; cLight.outLightDistance = length(lDir); - cLight.outIntensity = pushC.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); + cLight.outIntensity = pcRay.lightIntensity / (cLight.outLightDistance * cLight.outLightDistance); cLight.outLightDir = normalize(lDir); - float theta = dot(cLight.outLightDir, normalize(-pushC.lightDirection)); - float epsilon = pushC.lightSpotCutoff - pushC.lightSpotOuterCutoff; - float spotIntensity = clamp((theta - pushC.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); + float theta = dot(cLight.outLightDir, normalize(-pcRay.lightDirection)); + float epsilon = pcRay.lightSpotCutoff - pcRay.lightSpotOuterCutoff; + float spotIntensity = clamp((theta - pcRay.lightSpotOuterCutoff) / epsilon, 0.0, 1.0); cLight.outIntensity *= spotIntensity; } else // Directional light { - cLight.outLightDir = normalize(-pushC.lightDirection); + cLight.outLightDir = normalize(-pcRay.lightDirection); cLight.outIntensity = 1.0; cLight.outLightDistance = 10000000; } #else - executeCallableEXT(pushC.lightType, 0); + executeCallableEXT(pcRay.lightType, 0); #endif // Material of the object @@ -127,10 +116,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, cLight.outLightDir, normal); + vec3 diffuse = computeDiffuse(mat, cLight.outLightDir, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -139,7 +128,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, cLight.outLightDir) > 0) + if(dot(worldNrm, cLight.outLightDir) > 0) { float tMin = 0.001; float tMax = cLight.outLightDistance; @@ -167,7 +156,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, cLight.outLightDir, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, cLight.outLightDir, worldNrm); } } diff --git a/ray_tracing_callable/shaders/raytrace.rgen b/ray_tracing_callable/shaders/raytrace.rgen index ebae40a..4802cd0 100644 --- a/ray_tracing_callable/shaders/raytrace.rgen +++ b/ray_tracing_callable/shaders/raytrace.rgen @@ -20,21 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + void main() { @@ -42,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; diff --git a/ray_tracing_callable/shaders/raytrace.rmiss b/ray_tracing_callable/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_callable/shaders/raytrace.rmiss +++ b/ray_tracing_callable/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_callable/shaders/vert_shader.vert b/ray_tracing_callable/shaders/vert_shader.vert index a46389c..40baa80 100644 --- a/ray_tracing_callable/shaders/vert_shader.vert +++ b/ray_tracing_callable/shaders/vert_shader.vert @@ -26,41 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - float lightIntensity; - vec3 lightDirection; - float lightSpotCutoff; - float lightSpotOuterCutoff; - uint instanceId; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -70,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_callable/shaders/wavefront.glsl b/ray_tracing_callable/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_callable/shaders/wavefront.glsl +++ b/ray_tracing_callable/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_gltf/README.md b/ray_tracing_gltf/README.md index afd56b0..7c51c68 100644 --- a/ray_tracing_gltf/README.md +++ b/ray_tracing_gltf/README.md @@ -34,27 +34,48 @@ of the number of elements and offsets. From the source tutorial, we will not need the following and therefore remove it: ~~~~C - struct ObjModel {..}; - struct ObjInstance {..}; - std::vector m_objModel; - std::vector m_objInstance; - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances ~~~~ -But instead, we will use this following structure to retrieve the information of the primitive that has been hit in the closest hit shader; +In `host_device.h` we will add new host/device structures: PrimMeshInfo, SceneDesc and GltfShadeMaterial. ~~~~C - // Structure used for retrieving the primitive information in the closest hit - // The gl_InstanceCustomIndexNV - struct RtPrimitiveLookup - { - uint32_t indexOffset; - uint32_t vertexOffset; - int materialIndex; - }; - ~~~~ +// Structure used for retrieving the primitive information in the closest hit +struct PrimMeshInfo +{ + uint indexOffset; + uint vertexOffset; + int materialIndex; +}; - And for holding the information, we will be using a helper class to hold glTF scene and buffers for the data. +// Scene buffer addresses +struct SceneDesc +{ + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t normalAddress; // Address of the Normal buffer + uint64_t uvAddress; // Address of the texture coordinates buffer + uint64_t indexAddress; // Address of the triangle indices buffer + uint64_t materialAddress; // Address of the Materials buffer (GltfShadeMaterial) + uint64_t primInfoAddress; // Address of the mesh primitives buffer (PrimMeshInfo) +}; +~~~~ + +And also, our glTF material representation for the shading. This is a stripped down version of the glTF PBR. If you are interested in the +correct PBR implementation, check out [vk_raytrace](https://github.com/nvpro-samples/vk_raytrace). + +~~~~ C +struct GltfShadeMaterial +{ + vec4 pbrBaseColorFactor; + vec3 emissiveFactor; + int pbrBaseColorTexture; +}; +~~~~ + + + And for holding the all the buffers allocated for representing the scene, we will store them in the following. ~~~~C nvh::GltfScene m_gltfScene; @@ -63,26 +84,10 @@ But instead, we will use this following structure to retrieve the information of nvvk::Buffer m_uvBuffer; nvvk::Buffer m_indexBuffer; nvvk::Buffer m_materialBuffer; - nvvk::Buffer m_matrixBuffer; - nvvk::Buffer m_rtPrimLookup; + nvvk::Buffer m_primInfo; nvvk::Buffer m_sceneDesc; ~~~~ -And a structure to retrieve all buffers of the scene - -~~~~ C++ - struct SceneDescription - { - uint64_t vertexAddress; - uint64_t normalAddress; - uint64_t uvAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t matrixAddress; - uint64_t rtPrimAddress; - }; - ~~~~ - ## Loading glTF scene To load the scene, we will be using [TinyGLTF](https://github.com/syoyo/tinygltf) from Syoyo Fujita, then to avoid traversing @@ -146,26 +151,12 @@ We are making a simple material, extracting only a few members from the glTF mat ~~~~ -We could use `push_constant` to set the matrix of the node, but instead, we will push the index of the -node to draw and fetch the matrix from a buffer. - -~~~~C - // Instance Matrices used by rasterizer - std::vector nodeMatrices; - for(auto& node : m_gltfScene.m_nodes) - { - nodeMatrices.emplace_back(node.worldMatrix); - } - m_matrixBuffer = m_alloc.createBuffer(cmdBuf, nodeMatrices, - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); -~~~~ - To find the positions of the triangle hit in the closest hit shader, as well as the other attributes, we will store the offsets information of that geometry. ~~~~C // The following is used to find the primitive mesh information in the CHIT - std::vector primLookup; + std::vector primLookup; for(auto& primMesh : m_gltfScene.m_primMeshes) { primLookup.push_back({primMesh.firstIndex, primMesh.vertexOffset, primMesh.materialIndex}); @@ -177,15 +168,14 @@ attributes, we will store the offsets information of that geometry. Finally, we are creating a buffer holding the address of all buffers ~~~~ C++ - SceneDescription sceneDesc; + SceneDesc sceneDesc; sceneDesc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer); sceneDesc.indexAddress = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer); sceneDesc.normalAddress = nvvk::getBufferDeviceAddress(m_device, m_normalBuffer.buffer); sceneDesc.uvAddress = nvvk::getBufferDeviceAddress(m_device, m_uvBuffer.buffer); sceneDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_materialBuffer.buffer); - sceneDesc.matrixAddress = nvvk::getBufferDeviceAddress(m_device, m_matrixBuffer.buffer); - sceneDesc.rtPrimAddress = nvvk::getBufferDeviceAddress(m_device, m_rtPrimLookup.buffer); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, sizeof(SceneDescription), &sceneDesc, + sceneDesc.primInfoAddress = nvvk::getBufferDeviceAddress(m_device, m_primInfo.buffer); + m_sceneDesc = m_alloc.createBuffer(cmdBuf, sizeof(SceneDesc), &sceneDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); ~~~~ @@ -204,18 +194,18 @@ The finalize and releasing staging is waiting for the copy of all data to the GP NAME_VK(m_normalBuffer.buffer); NAME_VK(m_uvBuffer.buffer); NAME_VK(m_materialBuffer.buffer); - NAME_VK(m_matrixBuffer.buffer); - NAME_VK(m_rtPrimLookup.buffer); + NAME_VK(m_primInfo.buffer); NAME_VK(m_sceneDesc.buffer); } ~~~~ -**NOTE**: the macro `NAME_VK` is a convenience to name Vulkan object to easily identify them in Nsight Graphics and to know where it was created. +**:warning: NOTE**: the macro `NAME_VK` is a convenience to name Vulkan object to easily identify them in Nsight Graphics and to know where it was created. ## Converting geometry to BLAS -Instead of `objectToVkGeometryKHR()`, we will be using `primitiveToGeometry(const nvh::GltfPrimMesh& prim)`. -The function is similar, only the input is different. +Instead of `objectToVkGeometryKHR()`, we will be using `primitiveToVkGeometry(const nvh::GltfPrimMesh& prim)`. +The function is similar, only the input is different, except for `VkAccelerationStructureBuildRangeInfoKHR` where +we also include the offsets. ~~~~C //-------------------------------------------------------------------------------------------------- @@ -231,7 +221,7 @@ auto HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(nvmath::vec3f); // Describe index data (32-bit unsigned int) @@ -264,22 +254,18 @@ auto HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) ## Top Level creation -There are almost no changes for creating the TLAS but is actually even simpler. Each -drawable node has a matrix and an index to the geometry, which in our case, also -correspond directly to the BLAS ID. To know which geometry is used, and to find back -all the data (see structure `RtPrimitiveLookup`), we will set the `instanceCustomId` member -to the primitive mesh id. This value will be recovered with `gl_InstanceCustomIndexEXT` -in the closest hit shader. +There are almost no differences, besides the fact that the index of the geometry is stored in `primMesh`. ~~~~C for(auto& node : m_gltfScene.m_nodes) { - nvvk::RaytracingBuilderKHR::Instance rayInst; - rayInst.transform = node.worldMatrix; - rayInst.instanceCustomId = node.primMesh; // gl_InstanceCustomIndexEXT: to find which primitive - rayInst.blasId = node.primMesh; - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.hitGroupId = 0; // We will use the same hit group for all objects + VkAccelerationStructureInstanceKHR rayInst; + rayInst.transform = nvvk::toTransformMatrixKHR(node.worldMatrix); + rayInst.instanceCustomIndex = node.primMesh; // gl_InstanceCustomIndexEXT: to find which primitive + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(node.primMesh); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; + rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects tlas.emplace_back(rayInst); } ~~~~ @@ -288,8 +274,8 @@ in the closest hit shader. ## Raster Rendering Raster rendering is simple. The shader was changed to use vertex, normal and texture coordinates. For -each node, we will be pushing the instance Id (retrieve the matrix) and the material Id. Since we -don't have a scene graph, we could loop over all drawable nodes. +each node, we will be pushing the material Id this primitive is using. Since we have flatten the scene graph, +we can loop over all drawable nodes. ~~~~C std::vector vertexBuffers = {m_vertexBuffer.buffer, m_normalBuffer.buffer, m_uvBuffer.buffer}; @@ -301,10 +287,11 @@ don't have a scene graph, we could loop over all drawable nodes. { auto& primitive = m_gltfScene.m_primMeshes[node.primMesh]; - m_pushConstant.instanceId = idxNode++; - m_pushConstant.materialId = primitive.materialIndex; + m_pcRaster.modelMatrix = node.worldMatrix; + m_pcRaster.objIndex = node.primMesh; + m_pcRaster.materialId = primitive.materialIndex; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdDrawIndexed(cmdBuf, primitive.indexCount, 1, primitive.firstIndex, primitive.vertexOffset, 0); } ~~~~ @@ -316,12 +303,12 @@ In `createRtDescriptorSet()`, the only change we will add is the primitive info the data when hitting a triangle. ~~~~C - m_rtDescSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + m_rtDescSetLayoutBind.addBinding(ePrimLookup, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); // Primitive info // ... VkDescriptorBufferInfo primitiveInfoDesc{m_rtPrimLookup.buffer, 0, VK_WHOLE_SIZE}; // ... - writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &primitiveInfoDesc)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, ePrimLookup, &primitiveInfoDesc)); ~~~~ @@ -393,12 +380,12 @@ void HelloVulkan::updateFrame() refCamMatrix = m; refFov = fov; } - m_rtPushConstants.frame++; + m_pcRay.frame++; } void HelloVulkan::resetFrame() { - m_rtPushConstants.frame = -1; + m_pcRay.frame = -1; } ~~~~ @@ -436,16 +423,16 @@ To accumulate the samples, instead of only write to the image, we will also use ~~~~C // Do accumulation over time - if(pushC.frame > 0) + if(pcRay.frame > 0) { - float a = 1.0f / float(pushC.frame + 1); + float a = 1.0f / float(pcRay.frame + 1); vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; - imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, prd.hitValue, a), 1.f)); + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, hitValue, a), 1.f)); } else { // First frame, replace the value in the buffer - imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.f)); + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 1.f)); } ~~~~ diff --git a/ray_tracing_gltf/hello_vulkan.cpp b/ray_tracing_gltf/hello_vulkan.cpp index bb1ce69..81f1fd5 100644 --- a/ray_tracing_gltf/hello_vulkan.cpp +++ b/ray_tracing_gltf/hello_vulkan.cpp @@ -39,23 +39,10 @@ #include "nvvk/shaders_vk.hpp" #include "nvh/alignment.hpp" -#include "shaders/binding.glsl" -#include "shaders/gltf.glsl" #include "nvvk/buffers_vk.hpp" extern std::vector defaultSearchPaths; -// 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 @@ -75,16 +62,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -100,7 +88,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -119,13 +107,15 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) void HelloVulkan::createDescriptorSetLayout() { auto& bind = m_descSetLayoutBind; - // Camera matrices (binding = 0) - bind.addBinding(B_CAMERA, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Camera matrices + bind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Array of textures auto nbTextures = static_cast(m_textures.size()); - bind.addBinding(B_TEXTURES, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTextures, + bind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTextures, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); - - bind.addBinding(B_SCENEDESC, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Scene buffers + bind.addBinding(eSceneDesc, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); @@ -142,17 +132,17 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; VkDescriptorBufferInfo sceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_CAMERA, &dbiUnif)); - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, B_SCENEDESC, &sceneDesc)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, eSceneDesc, &sceneDesc)); // All texture samplers std::vector diit; for(auto& texture : m_textures) diit.emplace_back(texture.descriptor); - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, B_TEXTURES, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -164,7 +154,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -181,7 +171,7 @@ void HelloVulkan::createGraphicsPipeline() gpb.depthStencilState.depthTestEnable = true; gpb.addShader(nvh::loadFile("spv/vert_shader.vert.spv", true, paths, true), VK_SHADER_STAGE_VERTEX_BIT); gpb.addShader(nvh::loadFile("spv/frag_shader.frag.spv", true, paths, true), VK_SHADER_STAGE_FRAGMENT_BIT); - gpb.addBindingDescriptions({{0, sizeof(nvmath::vec3)}, {1, sizeof(nvmath::vec3)}, {2, sizeof(nvmath::vec2)}}); + gpb.addBindingDescriptions({{0, sizeof(nvmath::vec3f)}, {1, sizeof(nvmath::vec3f)}, {2, sizeof(nvmath::vec2f)}}); gpb.addAttributeDescriptions({ {0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // Position {1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0}, // Normal @@ -232,41 +222,30 @@ void HelloVulkan::loadScene(const std::string& filename) // Copying all materials, only the elements we need std::vector shadeMaterials; - for(auto& m : m_gltfScene.m_materials) + for(const auto& m : m_gltfScene.m_materials) { shadeMaterials.emplace_back(GltfShadeMaterial{m.baseColorFactor, m.emissiveFactor, m.baseColorTexture}); } m_materialBuffer = m_alloc.createBuffer(cmdBuf, shadeMaterials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); - // Instance Matrices used by rasterizer - std::vector nodeMatrices; - for(auto& node : m_gltfScene.m_nodes) - { - nodeMatrices.emplace_back(node.worldMatrix); - } - m_matrixBuffer = m_alloc.createBuffer(cmdBuf, nodeMatrices, - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); - // The following is used to find the primitive mesh information in the CHIT - std::vector primLookup; + std::vector primLookup; for(auto& primMesh : m_gltfScene.m_primMeshes) { primLookup.push_back({primMesh.firstIndex, primMesh.vertexOffset, primMesh.materialIndex}); } - m_rtPrimLookup = - m_alloc.createBuffer(cmdBuf, primLookup, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + m_primInfo = m_alloc.createBuffer(cmdBuf, primLookup, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); - SceneDescription sceneDesc; + SceneDesc sceneDesc; sceneDesc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer); sceneDesc.indexAddress = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer); sceneDesc.normalAddress = nvvk::getBufferDeviceAddress(m_device, m_normalBuffer.buffer); sceneDesc.uvAddress = nvvk::getBufferDeviceAddress(m_device, m_uvBuffer.buffer); sceneDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_materialBuffer.buffer); - sceneDesc.matrixAddress = nvvk::getBufferDeviceAddress(m_device, m_matrixBuffer.buffer); - sceneDesc.rtPrimAddress = nvvk::getBufferDeviceAddress(m_device, m_rtPrimLookup.buffer); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, sizeof(SceneDescription), &sceneDesc, + sceneDesc.primInfoAddress = nvvk::getBufferDeviceAddress(m_device, m_primInfo.buffer); + m_sceneDesc = m_alloc.createBuffer(cmdBuf, sizeof(SceneDesc), &sceneDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); // Creates all textures found @@ -280,8 +259,7 @@ void HelloVulkan::loadScene(const std::string& filename) NAME_VK(m_normalBuffer.buffer); NAME_VK(m_uvBuffer.buffer); NAME_VK(m_materialBuffer.buffer); - NAME_VK(m_matrixBuffer.buffer); - NAME_VK(m_rtPrimLookup.buffer); + NAME_VK(m_primInfo.buffer); NAME_VK(m_sceneDesc.buffer); } @@ -292,9 +270,9 @@ void HelloVulkan::loadScene(const std::string& filename) // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -347,7 +325,7 @@ void HelloVulkan::createTextureImages(const VkCommandBuffer& cmdBuf, tinygltf::M VkImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo); m_textures.emplace_back(m_alloc.createTexture(image, ivInfo, samplerCreateInfo)); - m_debug.setObjectName(m_textures[i].image, std::string("Txt" + std::to_string(i)).c_str()); + m_debug.setObjectName(m_textures[i].image, std::string("Txt" + std::to_string(i))); } } @@ -361,15 +339,14 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); + m_alloc.destroy(m_bGlobals); m_alloc.destroy(m_vertexBuffer); m_alloc.destroy(m_normalBuffer); m_alloc.destroy(m_uvBuffer); m_alloc.destroy(m_indexBuffer); m_alloc.destroy(m_materialBuffer); - m_alloc.destroy(m_matrixBuffer); - m_alloc.destroy(m_rtPrimLookup); + m_alloc.destroy(m_primInfo); m_alloc.destroy(m_sceneDesc); for(auto& t : m_textures) @@ -426,10 +403,11 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) { auto& primitive = m_gltfScene.m_primMeshes[node.primMesh]; - m_pushConstant.instanceId = idxNode++; - m_pushConstant.materialId = primitive.materialIndex; + m_pcRaster.modelMatrix = node.worldMatrix; + m_pcRaster.objIndex = node.primMesh; + m_pcRaster.materialId = primitive.materialIndex; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdDrawIndexed(cmdBuf, primitive.indexCount, 1, primitive.firstIndex, primitive.vertexOffset, 0); } @@ -610,7 +588,7 @@ void HelloVulkan::initRayTracing() //-------------------------------------------------------------------------------------------------- // Converting a GLTF primitive in the Raytracing Geometry used for the BLAS // -auto HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) +auto HelloVulkan::primitiveToVkGeometry(const nvh::GltfPrimMesh& prim) { // BLAS builder requires raw device addresses. VkDeviceAddress vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer); @@ -620,7 +598,7 @@ auto HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(nvmath::vec3f); // Describe index data (32-bit unsigned int) @@ -638,7 +616,7 @@ auto HelloVulkan::primitiveToGeometry(const nvh::GltfPrimMesh& prim) VkAccelerationStructureBuildRangeInfoKHR offset; offset.firstVertex = prim.vertexOffset; - offset.primitiveCount = prim.indexCount / 3; + offset.primitiveCount = maxPrimitiveCount; offset.primitiveOffset = prim.firstIndex * sizeof(uint32_t); offset.transformOffset = 0; @@ -660,25 +638,28 @@ void HelloVulkan::createBottomLevelAS() allBlas.reserve(m_gltfScene.m_primMeshes.size()); for(auto& primMesh : m_gltfScene.m_primMeshes) { - auto geo = primitiveToGeometry(primMesh); + auto geo = primitiveToVkGeometry(primMesh); allBlas.push_back({geo}); } m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; tlas.reserve(m_gltfScene.m_nodes.size()); for(auto& node : m_gltfScene.m_nodes) { - VkAccelerationStructureInstanceKHR rayInst; + VkAccelerationStructureInstanceKHR rayInst{}; rayInst.transform = nvvk::toTransformMatrixKHR(node.worldMatrix); rayInst.instanceCustomIndex = node.primMesh; // gl_InstanceCustomIndexEXT: to find which primitive rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(node.primMesh); rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -689,13 +670,12 @@ void HelloVulkan::createTopLevelAS() // void HelloVulkan::createRtDescriptorSet() { - // Top-level acceleration structure, usable by both the ray generation and the closest hit (to - // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + // Top-level acceleration structure, usable by both the ray generation and the closest hit (to shoot shadow rays) + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image - m_rtDescSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::ePrimLookup, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); // Primitive info m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -713,12 +693,12 @@ void HelloVulkan::createRtDescriptorSet() descASInfo.accelerationStructureCount = 1; descASInfo.pAccelerationStructures = &tlas; VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkDescriptorBufferInfo primitiveInfoDesc{m_rtPrimLookup.buffer, 0, VK_WHOLE_SIZE}; + VkDescriptorBufferInfo primitiveInfoDesc{m_primInfo.buffer, 0, VK_WHOLE_SIZE}; 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, &primitiveInfoDesc)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::ePrimLookup, &primitiveInfoDesc)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -731,7 +711,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -804,7 +784,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -856,10 +836,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -868,7 +848,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); auto& regions = m_sbtWrapper.getRegions(); @@ -896,10 +876,10 @@ void HelloVulkan::updateFrame() refCamMatrix = m; refFov = fov; } - m_rtPushConstants.frame++; + m_pcRay.frame++; } void HelloVulkan::resetFrame() { - m_rtPushConstants.frame = -1; + m_pcRay.frame = -1; } diff --git a/ray_tracing_gltf/hello_vulkan.h b/ray_tracing_gltf/hello_vulkan.h index 14995eb..52e8010 100644 --- a/ray_tracing_gltf/hello_vulkan.h +++ b/ray_tracing_gltf/hello_vulkan.h @@ -19,6 +19,8 @@ #pragma once +#include "shaders/host_device.h" + #include "nvvk/appbase_vk.hpp" #include "nvvk/debug_util_vk.hpp" #include "nvvk/descriptorsets_vk.hpp" @@ -52,25 +54,6 @@ public: void destroyResources(); void rasterize(const VkCommandBuffer& cmdBuff); - // Structure used for retrieving the primitive information in the closest hit - // The gl_InstanceCustomIndexNV - struct RtPrimitiveLookup - { - uint32_t indexOffset; - uint32_t vertexOffset; - int materialIndex; - }; - - struct SceneDescription - { - uint64_t vertexAddress; - uint64_t normalAddress; - uint64_t uvAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t matrixAddress; - uint64_t rtPrimAddress; - }; nvh::GltfScene m_gltfScene; nvvk::Buffer m_vertexBuffer; @@ -78,20 +61,18 @@ public: nvvk::Buffer m_uvBuffer; nvvk::Buffer m_indexBuffer; nvvk::Buffer m_materialBuffer; - nvvk::Buffer m_matrixBuffer; - nvvk::Buffer m_rtPrimLookup; + nvvk::Buffer m_primInfo; nvvk::Buffer m_sceneDesc; // Information pushed at each draw call - struct ObjPushConstant - { - nvmath::vec3f lightPosition{0.f, 4.5f, 0.f}; - int instanceId{0}; // To retrieve the transformation matrix - float lightIntensity{10.f}; - int lightType{0}; // 0: point, 1: infinite - int materialId{0}; + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {0.f, 4.5f, 0.f}, // light position + 0, // instance Id + 10.f, // light intensity + 0, // light type + 0 // material id }; - ObjPushConstant m_pushConstant; // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -101,13 +82,14 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - std::vector m_textures; // vector of all textures of the scene + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + std::vector m_textures; // vector of all textures of the scene nvvk::ResourceAllocatorDma m_alloc; // Allocator for buffer, images, acceleration structures nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -128,8 +110,8 @@ public: VkFormat m_offscreenDepthFormat{VK_FORMAT_X8_D24_UNORM_PACK32}; // #VKRay - auto primitiveToGeometry(const nvh::GltfPrimMesh& prim); void initRayTracing(); + auto primitiveToVkGeometry(const nvh::GltfPrimMesh& prim); void createBottomLevelAS(); void createTopLevelAS(); void createRtDescriptorSet(); @@ -150,12 +132,5 @@ public: VkPipeline m_rtPipeline; nvvk::SBTWrapper m_sbtWrapper; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity; - int lightType; - int frame{0}; - } m_rtPushConstants; + PushConstantRay m_pcRay{}; }; diff --git a/ray_tracing_gltf/main.cpp b/ray_tracing_gltf/main.cpp index b2f3d44..ab04caf 100644 --- a/ray_tracing_gltf/main.cpp +++ b/ray_tracing_gltf/main.cpp @@ -54,14 +54,14 @@ static void onErrorCallback(int error, const char* description) void renderUI(HelloVulkan& helloVk, bool useRaytracer) { ImGuiH::CameraWidget(); - if( !useRaytracer && ImGui::CollapsingHeader("Light")) + if(!useRaytracer && ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -204,7 +204,7 @@ int main(int argc, char** argv) { ImGuiH::Panel::Begin(); ImGui::ColorEdit3("Clear color", reinterpret_cast(&clearColor)); - if (ImGui::Checkbox("Ray Tracer mode", &useRaytracer)) // Switch between raster and ray tracing + if(ImGui::Checkbox("Ray Tracer mode", &useRaytracer)) // Switch between raster and ray tracing helloVk.resetFrame(); renderUI(helloVk, useRaytracer); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); diff --git a/ray_tracing_gltf/shaders/frag_shader.frag b/ray_tracing_gltf/shaders/frag_shader.frag index 9e9fc39..fba527f 100644 --- a/ray_tracing_gltf/shaders/frag_shader.frag +++ b/ray_tracing_gltf/shaders/frag_shader.frag @@ -26,34 +26,26 @@ #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_EXT_buffer_reference2 : require -#include "binding.glsl" #include "gltf.glsl" +#include "host_device.h" - -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; - int matetrialId; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; // Buffers - -layout(buffer_reference, scalar) buffer Matrices { mat4 m[]; }; layout(buffer_reference, scalar) buffer GltfMaterial { GltfShadeMaterial m[]; }; -layout(set = 0, binding = B_SCENEDESC ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; } ; -layout(set = 0, binding = B_TEXTURES) uniform sampler2D[] textureSamplers; +layout(set = 0, binding = eSceneDesc ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; } ; +layout(set = 0, binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on @@ -61,23 +53,23 @@ void main() { // Material of the object GltfMaterial gltfMat = GltfMaterial(sceneDesc.materialAddress); - GltfShadeMaterial mat = gltfMat.m[pushC.matetrialId]; + GltfShadeMaterial mat = gltfMat.m[pcRaster.materialId]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -86,13 +78,13 @@ void main() if(mat.pbrBaseColorTexture > -1) { uint txtId = mat.pbrBaseColorTexture; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_gltf/shaders/gltf.glsl b/ray_tracing_gltf/shaders/gltf.glsl index 229ad1b..604adb8 100644 --- a/ray_tracing_gltf/shaders/gltf.glsl +++ b/ray_tracing_gltf/shaders/gltf.glsl @@ -17,32 +17,8 @@ * SPDX-License-Identifier: Apache-2.0 */ -struct GltfShadeMaterial -{ - vec4 pbrBaseColorFactor; - vec3 emissiveFactor; - int pbrBaseColorTexture; -}; - -#ifndef __cplusplus -struct PrimMeshInfo -{ - uint indexOffset; - uint vertexOffset; - int materialIndex; -}; - -struct SceneDesc -{ - uint64_t vertexAddress; - uint64_t normalAddress; - uint64_t uvAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t matrixAddress; - uint64_t rtPrimAddress; -}; +#include "host_device.h" vec3 computeDiffuse(GltfShadeMaterial mat, vec3 lightDir, vec3 normal) { @@ -65,4 +41,3 @@ vec3 computeSpecular(GltfShadeMaterial mat, vec3 viewDir, vec3 lightDir, vec3 no return vec3(specular); } -#endif diff --git a/ray_tracing_gltf/shaders/host_device.h b/ray_tracing_gltf/shaders/host_device.h new file mode 100644 index 0000000..6f91087 --- /dev/null +++ b/ray_tracing_gltf/shaders/host_device.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eSceneDesc = 1, // Access to the scene buffers + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1, // Ray tracer output image + ePrimLookup = 2 // Lookup of objects +END_BINDING(); +// clang-format on + +// Scene buffer addresses +struct SceneDesc +{ + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t normalAddress; // Address of the Normal buffer + uint64_t uvAddress; // Address of the texture coordinates buffer + uint64_t indexAddress; // Address of the triangle indices buffer + uint64_t materialAddress; // Address of the Materials buffer (GltfShadeMaterial) + uint64_t primInfoAddress; // Address of the mesh primitives buffer (PrimMeshInfo) +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; + int materialId; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int frame; +}; + +// Structure used for retrieving the primitive information in the closest hit +struct PrimMeshInfo +{ + uint indexOffset; + uint vertexOffset; + int materialIndex; +}; + +struct GltfShadeMaterial +{ + vec4 pbrBaseColorFactor; + vec3 emissiveFactor; + int pbrBaseColorTexture; +}; + +#endif diff --git a/ray_tracing_gltf/shaders/pathtrace.rchit b/ray_tracing_gltf/shaders/pathtrace.rchit index 40035e8..7f8fb74 100644 --- a/ray_tracing_gltf/shaders/pathtrace.rchit +++ b/ray_tracing_gltf/shaders/pathtrace.rchit @@ -27,11 +27,10 @@ #extension GL_EXT_buffer_reference2 : require -#include "binding.glsl" #include "gltf.glsl" #include "raycommon.glsl" #include "sampling.glsl" - +#include "host_device.h" hitAttributeEXT vec2 attribs; @@ -49,20 +48,12 @@ layout(buffer_reference, scalar) readonly buffer Normals { vec3 n[]; }; layout(buffer_reference, scalar) readonly buffer TexCoords { vec2 t[]; }; layout(buffer_reference, scalar) readonly buffer Materials { GltfShadeMaterial m[]; }; -layout(set = 1, binding = B_SCENEDESC ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; }; -layout(set = 1, binding = B_TEXTURES) uniform sampler2D texturesMap[]; // all textures +layout(set = 1, binding = eSceneDesc ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; }; +layout(set = 1, binding = eTextures) uniform sampler2D texturesMap[]; // all textures +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; // clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; - void main() { diff --git a/ray_tracing_gltf/shaders/pathtrace.rgen b/ray_tracing_gltf/shaders/pathtrace.rgen index 15a4d76..c1d0eb2 100644 --- a/ray_tracing_gltf/shaders/pathtrace.rgen +++ b/ray_tracing_gltf/shaders/pathtrace.rgen @@ -21,35 +21,22 @@ #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable #extension GL_ARB_shader_clock : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require -#include "binding.glsl" #include "raycommon.glsl" #include "sampling.glsl" +#include "host_device.h" + +// clang-format off +layout(location = 0) rayPayloadEXT hitPayload prd; layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS; layout(set = 0, binding = 1, rgba32f) uniform image2D image; -layout(location = 0) rayPayloadEXT hitPayload prd; - -layout(set = 1, binding = B_CAMERA) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; - -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int frame; -} -pushC; +layout(set = 1, binding = 0) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { @@ -60,9 +47,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; @@ -98,9 +85,9 @@ void main() } // Do accumulation over time - if(pushC.frame > 0) + if(pcRay.frame > 0) { - float a = 1.0f / float(pushC.frame + 1); + float a = 1.0f / float(pcRay.frame + 1); vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, hitValue, a), 1.f)); } diff --git a/ray_tracing_gltf/shaders/raytrace.rchit b/ray_tracing_gltf/shaders/raytrace.rchit index 75aaabe..500a0d6 100644 --- a/ray_tracing_gltf/shaders/raytrace.rchit +++ b/ray_tracing_gltf/shaders/raytrace.rchit @@ -27,9 +27,9 @@ #extension GL_EXT_buffer_reference2 : require -#include "binding.glsl" #include "gltf.glsl" #include "raycommon.glsl" +#include "host_device.h" hitAttributeEXT vec2 attribs; @@ -49,8 +49,8 @@ layout(buffer_reference, scalar) readonly buffer Normals { vec3 n[]; }; layout(buffer_reference, scalar) readonly buffer TexCoords { vec2 t[]; }; layout(buffer_reference, scalar) readonly buffer Materials { GltfShadeMaterial m[]; }; -layout(set = 1, binding = B_SCENEDESC ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; }; -layout(set = 1, binding = B_TEXTURES) uniform sampler2D texturesMap[]; // all textures +layout(set = 1, binding = eSceneDesc ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; }; +layout(set = 1, binding = eTextures) uniform sampler2D texturesMap[]; // all textures // clang-format on diff --git a/ray_tracing_gltf/shaders/raytrace.rgen b/ray_tracing_gltf/shaders/raytrace.rgen index 811f888..c031ec4 100644 --- a/ray_tracing_gltf/shaders/raytrace.rgen +++ b/ray_tracing_gltf/shaders/raytrace.rgen @@ -20,22 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable -#include "binding.glsl" +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "sampling.glsl" +#include "host_device.h" + +// clang-format off +layout(location = 0) rayPayloadEXT hitPayload prd; layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS; layout(set = 0, binding = 1, rgba32f) uniform image2D image; -layout(location = 0) rayPayloadEXT hitPayload prd; - -layout(set = 1, binding = B_CAMERA) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 1, binding = 0) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { @@ -43,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; diff --git a/ray_tracing_gltf/shaders/vert_shader.vert b/ray_tracing_gltf/shaders/vert_shader.vert index dbacfc1..a847181 100644 --- a/ray_tracing_gltf/shaders/vert_shader.vert +++ b/ray_tracing_gltf/shaders/vert_shader.vert @@ -21,46 +21,30 @@ #extension GL_ARB_separate_shader_objects : enable #extension GL_EXT_scalar_block_layout : enable #extension GL_GOOGLE_include_directive : enable - #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require -#extension GL_EXT_buffer_reference2 : require -#include "binding.glsl" #include "gltf.glsl" +#include "host_device.h" -// clang-format off -layout(buffer_reference, scalar) buffer Matrices { mat4 m[]; }; -layout( set = 0, binding = B_SCENEDESC ) readonly buffer SceneDesc_ { SceneDesc sceneDesc; }; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; - int materialId; -} -pushC; + PushConstantRaster pcRaster; +}; -layout(location = 0) in vec3 inPosition; -layout(location = 1) in vec3 inNormal; -layout(location = 2) in vec2 inTexCoord; +layout(location = 0) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -70,18 +54,12 @@ out gl_PerVertex void main() { - Matrices matrices = Matrices(sceneDesc.matrixAddress); - mat4 objMatrix = matrices.m[pushC.instanceId]; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - mat4 objMatrixIT = transpose(inverse(objMatrix)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_indirect_scissor/hello_vulkan.cpp b/ray_tracing_indirect_scissor/hello_vulkan.cpp index 660dd91..e91be10 100644 --- a/ray_tracing_indirect_scissor/hello_vulkan.cpp +++ b/ray_tracing_indirect_scissor/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) { // Prepare new UBO contents on host. const float aspectRatio = m_size.width / static_cast(m_size.height); - CameraMatrices hostUBO = {}; - hostUBO.view = getViewMatrix(); - hostUBO.proj = getProjMatrix(); - // 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,13 +105,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 3) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -138,11 +129,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -150,7 +141,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -162,7 +153,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -222,30 +213,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } // Add a light-emitting colored lantern to the scene. May only be called before TLAS build. @@ -262,9 +258,9 @@ void HelloVulkan::addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brigh // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -273,15 +269,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -367,8 +363,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -393,6 +389,7 @@ void HelloVulkan::destroyResources() vkDestroyRenderPass(m_device, m_offscreenRenderPass, nullptr); vkDestroyFramebuffer(m_device, m_offscreenFramebuffer, nullptr); + // #VKRay m_rtBuilder.destroy(); vkDestroyPipeline(m_device, m_rtPipeline, nullptr); @@ -429,14 +426,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -626,7 +623,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -822,26 +819,29 @@ void HelloVulkan::createBottomLevelAS() // 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 +// gl_InstanceCustomIndexEXT will be the index of the obj id or lantern in m_instances 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); + tlas.reserve(m_instances.size() + m_lanternCount); // Add the OBJ instances. - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } @@ -868,12 +868,12 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image - // Lantern buffer (binding = 2) - m_rtDescSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Lantern buffer + m_rtDescSetLayoutBind.addBinding(eLanterns, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); assert(m_lanternCount > 0); @@ -895,9 +895,9 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorBufferInfo 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)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, eLanterns, &lanternBufferInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -910,7 +910,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -1073,7 +1073,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -1141,7 +1141,7 @@ void HelloVulkan::createRtShaderBindingTable() VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Map the SBT buffer and write in the handles. void* mapped = m_alloc.map(m_rtSBTBuffer); @@ -1276,7 +1276,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c // Bind compute shader, update push constant and descriptors, dispatch compute. vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_lanternIndirectCompPipeline); - nvmath::mat4 view = getViewMatrix(); + nvmath::mat4f view = getViewMatrix(); m_lanternIndirectPushConstants.viewRowX = view.row(0); m_lanternIndirectPushConstants.viewRowY = view.row(1); m_lanternIndirectPushConstants.viewRowZ = view.row(2); @@ -1305,14 +1305,14 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 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; + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; + m_pcRay.lanternPassNumber = -1; // Global non-lantern pass + m_pcRay.screenX = m_size.width; + m_pcRay.screenY = m_size.height; + m_pcRay.lanternDebug = m_lanternDebug; std::vector descSets{m_rtDescSet, m_descSet}; vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); @@ -1320,7 +1320,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); // Size of a program identifier @@ -1362,10 +1362,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c 0, nullptr, 0, nullptr, 1, &imageBarrier); // Set lantern pass number. - m_rtPushConstants.lanternPassNumber = i; + m_pcRay.lanternPassNumber = i; vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); VkDeviceAddress indirectDeviceAddress = diff --git a/ray_tracing_indirect_scissor/hello_vulkan.h b/ray_tracing_indirect_scissor/hello_vulkan.h index 9e233d0..34355e7 100644 --- a/ray_tracing_indirect_scissor/hello_vulkan.h +++ b/ray_tracing_indirect_scissor/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -45,13 +46,13 @@ public: void addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); - nvmath::mat4 getViewMatrix() { return CameraManip.getMatrix(); } + nvmath::mat4f getViewMatrix() { return CameraManip.getMatrix(); } static constexpr float nearZ = 0.1f; - nvmath::mat4 getProjMatrix() + nvmath::mat4f getProjMatrix() { const float aspectRatio = m_size.width / static_cast(m_size.height); return nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, nearZ, 1000.0f); @@ -73,28 +74,21 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices{0}; - VkDeviceAddress indices{0}; - VkDeviceAddress materials{0}; - VkDeviceAddress materialIndices{0}; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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 + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Information on each colored lantern illuminating the scene. struct Lantern @@ -113,17 +107,18 @@ public: { // Filled in by the device using a compute shader. // NOTE: I rely on indirectCommand being the first member. - VkTraceRaysIndirectCommandKHR indirectCommand; + VkTraceRaysIndirectCommandKHR indirectCommand{}; int32_t offsetX{0}; int32_t offsetY{0}; // Filled in by the host. - Lantern lantern; + Lantern lantern{}; }; // Array of objects and instances in the scene. Not modifiable after acceleration structure build. - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances // Array of lanterns in scene. Not modifiable after acceleration structure build. std::vector m_lanterns; @@ -136,8 +131,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -146,7 +141,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -219,29 +214,9 @@ public: 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; + // Push constant for ray tracer. + PushConstantRay m_pcRay{}; - // 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 @@ -253,18 +228,18 @@ public: // 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::vec4f viewRowX; // First 3 rows of view matrix. + nvmath::vec4f viewRowY; // Set w=1 implicitly in shader. + nvmath::vec4f viewRowZ; - nvmath::mat4 proj; // Perspective matrix - float nearZ; // Near plane used to create projection matrix. + nvmath::mat4f 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; + int32_t screenX{}; + int32_t screenY{}; // Length of the LanternIndirectEntry array. - int32_t lanternCount; + int32_t lanternCount{}; } m_lanternIndirectPushConstants; }; diff --git a/ray_tracing_indirect_scissor/main.cpp b/ray_tracing_indirect_scissor/main.cpp index b025ab9..71575ea 100644 --- a/ray_tracing_indirect_scissor/main.cpp +++ b/ray_tracing_indirect_scissor/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); ImGui::Checkbox("Lantern Debug", &helloVk.m_lanternDebug); } } @@ -175,7 +175,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_indirect_scissor/shaders/frag_shader.frag b/ray_tracing_indirect_scissor/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_indirect_scissor/shaders/frag_shader.frag +++ b/ray_tracing_indirect_scissor/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_indirect_scissor/shaders/host_device.h b/ray_tracing_indirect_scissor/shaders/host_device.h new file mode 100644 index 0000000..d277dee --- /dev/null +++ b/ray_tracing_indirect_scissor/shaders/host_device.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1, // Ray tracer output image + eLanterns = 2 // All lanterns +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int 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. + int lanternPassNumber; + + // Pixel dimensions of the output image. + int screenX; + int screenY; + + // See m_lanternDebug. + int lanternDebug; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_indirect_scissor/shaders/raycommon.glsl b/ray_tracing_indirect_scissor/shaders/raycommon.glsl index 3c67956..f20cbc3 100644 --- a/ray_tracing_indirect_scissor/shaders/raycommon.glsl +++ b/ray_tracing_indirect_scissor/shaders/raycommon.glsl @@ -24,16 +24,3 @@ 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 index 7a1f5dc..2886200 100644 --- a/ray_tracing_indirect_scissor/shaders/raytrace.rchit +++ b/ray_tracing_indirect_scissor/shaders/raytrace.rchit @@ -40,17 +40,20 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 2, set = 0) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eLanterns) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; + +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; // clang-format on + void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -66,47 +69,44 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - vec3 colorIntensity = vec3(pushC.lightIntensity); + vec3 colorIntensity = vec3(pcRay.lightIntensity); float lightDistance = 100000.0; // ray direction is towards lantern, if in lantern pass. - if(pushC.lanternPassNumber >= 0) + if(pcRay.lanternPassNumber >= 0) { - LanternIndirectEntry lantern = lanterns.lanterns[pushC.lanternPassNumber]; + LanternIndirectEntry lantern = lanterns.lanterns[pcRay.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); + float distanceFade = pcRay.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) + else if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - colorIntensity = vec3(pushC.lightIntensity / (lightDistance * lightDistance)); + colorIntensity = vec3(pcRay.lightIntensity / (lightDistance * lightDistance)); L = normalize(lDir); } else // or directional light. { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -115,10 +115,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -127,7 +127,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -135,7 +135,7 @@ void main() vec3 rayDir = L; // Ordinary shadow from the simple tutorial. - if(pushC.lanternPassNumber < 0) + if(pcRay.lanternPassNumber < 0) { isShadowed = true; uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT; @@ -179,7 +179,7 @@ void main() 2 // payload (location = 2) ); // Did we hit the lantern we expected? - isShadowed = (hitLanternInstance != pushC.lanternPassNumber); + isShadowed = (hitLanternInstance != pcRay.lanternPassNumber); } } @@ -190,7 +190,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rgen b/ray_tracing_indirect_scissor/shaders/raytrace.rgen index ff6c31a..b1a2e85 100644 --- a/ray_tracing_indirect_scissor/shaders/raytrace.rgen +++ b/ray_tracing_indirect_scissor/shaders/raytrace.rgen @@ -20,47 +20,44 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 0, binding = eLanterns) buffer LanternArray { LanternIndirectEntry lanterns[]; } lanterns; + +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; + +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on -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) + if(pcRay.lanternPassNumber >= 0) { - pixelOffset.x = lanterns.lanterns[pushC.lanternPassNumber].offsetX; - pixelOffset.y = lanterns.lanterns[pushC.lanternPassNumber].offsetY; + pixelOffset.x = lanterns.lanterns[pcRay.lanternPassNumber].offsetX; + pixelOffset.y = lanterns.lanterns[pcRay.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); + const vec2 inUV = pixelCenter / vec2(pcRay.screenX, pcRay.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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; @@ -87,7 +84,7 @@ void main() // 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) + if(prd.additiveBlending && pcRay.lanternPassNumber >= 0) { oldColor = imageLoad(image, pixelIntCoord).rgb; } diff --git a/ray_tracing_indirect_scissor/shaders/raytrace.rmiss b/ray_tracing_indirect_scissor/shaders/raytrace.rmiss index 8e6b740..368a93f 100644 --- a/ray_tracing_indirect_scissor/shaders/raytrace.rmiss +++ b/ray_tracing_indirect_scissor/shaders/raytrace.rmiss @@ -20,12 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; +layout(push_constant) uniform _PushConstantRay +{ + PushConstantRay pcRay; +}; + void main() { - prd.hitValue = pushC.clearColor.xyz * 0.8; - prd.additiveBlending = false; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_indirect_scissor/shaders/vert_shader.vert b/ray_tracing_indirect_scissor/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_indirect_scissor/shaders/vert_shader.vert +++ b/ray_tracing_indirect_scissor/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_indirect_scissor/shaders/wavefront.glsl b/ray_tracing_indirect_scissor/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_indirect_scissor/shaders/wavefront.glsl +++ b/ray_tracing_indirect_scissor/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_instances/README.md b/ray_tracing_instances/README.md index 7afb84f..e96e9cf 100644 --- a/ray_tracing_instances/README.md +++ b/ray_tracing_instances/README.md @@ -26,7 +26,7 @@ In `main.cpp`, add the following includes: #include ~~~~ -Then replace the calls to `helloVk.loadModel` in `main()` by +Then replace the calls to `helloVk.loadModel` in `main()` by following, which will create instances of cube and cube_multi. ~~~~ C++ // Creation of the example @@ -39,17 +39,14 @@ Then replace the calls to `helloVk.loadModel` in `main()` by std::normal_distribution dis(1.0f, 1.0f); std::normal_distribution disn(0.05f, 0.05f); - for(int n = 0; n < 2000; ++n) + for(uint32_t n = 0; n < 2000; ++n) { - HelloVulkan::ObjInstance inst = helloVk.m_objInstance[n % 2];; float scale = fabsf(disn(gen)); nvmath::mat4f mat = nvmath::translation_mat4(nvmath::vec3f{dis(gen), 2.0f + dis(gen), dis(gen)}); mat = mat * nvmath::rotation_mat4_x(dis(gen)); mat = mat * nvmath::scale_mat4(nvmath::vec3f(scale)); - inst.transform = mat; - inst.transformIT = nvmath::transpose(nvmath::invert((inst.transform))); - helloVk.m_objInstance.push_back(inst); + helloVk.m_instances.push_back({mat, n % 2}); } ~~~~ @@ -71,16 +68,12 @@ Remove the previous code and replace it with the following std::normal_distribution disn(0.05f, 0.05f); for(int n = 0; n < 2000; ++n) { - helloVk.loadModel(nvh::findFile("media/scenes/cube_multi.obj", defaultSearchPaths, true)); - HelloVulkan::ObjInstance& inst = helloVk.m_objInstance.back(); - float scale = fabsf(disn(gen)); - nvmath::mat4f mat = - nvmath::translation_mat4(nvmath::vec3f{dis(gen), 2.0f + dis(gen), dis(gen)}); - mat = mat * nvmath::rotation_mat4_x(dis(gen)); - mat = mat * nvmath::scale_mat4(nvmath::vec3f(scale)); - inst.transform = mat; - inst.transformIT = nvmath::transpose(nvmath::invert((inst.transform))); + nvmath::mat4f mat = nvmath::translation_mat4(nvmath::vec3f{dis(gen), 2.0f + dis(gen), dis(gen)}); + mat = mat * nvmath::rotation_mat4_x(dis(gen)); + mat = mat * nvmath::scale_mat4(nvmath::vec3f(scale)); + + helloVk.loadModel(nvh::findFile("media/scenes/cube_multi.obj", defaultSearchPaths, true), mat); } helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); diff --git a/ray_tracing_instances/hello_vulkan.cpp b/ray_tracing_instances/hello_vulkan.cpp index b741ddd..31f0040 100644 --- a/ray_tracing_instances/hello_vulkan.cpp +++ b/ray_tracing_instances/hello_vulkan.cpp @@ -42,17 +42,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -72,16 +61,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -97,7 +87,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -117,13 +107,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 3) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -140,11 +131,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -152,7 +143,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -164,7 +155,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -224,30 +215,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -257,9 +253,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -268,15 +264,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -362,8 +358,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -417,14 +413,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -615,7 +611,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -664,19 +660,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -689,9 +688,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -711,8 +710,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -725,7 +724,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -797,7 +796,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -850,10 +849,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -862,7 +861,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); auto& regions = m_sbtWrapper.getRegions(); diff --git a/ray_tracing_instances/hello_vulkan.h b/ray_tracing_instances/hello_vulkan.h index 0e1990d..fe855aa 100644 --- a/ray_tracing_instances/hello_vulkan.h +++ b/ray_tracing_instances/hello_vulkan.h @@ -41,6 +41,7 @@ using Allocator = nvvk::ResourceAllocatorDedicated; #include "nvvk/appbase_vk.hpp" #include "nvvk/debug_util_vk.hpp" #include "nvvk/descriptorsets_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -62,7 +63,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -80,32 +81,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices{0}; - VkDeviceAddress indices{0}; - VkDeviceAddress materials{0}; - VkDeviceAddress materialIndices{0}; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -115,8 +111,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -126,7 +122,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -168,11 +164,6 @@ public: VkPipeline m_rtPipeline; nvvk::SBTWrapper m_sbtWrapper; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{}; }; diff --git a/ray_tracing_instances/main.cpp b/ray_tracing_instances/main.cpp index a193839..0b2bbee 100644 --- a/ray_tracing_instances/main.cpp +++ b/ray_tracing_instances/main.cpp @@ -84,12 +84,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -190,17 +190,14 @@ int main(int argc, char** argv) std::mt19937 gen(rd()); //Standard mersenne_twister_engine seeded with rd() std::normal_distribution dis(1.0f, 1.0f); std::normal_distribution disn(0.05f, 0.05f); - for(int n = 0; n < 2000; ++n) + for(uint32_t n = 0; n < 2000; ++n) { - helloVk.loadModel(nvh::findFile("media/scenes/cube_multi.obj", defaultSearchPaths, true)); - HelloVulkan::ObjInstance& inst = helloVk.m_objInstance.back(); - float scale = fabsf(disn(gen)); nvmath::mat4f mat = nvmath::translation_mat4(nvmath::vec3f{dis(gen), 2.0f + dis(gen), dis(gen)}); mat = mat * nvmath::rotation_mat4_x(dis(gen)); mat = mat * nvmath::scale_mat4(nvmath::vec3f(scale)); - inst.transform = mat; - inst.transformIT = nvmath::transpose(nvmath::invert((inst.transform))); + + helloVk.loadModel(nvh::findFile("media/scenes/cube_multi.obj", defaultSearchPaths, true), mat); } helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); @@ -212,7 +209,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_instances/shaders/frag_shader.frag b/ray_tracing_instances/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_instances/shaders/frag_shader.frag +++ b/ray_tracing_instances/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_instances/shaders/host_device.h b/ray_tracing_instances/shaders/host_device.h new file mode 100644 index 0000000..a8377f2 --- /dev/null +++ b/ray_tracing_instances/shaders/host_device.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_instances/shaders/raytrace.rchit b/ray_tracing_instances/shaders/raytrace.rchit index 2b523a7..2eb634e 100644 --- a/ray_tracing_instances/shaders/raytrace.rchit +++ b/ray_tracing_instances/shaders/raytrace.rchit @@ -39,25 +39,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +66,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +97,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +109,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -147,7 +137,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_instances/shaders/raytrace.rgen b/ray_tracing_instances/shaders/raytrace.rgen index ebae40a..4802cd0 100644 --- a/ray_tracing_instances/shaders/raytrace.rgen +++ b/ray_tracing_instances/shaders/raytrace.rgen @@ -20,21 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + void main() { @@ -42,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; diff --git a/ray_tracing_instances/shaders/raytrace.rmiss b/ray_tracing_instances/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_instances/shaders/raytrace.rmiss +++ b/ray_tracing_instances/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_instances/shaders/vert_shader.vert b/ray_tracing_instances/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_instances/shaders/vert_shader.vert +++ b/ray_tracing_instances/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_instances/shaders/wavefront.glsl b/ray_tracing_instances/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_instances/shaders/wavefront.glsl +++ b/ray_tracing_instances/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_intersection/README.md b/ray_tracing_intersection/README.md index 82218ea..0e05aae 100644 --- a/ray_tracing_intersection/README.md +++ b/ray_tracing_intersection/README.md @@ -25,28 +25,36 @@ To do this, we will need to: * Add a new closest hit shader (.chit) * Create `VkAccelerationStructureGeometryKHR` from `VkAccelerationStructureGeometryAabbsDataKHR` -## Creating all spheres +## Creating all implicit objects -In the HelloVulkan class, we will add the structures we will need. First the structure that defines a sphere. +In `host_device.h`, we will add the structures we will need. First the structure that defines a sphere. Note that it will also be use for defining the box. This information will be retrieve in the intersection shader to return the intersection point. ~~~~ C++ - struct Sphere - { - nvmath::vec3f center; - float radius; - }; +struct Sphere +{ + vec3 center; + float radius; +}; ~~~~ Then we need the Aabb structure holding all the spheres, but also used for the creation of the BLAS (`VK_GEOMETRY_TYPE_AABBS_KHR`). ~~~~ C++ - struct Aabb - { - nvmath::vec3f minimum; - nvmath::vec3f maximum; - }; +struct Aabb +{ + vec3 minimum; + vec3 maximum; +}; ~~~~ +Also add the following define to distinguish between sphere and box + +~~~~ C++ +#define KIND_SPHERE 0 +#define KIND_CUBE 1 +~~~~ + + All the information will need to be hold in buffers, which will be available to the shaders. ~~~~ C++ @@ -57,11 +65,11 @@ All the information will need to be hold in buffers, which will be available to nvvkBuffer m_spheresMatIndexBuffer; // Define which sphere uses which material ~~~~ -Finally, there are two functions, one to create the spheres, and one that will create the intermediate structure for the BLAS. +Finally, there are two functions, one to create the spheres, and one that will create the intermediate structure for the BLAS, similar to `objectToVkGeometryKHR()`. ~~~~ C++ - void createSpheres(); - nvvk::RaytracingBuilderKHR::Blas sphereToVkGeometryKHR(); + void createSpheres(); + auto sphereToVkGeometryKHR(); ~~~~ The following implementation will create 2.000.000 spheres at random positions and radius. It will create the Aabb from the sphere definition, two materials which will be assigned alternatively to each object. All the created information will be moved to Vulkan buffers to be accessed by the intersection and closest shaders. @@ -120,9 +128,13 @@ void HelloVulkan::createSpheres(uint32_t nbSpheres) nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex); auto cmdBuf = genCmdBuf.createCommandBuffer(); m_spheresBuffer = m_alloc.createBuffer(cmdBuf, m_spheres, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - m_spheresAabbBuffer = m_alloc.createBuffer(cmdBuf, aabbs, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); - m_spheresMatIndexBuffer = m_alloc.createBuffer(cmdBuf, matIdx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); - m_spheresMatColorBuffer = m_alloc.createBuffer(cmdBuf, materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_spheresAabbBuffer = m_alloc.createBuffer(cmdBuf, aabbs, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR); + m_spheresMatIndexBuffer = + m_alloc.createBuffer(cmdBuf, matIdx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); + m_spheresMatColorBuffer = + m_alloc.createBuffer(cmdBuf, materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT); genCmdBuf.submitAndWait(cmdBuf); // Debug information @@ -132,11 +144,14 @@ void HelloVulkan::createSpheres(uint32_t nbSpheres) m_debug.setObjectName(m_spheresMatIndexBuffer.buffer, "spheresMatIdx"); // Adding an extra instance to get access to the material buffers + ObjDesc objDesc{}; + objDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresMatColorBuffer.buffer); + objDesc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresMatIndexBuffer.buffer); + m_objDesc.emplace_back(objDesc); + ObjInstance instance{}; - instance.objIndex = static_cast(m_objModel.size()); - instance.materials = nvvk::getBufferDeviceAddress(m_device, m_spheresMatColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, m_spheresMatIndexBuffer.buffer); - m_objInstance.emplace_back(instance); + instance.objIndex = static_cast(m_objModel.size()); + m_instances.emplace_back(instance); } ~~~~ @@ -157,7 +172,7 @@ What is changing compare to triangle primitive is the Aabb data (see Aabb struct //-------------------------------------------------------------------------------------------------- // Returning the ray tracing geometry used for the BLAS, containing all spheres // -nvvk::RaytracingBuilderKHR::BlasInput HelloVulkan::sphereToVkGeometryKHR() +auto HelloVulkan::sphereToVkGeometryKHR() { VkDeviceAddress dataAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresAabbBuffer.buffer); @@ -191,10 +206,10 @@ In `main.cpp`, where we are loading the OBJ model, we can replace it with ~~~~ C++ // Creation of the example helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); - helloVk.createSpheres(); + helloVk.createSpheres(2000000); ~~~~ - **Note:** it is possible to have more OBJ models, but the spheres will need to be added after all of them. + **:warning: Note:** it is possible to have more OBJ models, but the spheres will need to be added after all of them, due the way we build TLAS. The scene will be large, better to move the camera out @@ -241,10 +256,11 @@ The hitGroupId will be set to 1 instead of 0. We need to add a new hit group for Because we have added an extra instance when creating the implicit objects, there is one element less to loop for. Therefore the loop will now look like this: ~~~~ C++ - auto nbObj = static_cast(m_objInstance.size()) - 1; + auto nbObj = static_cast(m_instances.size()) - 1; tlas.reserve(nbObj); for(uint32_t i = 0; i < nbObj; i++) { + const auto& inst = m_instances[i]; ... } ~~~~ @@ -253,29 +269,36 @@ Because we have added an extra instance when creating the implicit objects, ther Just after the loop and before building the TLAS, we need to add the following. ~~~~ C++ - // Add the blas containing all spheres + // Add the blas containing all implicit objects { - nvvk::RaytracingBuilder::Instance rayInst; - rayInst.transform = m_objInstance[0].transform; // Position of the instance - rayInst.instanceCustomId = nbObj; // gl_InstanceCustomIndexEXT - rayInst.blasId = static_cast(m_objModel.size()); - rayInst.hitGroupId = 1; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(nvmath::mat4f(1)); // Position of the instance (identity) + rayInst.instanceCustomIndex = nbObj; // nbObj == last object == implicit + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(static_cast(m_objModel.size())); + rayInst.instanceShaderBindingTableRecordOffset = 1; // We will use the same hit group for all objects + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 tlas.emplace_back(rayInst); } ~~~~ -The `instanceCustomId` will give us the last element of m_objInstance, and in the shader will will be able to access the materials +The `instanceCustomIndex` will give us the last element of `m_instances`, and in the shader will will be able to access the materials assigned to the implicit objects. ## Descriptors To access the newly created buffers holding all the spheres, some changes are required to the descriptors. + +Add a new enum to `Binding` +~~~~ C++ + eImplicit = 3, // All implicit objects +~~~~ + The descriptor need to add an binding to the implicit object buffer. ~~~~ C++ // Storing spheres (binding = 3) - m_descSetLayoutBind.addBinding(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + m_descSetLayoutBind.addBinding(eImplicit, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR); ~~~~ @@ -284,7 +307,7 @@ Then write the buffer for the spheres after the array of textures ~~~~ C++ VkDescriptorBufferInfo dbiSpheres{m_spheresBuffer.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 3, &dbiSpheres)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, eImplicit, &dbiSpheres)); ~~~~ ## Intersection Shader @@ -323,27 +346,6 @@ Here is how the two hit group looks like: m_rtShaderGroups.push_back(group); ~~~~ -### raycommon.glsl - -To share the structure of the data across the shaders, we can add the following to `raycommon.glsl` - -~~~~ C++ -struct Sphere -{ - vec3 center; - float radius; -}; - -struct Aabb -{ - vec3 minimum; - vec3 maximum; -}; - -#define KIND_SPHERE 0 -#define KIND_CUBE 1 -~~~~ - ### raytrace.rint The intersection shader `raytrace.rint` need to be added to the shader directory and CMake to be rerun such that it is added to the project. The shader will be called every time a ray will hit one of the Aabb of the scene. Note that there are no Aabb information that can be retrieved in the intersection shader. It is also not possible to have the value of the hit point that the ray tracer might have calculated on the GPU. @@ -360,6 +362,7 @@ We first declare the extensions and include common files. #extension GL_GOOGLE_include_directive : enable #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_EXT_buffer_reference2 : require + #include "raycommon.glsl" #include "wavefront.glsl" ~~~~ @@ -368,7 +371,7 @@ We first declare the extensions and include common files. The following is the topology of all spheres, which we will be able to retrieve using `gl_PrimitiveID`. ~~~~ C++ -layout(binding = 3, set = 1, scalar) buffer allSpheres_ +layout(binding = 3, set = eImplicit, scalar) buffer allSpheres_ { Sphere allSpheres[]; }; diff --git a/ray_tracing_intersection/hello_vulkan.cpp b/ray_tracing_intersection/hello_vulkan.cpp index 4c6a2e3..9ab57f3 100644 --- a/ray_tracing_intersection/hello_vulkan.cpp +++ b/ray_tracing_intersection/hello_vulkan.cpp @@ -41,17 +41,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -71,16 +60,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -96,7 +86,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -116,16 +106,17 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Storing spheres (binding = 3) - m_descSetLayoutBind.addBinding(3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Implicit geometries + m_descSetLayoutBind.addBinding(eImplicit, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR); @@ -142,11 +133,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -154,10 +145,10 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); VkDescriptorBufferInfo dbiSpheres{m_spheresBuffer.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 3, &dbiSpheres)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, eImplicit, &dbiSpheres)); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -169,7 +160,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -229,30 +220,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -262,9 +258,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -273,15 +269,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -367,8 +363,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -427,15 +423,16 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - uint32_t nbInst = static_cast(m_objInstance.size() - 1); // Remove the implicit object + uint32_t nbInst = static_cast(m_instances.size() - 1); // Remove the implicit object for(uint32_t i = 0; i < nbInst; ++i) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& inst = m_instances[i]; + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -625,7 +622,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -659,7 +656,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) //-------------------------------------------------------------------------------------------------- // Returning the ray tracing geometry used for the BLAS, containing all spheres // -nvvk::RaytracingBuilderKHR::BlasInput HelloVulkan::sphereToVkGeometryKHR() +auto HelloVulkan::sphereToVkGeometryKHR() { VkDeviceAddress dataAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresAabbBuffer.buffer); @@ -754,13 +751,19 @@ void HelloVulkan::createSpheres(uint32_t nbSpheres) // Adding an extra instance to get access to the material buffers + ObjDesc objDesc{}; + objDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresMatColorBuffer.buffer); + objDesc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, m_spheresMatIndexBuffer.buffer); + m_objDesc.emplace_back(objDesc); + ObjInstance instance{}; - instance.objIndex = static_cast(m_objModel.size()); - instance.materials = nvvk::getBufferDeviceAddress(m_device, m_spheresMatColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, m_spheresMatIndexBuffer.buffer); - m_objInstance.emplace_back(instance); + instance.objIndex = static_cast(m_objModel.size()); + m_instances.emplace_back(instance); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createBottomLevelAS() { // BLAS - Storing each primitive in a geometry @@ -783,33 +786,38 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - auto nbObj = static_cast(m_objInstance.size()) - 1; + auto nbObj = static_cast(m_instances.size()) - 1; tlas.reserve(nbObj); for(uint32_t i = 0; i < nbObj; i++) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + const auto& inst = m_instances[i]; + + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } - // Add the blas containing all spheres + // Add the blas containing all implicit objects { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[0].transform); // Position of the instance - rayInst.instanceCustomIndex = nbObj; // gl_InstanceCustomIndexEXT + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(nvmath::mat4f(1)); // (identity) + rayInst.instanceCustomIndex = nbObj; // nbObj == last object == implicit rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(static_cast(m_objModel.size())); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 1; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } @@ -823,9 +831,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -845,8 +853,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -859,7 +867,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -947,7 +955,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -1013,7 +1021,7 @@ void HelloVulkan::createRtShaderBindingTable() VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Map the SBT buffer and write in the handles. void* mapped = m_alloc.map(m_rtSBTBuffer); @@ -1034,10 +1042,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -1046,7 +1054,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); // Size of a program identifier diff --git a/ray_tracing_intersection/hello_vulkan.h b/ray_tracing_intersection/hello_vulkan.h index 8f06847..971eb73 100644 --- a/ray_tracing_intersection/hello_vulkan.h +++ b/ray_tracing_intersection/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -44,7 +45,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -62,32 +63,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices{0}; - VkDeviceAddress indices{0}; - VkDeviceAddress materials{0}; - VkDeviceAddress materialIndices{0}; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // Information pushed at each draw call - struct ObjPushConstant - { - nvmath::vec3f lightPosition{10.f, 55.f, 8.f}; - int instanceId{0}; // To retrieve the transformation matrix - float lightIntensity{1000.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 55.f, 8.f}, // light position + 0, // instance Id + 1000.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -97,8 +93,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -107,7 +103,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -150,33 +146,16 @@ public: VkPipeline m_rtPipeline; nvvk::Buffer m_rtSBTBuffer; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{}; - struct Sphere - { - nvmath::vec3f center; - float radius{0}; - }; - - struct Aabb - { - nvmath::vec3f minimum; - nvmath::vec3f maximum; - }; - - nvvk::RaytracingBuilderKHR::BlasInput sphereToVkGeometryKHR(); - std::vector m_spheres; // All spheres nvvk::Buffer m_spheresBuffer; // Buffer holding the spheres nvvk::Buffer m_spheresAabbBuffer; // Buffer of all Aabb nvvk::Buffer m_spheresMatColorBuffer; // Multiple materials nvvk::Buffer m_spheresMatIndexBuffer; // Define which sphere uses which material - void createSpheres(uint32_t nbSpheres); + + void createSpheres(uint32_t nbSpheres); + auto sphereToVkGeometryKHR(); }; diff --git a/ray_tracing_intersection/main.cpp b/ray_tracing_intersection/main.cpp index 688eb2e..47a952c 100644 --- a/ray_tracing_intersection/main.cpp +++ b/ray_tracing_intersection/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } ImGui::Text("Nb Spheres and Cubes: %llu", helloVk.m_spheres.size()); } @@ -167,7 +167,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_intersection/shaders/frag_shader.frag b/ray_tracing_intersection/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_intersection/shaders/frag_shader.frag +++ b/ray_tracing_intersection/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_intersection/shaders/host_device.h b/ray_tracing_intersection/shaders/host_device.h new file mode 100644 index 0000000..b0495ae --- /dev/null +++ b/ray_tracing_intersection/shaders/host_device.h @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2, // Access to textures + eImplicit = 3 // All implicit objects +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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 Sphere +{ + vec3 center; + float radius; +}; + +struct Aabb +{ + vec3 minimum; + vec3 maximum; +}; + +#define KIND_SPHERE 0 +#define KIND_CUBE 1 + +#endif diff --git a/ray_tracing_intersection/shaders/raycommon.glsl b/ray_tracing_intersection/shaders/raycommon.glsl index 8e8b219..b896c84 100644 --- a/ray_tracing_intersection/shaders/raycommon.glsl +++ b/ray_tracing_intersection/shaders/raycommon.glsl @@ -21,18 +21,3 @@ struct hitPayload { vec3 hitValue; }; - -struct Sphere -{ - vec3 center; - float radius; -}; - -struct Aabb -{ - vec3 minimum; - vec3 maximum; -}; - -#define KIND_SPHERE 0 -#define KIND_CUBE 1 diff --git a/ray_tracing_intersection/shaders/raytrace.rchit b/ray_tracing_intersection/shaders/raytrace.rchit index 2b523a7..c79911d 100644 --- a/ray_tracing_intersection/shaders/raytrace.rchit +++ b/ray_tracing_intersection/shaders/raytrace.rchit @@ -22,7 +22,6 @@ #extension GL_EXT_nonuniform_qualifier : enable #extension GL_EXT_scalar_block_layout : enable #extension GL_GOOGLE_include_directive : enable - #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_EXT_buffer_reference2 : require @@ -39,25 +38,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +65,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +96,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +108,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -147,7 +136,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_intersection/shaders/raytrace.rgen b/ray_tracing_intersection/shaders/raytrace.rgen index ebae40a..4802cd0 100644 --- a/ray_tracing_intersection/shaders/raytrace.rgen +++ b/ray_tracing_intersection/shaders/raytrace.rgen @@ -20,21 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + void main() { @@ -42,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; diff --git a/ray_tracing_intersection/shaders/raytrace.rint b/ray_tracing_intersection/shaders/raytrace.rint index f7c530d..b772c46 100644 --- a/ray_tracing_intersection/shaders/raytrace.rint +++ b/ray_tracing_intersection/shaders/raytrace.rint @@ -28,7 +28,7 @@ #include "wavefront.glsl" -layout(binding = 3, set = 1, scalar) buffer allSpheres_ +layout(set = 1, binding = eImplicit, scalar) buffer allSpheres_ { Sphere allSpheres[]; }; diff --git a/ray_tracing_intersection/shaders/raytrace.rmiss b/ray_tracing_intersection/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_intersection/shaders/raytrace.rmiss +++ b/ray_tracing_intersection/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_intersection/shaders/raytrace2.rchit b/ray_tracing_intersection/shaders/raytrace2.rchit index fdbdd1c..e20e916 100644 --- a/ray_tracing_intersection/shaders/raytrace2.rchit +++ b/ray_tracing_intersection/shaders/raytrace2.rchit @@ -22,6 +22,7 @@ #extension GL_EXT_nonuniform_qualifier : enable #extension GL_EXT_scalar_block_layout : enable #extension GL_GOOGLE_include_directive : enable + #extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #extension GL_EXT_buffer_reference2 : require @@ -39,27 +40,19 @@ layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indice layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -layout(binding = 3, set = 1, scalar) buffer allSpheres_ {Sphere i[];} allSpheres; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; +layout(set = 1, binding = eImplicit, scalar) buffer allSpheres_ {Sphere i[];} allSpheres; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; // clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; - void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); @@ -68,32 +61,32 @@ void main() Sphere instance = allSpheres.i[gl_PrimitiveID]; // Computing the normal at hit position - vec3 normal = normalize(worldPos - instance.center); + vec3 worldNrm = normalize(worldPos - instance.center); // Computing the normal for a cube if(gl_HitKindEXT == KIND_CUBE) // Aabb { - vec3 absN = abs(normal); + vec3 absN = abs(worldNrm); float maxC = max(max(absN.x, absN.y), absN.z); - normal = (maxC == absN.x) ? vec3(sign(normal.x), 0, 0) : - (maxC == absN.y) ? vec3(0, sign(normal.y), 0) : vec3(0, 0, sign(normal.z)); + worldNrm = (maxC == absN.x) ? vec3(sign(worldNrm.x), 0, 0) : + (maxC == absN.y) ? vec3(0, sign(worldNrm.y), 0) : vec3(0, 0, sign(worldNrm.z)); } // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -101,12 +94,12 @@ void main() WaveFrontMaterial mat = materials.m[matIdx]; // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); vec3 specular = vec3(0); float attenuation = 0.3; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -135,7 +128,7 @@ void main() { attenuation = 1; // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_intersection/shaders/vert_shader.vert b/ray_tracing_intersection/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_intersection/shaders/vert_shader.vert +++ b/ray_tracing_intersection/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_intersection/shaders/wavefront.glsl b/ray_tracing_intersection/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_intersection/shaders/wavefront.glsl +++ b/ray_tracing_intersection/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_jitter_cam/README.md b/ray_tracing_jitter_cam/README.md index 6a0ae9f..c626f04 100644 --- a/ray_tracing_jitter_cam/README.md +++ b/ray_tracing_jitter_cam/README.md @@ -58,31 +58,17 @@ Since our jittered samples will be accumulated across frames, we need to know wh Note that the uniform image is read/write, which makes it possible to accumulate previous frames. -In `raytrace.rgen`, add the push constant block from `raytrace.rchit`, adding a new `frame` member: +Add the frame member to the `PushConstantRay` struct in `shaders/host_device.h`: ~~~~ C++ -layout(push_constant) uniform Constants +struct PushConstantRay { vec4 clearColor; vec3 lightPosition; float lightIntensity; int lightType; int frame; -} -pushC; -~~~~ - -Also add this frame member to the `RtPushConstant` struct in `hello_vulkan.h`: - -~~~~ C++ - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity; - int lightType; - int frame{0}; - } m_rtPushConstants; +}; ~~~~ ## Random and Jitter @@ -91,7 +77,7 @@ In `raytrace.rgen`, at the beginning of `main()`, initialize the random seed: ~~~~ C++ // Initialize the random number - uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pushC.frame); + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pcRay.frame); ~~~~ Then we need two random numbers to vary the X and Y inside the pixel, except for frame 0, where we always shoot @@ -102,7 +88,7 @@ float r1 = rnd(seed); float r2 = rnd(seed); // Subpixel jitter: send the ray through a different position inside the pixel // each time, to provide antialiasing. -vec2 subpixel_jitter = pushC.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); +vec2 subpixel_jitter = pcRay.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); ~~~~ Now we only need to change how we compute the pixel center: @@ -118,9 +104,9 @@ Otherwise, we combine the new image with the previous `frame` frames. ~~~~ C++ // Do accumulation over time - if(pushC.frame > 0) + if(pcRay.frame > 0) { - float a = 1.0f / float(pushC.frame + 1); + float a = 1.0f / float(pcRay.frame + 1); vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, prd.hitValue, a), 1.f)); } @@ -164,7 +150,7 @@ void HelloVulkan::updateFrame() refCamMatrix = m; refFov = fov; } - m_rtPushConstants.frame++; + m_pcRay.frame++; } ~~~~ @@ -173,7 +159,7 @@ Since `resetFrame` will be called before `updateFrame` increments the frame coun ~~~~ C++ void HelloVulkan::resetFrame() { - m_rtPushConstants.frame = -1; + m_pcRay.frame = -1; } ~~~~ @@ -246,7 +232,7 @@ changed |= ImGui::SliderInt("Max Frames", &helloVk.m_maxFrames, 1, 100); Then in `raytrace()`, immediately after the call to `updateFrame()`, return if the current frame has exceeded the max frame. ~~~~ C++ - if(m_rtPushConstants.frame >= m_maxFrames) + if(m_pcRay.frame >= m_maxFrames) return; ~~~~ diff --git a/ray_tracing_jitter_cam/hello_vulkan.cpp b/ray_tracing_jitter_cam/hello_vulkan.cpp index 61cdc34..14ad2af 100644 --- a/ray_tracing_jitter_cam/hello_vulkan.cpp +++ b/ray_tracing_jitter_cam/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,13 +105,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -138,11 +129,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -150,7 +141,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -162,7 +153,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -222,30 +213,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -255,9 +251,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -266,15 +262,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -360,8 +356,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -415,14 +411,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -613,7 +609,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -662,19 +658,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -687,9 +686,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -709,8 +708,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -723,7 +722,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -795,7 +794,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -867,7 +866,7 @@ void HelloVulkan::createRtShaderBindingTable() VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Map the SBT buffer and write in the handles. void* mapped = m_alloc.map(m_rtSBTBuffer); @@ -887,15 +886,15 @@ void HelloVulkan::createRtShaderBindingTable() void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) { updateFrame(); - if(m_rtPushConstants.frame >= m_maxFrames) + if(m_pcRay.frame >= m_maxFrames) return; m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -904,7 +903,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); // Size of a program identifier @@ -946,10 +945,10 @@ void HelloVulkan::updateFrame() refCamMatrix = m; refFov = fov; } - m_rtPushConstants.frame++; + m_pcRay.frame++; } void HelloVulkan::resetFrame() { - m_rtPushConstants.frame = -1; + m_pcRay.frame = -1; } diff --git a/ray_tracing_jitter_cam/hello_vulkan.h b/ray_tracing_jitter_cam/hello_vulkan.h index 276f1d9..e6c9ed6 100644 --- a/ray_tracing_jitter_cam/hello_vulkan.h +++ b/ray_tracing_jitter_cam/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -44,7 +45,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -62,32 +63,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -97,8 +93,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -107,7 +103,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -152,12 +148,6 @@ public: nvvk::Buffer m_rtSBTBuffer; int m_maxFrames{10}; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - int frame{0}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{}; }; diff --git a/ray_tracing_jitter_cam/main.cpp b/ray_tracing_jitter_cam/main.cpp index 47c6dd4..7676df1 100644 --- a/ray_tracing_jitter_cam/main.cpp +++ b/ray_tracing_jitter_cam/main.cpp @@ -57,7 +57,7 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - auto& pc = helloVk.m_pushConstant; + auto& pc = helloVk.m_pcRaster; changed |= ImGui::RadioButton("Point", &pc.lightType, 0); ImGui::SameLine(); changed |= ImGui::RadioButton("Infinite", &pc.lightType, 1); @@ -170,7 +170,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_jitter_cam/shaders/frag_shader.frag b/ray_tracing_jitter_cam/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_jitter_cam/shaders/frag_shader.frag +++ b/ray_tracing_jitter_cam/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_jitter_cam/shaders/host_device.h b/ray_tracing_jitter_cam/shaders/host_device.h new file mode 100644 index 0000000..926e29d --- /dev/null +++ b/ray_tracing_jitter_cam/shaders/host_device.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int frame; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_jitter_cam/shaders/passthrough.vert b/ray_tracing_jitter_cam/shaders/passthrough.vert index 2f90989..65c3460 100644 --- a/ray_tracing_jitter_cam/shaders/passthrough.vert +++ b/ray_tracing_jitter_cam/shaders/passthrough.vert @@ -17,7 +17,6 @@ * SPDX-License-Identifier: Apache-2.0 */ - #version 450 layout(location = 0) out vec2 outUV; diff --git a/ray_tracing_jitter_cam/shaders/raytrace.rchit b/ray_tracing_jitter_cam/shaders/raytrace.rchit index 2b523a7..2eb634e 100644 --- a/ray_tracing_jitter_cam/shaders/raytrace.rchit +++ b/ray_tracing_jitter_cam/shaders/raytrace.rchit @@ -39,25 +39,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +66,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +97,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +109,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -147,7 +137,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_jitter_cam/shaders/raytrace.rgen b/ray_tracing_jitter_cam/shaders/raytrace.rgen index 740edca..a816f35 100644 --- a/ray_tracing_jitter_cam/shaders/raytrace.rgen +++ b/ray_tracing_jitter_cam/shaders/raytrace.rgen @@ -20,39 +20,26 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require #include "random.glsl" #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; - -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int frame; -} -pushC; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on const int NBSAMPLES = 10; void main() { // Initialize the random number - uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pushC.frame); + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pcRay.frame); vec3 hitValues = vec3(0); @@ -63,15 +50,15 @@ void main() float r2 = rnd(seed); // Subpixel jitter: send the ray through a different position inside the pixel // each time, to provide antialiasing. - vec2 subpixel_jitter = pushC.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); + vec2 subpixel_jitter = pcRay.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + subpixel_jitter; const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; @@ -94,9 +81,9 @@ void main() prd.hitValue = hitValues / NBSAMPLES; // Do accumulation over time - if(pushC.frame > 0) + if(pcRay.frame > 0) { - float a = 1.0f / float(pushC.frame + 1); + float a = 1.0f / float(pcRay.frame + 1); vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, prd.hitValue, a), 1.f)); } diff --git a/ray_tracing_jitter_cam/shaders/raytrace.rmiss b/ray_tracing_jitter_cam/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_jitter_cam/shaders/raytrace.rmiss +++ b/ray_tracing_jitter_cam/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_jitter_cam/shaders/vert_shader.vert b/ray_tracing_jitter_cam/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_jitter_cam/shaders/vert_shader.vert +++ b/ray_tracing_jitter_cam/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_jitter_cam/shaders/wavefront.glsl b/ray_tracing_jitter_cam/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_jitter_cam/shaders/wavefront.glsl +++ b/ray_tracing_jitter_cam/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_manyhits/README.md b/ray_tracing_manyhits/README.md index 5965fc8..dd12205 100644 --- a/ray_tracing_manyhits/README.md +++ b/ray_tracing_manyhits/README.md @@ -10,7 +10,6 @@ The ray tracing tutorial only uses one closest hit shader, but it is also possib For example, this could be used to give different models different shaders, or to use a less complex shader when tracing reflections. - ## Setting up the Scene For this example, we will load the `wuson` model and create another translated instance of it. @@ -21,11 +20,8 @@ Then you can change the `helloVk.loadModel` calls to the following: // Creation of the example helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths, true), nvmath::translation_mat4(nvmath::vec3f(-1, 0, 0))); - - HelloVulkan::ObjInstance inst = helloVk.m_objInstance[0]; // Instance the wuson object - inst.transform = nvmath::translation_mat4(nvmath::vec3f(1, 0, 0)); - inst.transformIT = nvmath::transpose(nvmath::invert(inst.transform)); - helloVk.m_objInstance.push_back(inst); // Adding an instance of the wuson + + helloVk.m_instances.push_back({nvmath::translation_mat4(nvmath::vec3f(1, 0, 0)), 0}); // Adding an instance of the Wuson helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); ~~~~ @@ -93,43 +89,28 @@ If you set the offset to `1`, then all ray hits will use the new CHIT, and the r ![](images/manyhits2.png) -!!! Warning - After testing this out, make sure to revert this change in `raytrace.rgen` before continuing. +**:warning:** After testing this out, make sure to revert this change in `raytrace.rgen` before continuing. ### `hello_vulkan.h` -In the `ObjInstance` structure, we will add a new member variable that specifies which hit shader the instance will use: +In the `ObjInstance` structure, we will add a new member `hitgroup` variable that specifies which hit shader the instance will use: ~~~~ C++ -uint32_t hitgroup{0}; // Hit group of the instance + struct ObjInstance + { + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference + int hitgroup{0}; // Hit group of the instance + }; ~~~~ -This change also needs to be reflected in the `sceneDesc` structure in `wavefront.glsl`: - -~~~~ C++ -struct SceneDesc -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; - int hitGroup; -}; -~~~~ - - **Note:** - The solution will not automatically recompile the shaders after this change to `wavefront.glsl`; instead, you will need to recompile all of the SPIR-V shaders. - ### `hello_vulkan.cpp` -Finally, we need to tell the top-level acceleration structure which hit group to use for each instance. In `createTopLevelAS()` in `hello_vulkan.cpp`, change the line setting `rayInst.hitGroupId` to +Finally, we need to tell the top-level acceleration structure which hit group to use for each instance. In `createTopLevelAS()` +in `hello_vulkan.cpp`, we will offset the record of the shading binding table (SBT) with the hit group. ~~~~ C++ -rayInst.hitGroupId = m_objInstance[i].hitgroup; +rayInst.instanceShaderBindingTableRecordOffset = inst.hitgroup; // Using the hit group set in main ~~~~ ### Choosing the Hit shader @@ -137,8 +118,8 @@ rayInst.hitGroupId = m_objInstance[i].hitgroup; Back in `main.cpp`, after loading the scene's models, we can now have both `wuson` models use the new CHIT by adding the following: ~~~~ C++ - helloVk.m_objInstance[0].hitgroup = 1; - helloVk.m_objInstance[1].hitgroup = 1; + helloVk.m_instances[0].hitgroup = 1; + helloVk.m_instances[1].hitgroup = 1; ~~~~ ![](images/manyhits3.png) @@ -149,33 +130,24 @@ When creating the [Shader Binding Table](https://www.khronos.org/registry/vulkan This information can be used to pass extra information to a shader, for each entry in the SBT. - **Note:** + **:warning: Note:** Since each entry in an SBT group must have the same size, each entry of the group has to have enough space to accommodate the largest element in the entire group. The following diagram represents our current SBT, with the addition of some data to `HitGroup1`. As mentioned in the **note**, even if -`HitGroup0` doesn't have any shader record data, it still needs to have the same size as `HitGroup1`. +`HitGroup0` doesn't have any shader record data, it still needs to have the same size as `HitGroup1`, the largest of the hit group. -~~~~ Bash -+-----------+----------+ -| RayGen | Handle 0 | -+-----------+----------+ -| Miss | Handle 1 | -+-----------+----------+ -| Miss | Handle 2 | -+-----------+----------+ -| HitGroup0 | Handle 3 | -| | -Empty- | -+-----------+----------+ -| HitGroup1 | Handle 4 | -| | Data 0 | -+-----------+----------+ -~~~~ +|Group|Handle| +| ------ | ------ | +| RayGen | Handle 0 | +| Miss0 | Handle 1 | +| Miss1 | Handle 2 | +| HitGroup0 | Handle 3
-Empty- | +| HitGroup1 | Handle 4
Data 0 | ## `hello_vulkan.h` In the HelloVulkan class, we will add a structure to hold the hit group data. - ### `raytrace2.rchit` In the closest hit shader, we can retrieve the shader record using the `layout(shaderRecordEXT)` descriptor ~~~~ C++ -layout(shaderRecordEXT) buffer sr_ { vec4 c; } shaderRec; +layout(shaderRecordEXT) buffer sr_ { vec4 shaderRec; }; ~~~~ and use this information to return the color: @@ -198,14 +169,13 @@ and use this information to return the color: ~~~~ C++ void main() { - prd.hitValue = shaderRec.c.rgb; + prd.hitValue = shaderRec.rgb; } ~~~~ - **Note:** + **:warning: Note:** Adding a new shader requires to rerun CMake to added to the project compilation system. - ### `main.cpp` In `main`, after we set which hit group an instance will use, we can add the data we want to set through the shader record. @@ -217,13 +187,13 @@ In `main`, after we set which hit group an instance will use, we can add the dat ### `HelloVulkan::createRtShaderBindingTable` -**NEW** +**:star:NEW:star:** -The creation of the shading binding table as it was done, was using hardcoded offsets and potentially could lead to errors. -Instead, the new code uses the `nvvk::SBTWraper` that uses the ray tracing pipeline and the `VkRayTracingPipelineCreateInfoKHR` to -create the SBT information. +The creation of the shading binding table as it was done, was using hardcoded offsets and potentially could lead to errors. +Instead, the new code uses the `nvvk::SBTWraper` that uses the ray tracing pipeline and the `VkRayTracingPipelineCreateInfoKHR` to +create the SBT information. -The wrapper will find the handles for each group and will add the +The wrapper will find the handles for each group and will add the data `m_hitShaderRecord` to the Hit group. ```` C @@ -238,19 +208,17 @@ The buffer for Hit will have the following layout ``` | handle | handle,data | handle,data | -``` +``` -The wrapper will make sure the stride covers the largest data and is aligned +The wrapper will make sure the stride covers the largest data and is aligned based on the GPU properties. - - **OLD - for reference** -Since we are no longer compacting all handles in a continuous buffer, we need to fill the SBT as described above. +~~Since we are no longer compacting all handles in a continuous buffer, we need to fill the SBT as described above.~~ -After retrieving the handles of all 5 groups (raygen, miss, miss shadow, hit0, and hit1) -using `getRayTracingShaderGroupHandlesKHR`, store the pointers to easily retrieve them. +~~After retrieving the handles of all 5 groups (raygen, miss, miss shadow, hit0, and hit1) +using `getRayTracingShaderGroupHandlesKHR`, store the pointers to easily retrieve them.~~ ~~~~ C++ // Retrieve the handle pointers @@ -261,7 +229,7 @@ using `getRayTracingShaderGroupHandlesKHR`, store the pointers to easily retriev } ~~~~ -The size of each group can be described as follows: +~~The size of each group can be described as follows:~~ ~~~~ C++ // Sizes @@ -272,7 +240,7 @@ The size of each group can be described as follows: uint32_t newSbtSize = rayGenSize + 2 * missSize + 2 * hitSize; ~~~~ -Then write the new SBT like this, where only Hit 1 has extra data. +~~Then write the new SBT like this, where only Hit 1 has extra data.~~ ~~~~ C++ std::vector sbtBuffer(newSbtSize); @@ -299,16 +267,15 @@ Then write the new SBT like this, where only Hit 1 has extra data. } ~~~~ -Then change the call to `m_alloc.createBuffer` to create the SBT buffer from `sbtBuffer`: +~~Then change the call to `m_alloc.createBuffer` to create the SBT buffer from `sbtBuffer`:~~ ~~~~ C++ m_rtSBTBuffer = m_alloc.createBuffer(cmdBuf, sbtBuffer, VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR); ~~~~ - ### `raytrace` -**NEW** +**:star:NEW:star:** The mvvk::SBTWrapper gives use the information without having to compute the `VkStridedDeviceAddressRegionKHR` @@ -319,7 +286,7 @@ The mvvk::SBTWrapper gives use the information without having to compute the `Vk **OLD** -Finally, since the size of the hit group is now larger than just the handle, we need to set the new value of the hit group stride in `HelloVulkan::raytrace`. +~~Finally, since the size of the hit group is now larger than just the handle, we need to set the new value of the hit group stride in `HelloVulkan::raytrace`.~~ ~~~~ C++ VkDeviceSize hitGroupSize = @@ -327,7 +294,7 @@ Finally, since the size of the hit group is now larger than just the handle, we m_rtProperties.shaderGroupBaseAlignment); ~~~~ -The stride device address will be modified like this: +~~The stride device address will be modified like this:~~ ~~~~ C++ using Stride = VkStridedDeviceAddressRegionKHR; @@ -338,8 +305,8 @@ The stride device address will be modified like this: Stride{0u, 0u, 0u}}; // callable ~~~~ -**Note:** - The result should now show both `wuson` models with a yellow color. +~~**Note:** + The result should now show both `wuson` models with a yellow color.~~ ![](images/manyhits4.png) @@ -349,25 +316,14 @@ The SBT can be larger than the number of shading models, which could then be use The following modification will add another entry to the SBT with a different color per instance. The new SBT hit group (2) will use the same CHIT handle (4) as hit group 1. -~~~~ Bash -+-----------+----------+ -| RayGen | Handle 0 | -+-----------+----------+ -| Miss | Handle 1 | -+-----------+----------+ -| Miss | Handle 2 | -+-----------+----------+ -| HitGroup0 | Handle 3 | -| | -Empty- | -+-----------+----------+ -| HitGroup1 | Handle 4 | -| | Data 0 | -+-----------+----------+ -| HitGroup2 | Handle 4 | -| | Data 1 | -+-----------+----------+ -~~~~ - +|Group|Handle| +| ------ | ------ | +| RayGen | Handle 0 | +| Miss0 | Handle 1 | +| Miss1 | Handle 2 | +| HitGroup0 | Handle 3
-Empty- | +| HitGroup1 | Handle 4
Data 0 | +| HitGroup1 | Handle 4
Data 1 | ### `main.cpp` @@ -378,19 +334,26 @@ In the description of the scene in `main`, we will tell the `wuson` models to us helloVk.m_hitShaderRecord.resize(2); helloVk.m_hitShaderRecord[0].color = nvmath::vec4f(0, 1, 0, 0); // Green helloVk.m_hitShaderRecord[1].color = nvmath::vec4f(0, 1, 1, 0); // Cyan - helloVk.m_objInstance[0].hitgroup = 1; // wuson 0 - helloVk.m_objInstance[1].hitgroup = 2; // wuson 1 + helloVk.m_instances[0].hitgroup = 1; // wuson 0 + helloVk.m_instances[1].hitgroup = 2; // wuson 1 ~~~~ ### `createRtShaderBindingTable` -The size of the SBT will now account for its 3 hit groups: +**:star:NEW:star:** + +If you are using the SBT wrapper, this part is automatically handled. + +**OLD** + + +~~The size of the SBT will now account for its 3 hit groups:~~ ~~~~ C++ uint32_t newSbtSize = rayGenSize + 2 * missSize + 3 * hitSize; ~~~~ -Finally, we need to add the new entry as well at the end of the buffer, reusing the handle of the second Hit Group and setting a different color. +~~Finally, we need to add the new entry as well at the end of the buffer, reusing the handle of the second Hit Group and setting a different color.~~ ~~~~ C++ pHitBuffer = pBuffer; @@ -400,7 +363,7 @@ Finally, we need to add the new entry as well at the end of the buffer, reusing pBuffer += hitSize; ~~~~ -**Note:** - Adding entries like this can be error-prone and inconvenient for decent +~~**Note:** + Adding entries like this can be error-prone and inconvenient for decent scene sizes. Instead, it is recommended to wrap the storage of handles, data, - and size per group in a SBT utility to handle this automatically. + and size per group in a SBT utility to handle this automatically.~~ diff --git a/ray_tracing_manyhits/hello_vulkan.cpp b/ray_tracing_manyhits/hello_vulkan.cpp index fedff2c..075917f 100644 --- a/ray_tracing_manyhits/hello_vulkan.cpp +++ b/ray_tracing_manyhits/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,13 +105,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -138,11 +129,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -150,7 +141,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -162,7 +153,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -222,30 +213,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -255,9 +251,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -266,15 +262,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -360,8 +356,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -416,14 +412,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -615,7 +611,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -664,19 +660,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); - rayInst.instanceShaderBindingTableRecordOffset = m_objInstance[i].hitgroup; // Using the hit group set in main - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 + rayInst.instanceShaderBindingTableRecordOffset = inst.hitgroup; // Using the hit group set in main tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -689,9 +688,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -711,8 +710,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -725,7 +724,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -814,7 +813,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -945,10 +944,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -957,7 +956,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); //// Size of a program identifier diff --git a/ray_tracing_manyhits/hello_vulkan.h b/ray_tracing_manyhits/hello_vulkan.h index cb0d173..f08da79 100644 --- a/ray_tracing_manyhits/hello_vulkan.h +++ b/ray_tracing_manyhits/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -46,7 +47,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -64,33 +65,28 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; - uint32_t hitgroup{0}; // Hit group of the instance + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference + int hitgroup{0}; // Hit group of the instance }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -100,8 +96,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -110,7 +106,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -153,13 +149,8 @@ public: VkPipeline m_rtPipeline; nvvk::Buffer m_rtSBTBuffer; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{}; struct HitRecordBuffer { diff --git a/ray_tracing_manyhits/main.cpp b/ray_tracing_manyhits/main.cpp index 115e620..e7d567f 100644 --- a/ray_tracing_manyhits/main.cpp +++ b/ray_tracing_manyhits/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -159,10 +159,8 @@ int main(int argc, char** argv) helloVk.loadModel(nvh::findFile("media/scenes/wuson.obj", defaultSearchPaths, true), nvmath::translation_mat4(nvmath::vec3f(-1, 0, 0))); - HelloVulkan::ObjInstance inst = helloVk.m_objInstance[0]; // Instance the Wuson object - inst.transform = nvmath::translation_mat4(nvmath::vec3f(1, 0, 0)); - inst.transformIT = nvmath::transpose(nvmath::invert(inst.transform)); - helloVk.m_objInstance.push_back(inst); // Adding an instance of the Wuson + helloVk.m_instances.push_back({nvmath::translation_mat4(nvmath::vec3f(1, 0, 0)), 0}); // Adding an instance of the Wuson + helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); @@ -170,14 +168,15 @@ int main(int argc, char** argv) helloVk.m_hitShaderRecord.resize(2); helloVk.m_hitShaderRecord[0].color = nvmath::vec4f(0, 1, 0, 0); // Green helloVk.m_hitShaderRecord[1].color = nvmath::vec4f(0, 1, 1, 0); // Cyan - helloVk.m_objInstance[0].hitgroup = 1; // Wuson 0 - helloVk.m_objInstance[1].hitgroup = 2; // Wuson 1 + helloVk.m_instances[0].hitgroup = 1; // Wuson 0 + helloVk.m_instances[1].hitgroup = 2; // Wuson 1 + helloVk.m_instances[2].hitgroup = 0; // Plane helloVk.createOffscreenRender(); helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_manyhits/shaders/frag_shader.frag b/ray_tracing_manyhits/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_manyhits/shaders/frag_shader.frag +++ b/ray_tracing_manyhits/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_manyhits/shaders/host_device.h b/ray_tracing_manyhits/shaders/host_device.h new file mode 100644 index 0000000..a8377f2 --- /dev/null +++ b/ray_tracing_manyhits/shaders/host_device.h @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_manyhits/shaders/raytrace.rchit b/ray_tracing_manyhits/shaders/raytrace.rchit index 2b523a7..2eb634e 100644 --- a/ray_tracing_manyhits/shaders/raytrace.rchit +++ b/ray_tracing_manyhits/shaders/raytrace.rchit @@ -39,25 +39,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +66,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +97,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +109,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -147,7 +137,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } diff --git a/ray_tracing_manyhits/shaders/raytrace.rgen b/ray_tracing_manyhits/shaders/raytrace.rgen index ebae40a..4802cd0 100644 --- a/ray_tracing_manyhits/shaders/raytrace.rgen +++ b/ray_tracing_manyhits/shaders/raytrace.rgen @@ -20,21 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + void main() { @@ -42,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; diff --git a/ray_tracing_manyhits/shaders/raytrace.rmiss b/ray_tracing_manyhits/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_manyhits/shaders/raytrace.rmiss +++ b/ray_tracing_manyhits/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_manyhits/shaders/raytrace2.rchit b/ray_tracing_manyhits/shaders/raytrace2.rchit index 2e61de5..e08f612 100644 --- a/ray_tracing_manyhits/shaders/raytrace2.rchit +++ b/ray_tracing_manyhits/shaders/raytrace2.rchit @@ -23,14 +23,12 @@ #include "raycommon.glsl" +// clang-format off layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(shaderRecordEXT) buffer sr_ -{ - vec4 c; -} -shaderRec; +layout(shaderRecordEXT) buffer sr_ { vec4 shaderRec; }; +// clang-format on void main() { - prd.hitValue = shaderRec.c.rgb; + prd.hitValue = shaderRec.rgb; } diff --git a/ray_tracing_manyhits/shaders/vert_shader.vert b/ray_tracing_manyhits/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_manyhits/shaders/vert_shader.vert +++ b/ray_tracing_manyhits/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_manyhits/shaders/wavefront.glsl b/ray_tracing_manyhits/shaders/wavefront.glsl index 2e446da..b326f8a 100644 --- a/ray_tracing_manyhits/shaders/wavefront.glsl +++ b/ray_tracing_manyhits/shaders/wavefront.glsl @@ -17,41 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; - int hitGroup; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_motionblur/CMakeLists.txt b/ray_tracing_motionblur/CMakeLists.txt new file mode 100644 index 0000000..1bf130c --- /dev/null +++ b/ray_tracing_motionblur/CMakeLists.txt @@ -0,0 +1,80 @@ +#***************************************************************************** +# 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 +compile_glsl_directory( + SRC "${CMAKE_CURRENT_SOURCE_DIR}/shaders" + DST "${CMAKE_CURRENT_SOURCE_DIR}/spv" + VULKAN_TARGET "vulkan1.2" + DEPENDENCY ${VULKAN_BUILD_DEPENDENCIES} + ) + + +#-------------------------------------------------------------------------------------------------- +# 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} ${GLSL_HEADERS}) + + +#-------------------------------------------------------------------------------------------------- +# 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 Sources" FILES ${GLSL_SOURCES}) +source_group("Shader Headers" FILES ${GLSL_HEADERS}) + + +#-------------------------------------------------------------------------------------------------- +# Linkage +# +target_link_libraries(${PROJNAME} ${PLATFORM_LIBRARIES} nvpro_core) + +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.) +# +_finalize_target( ${PROJNAME} ) + + +install(FILES ${SPV_OUTPUT} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}/spv") +install(FILES ${SPV_OUTPUT} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}/spv") diff --git a/ray_tracing_motionblur/README.md b/ray_tracing_motionblur/README.md new file mode 100644 index 0000000..03c182b --- /dev/null +++ b/ray_tracing_motionblur/README.md @@ -0,0 +1,260 @@ +# Motion Blur + +![](images/motionblur.png) + +This is an extension of the Vulkan ray tracing [tutorial](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR). + +If you haven't compiled it before, here is the [setup](../docs/setup.md). + + +## VK_NV_ray_tracing_motion_blur + +This sample shows the usage of the [motion blur extension](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_NV_ray_tracing_motion_blur.html). In changes from the original sample, we will do the following: + +* Use trace call with a time parameter. +* Using the various flags to enable motion support in an acceleration structure. +* Support for time-varying vertex positions in a geometry. +* Add motion over time to instances, including scaling, shearing, rotation, and translation (SRT) and matrix motion, while keeping some static. + +Defining an animation works by defining the state of the scene at a start time, T0, and an end time, T1. For instance, T0 could be the start of a frame, and T1 could be the end of a frame, then rays can be traced at any intermediate time, such as at t=0.5, halfway through the frame, and motion blur can be done by choosing a random t for each ray. + +## Enabling Motion Blur + +### Extensions + +In main.cpp, we add the device extension `VK_NV_ray_tracing_motion_blur` and enable all features. + +```` C + // #NV_Motion_blur + VkPhysicalDeviceRayTracingMotionBlurFeaturesNV rtMotionBlurFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MOTION_BLUR_FEATURES_NV}; + contextInfo.addDeviceExtension(VK_NV_RAY_TRACING_MOTION_BLUR_EXTENSION_NAME, false, &rtMotionBlurFeatures); // Required for motion blur +```` + +### Pipeline + +When creating the ray tracing pipeline, the VkRayTracingPipelineCreateInfoKHR struct's flags must include `VK_PIPELINE_CREATE_RAY_TRACING_ALLOW_MOTION_BIT_NV`. + +```` C + rayPipelineInfo.flags = VK_PIPELINE_CREATE_RAY_TRACING_ALLOW_MOTION_BIT_NV; +```` + +### Scene Objects + +We will use the following four models. The later sections will add matrix animation to two instances of the cube_multi.obj model, +and the plane.obj model will stay static. The third and fourth models are the keyframes for a vertex animation. Cube.obj is the +cube at time 0 (T0), and cube_modif.obj is the cube at time 1 (T1). + + +```` C + // Creation of the example + helloVk.loadModel(nvh::findFile("media/scenes/cube_multi.obj", defaultSearchPaths, true)); + helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); + helloVk.loadModel(nvh::findFile("media/scenes/cube.obj", defaultSearchPaths, true)); + helloVk.loadModel(nvh::findFile("media/scenes/cube_modif.obj", defaultSearchPaths, true)); +```` + + + +## Vertex Varying Motion + +As seen in the picture, the vertices of the left green cube change positions over time. +We specify this by giving two geometries to the BLAS builder. Setting the geometry at T0 +is done the same way as before. To add the destination keyframe at T1, we make the +`VkAccelerationStructureGeometryTrianglesDataKHR` structure's `pNext` field point to a +`VkAccelerationStructureGeometryMotionTrianglesDataNV` structure. Additionally, we must add +`VK_BUILD_ACCELERATION_STRUCTURE_MOTION_BIT_NV` to the BLAS build info flags. + + + +At first we are adding the cube_multi and plane. The cube_multi object's geometry doesn't animate, +but its transformation does, so we will set its animation in the TLAS in the Instance Motion section. + +````C +void HelloVulkan::createBottomLevelAS() +{ + // Static geometries + std::vector allBlas; + allBlas.emplace_back(objectToVkGeometryKHR(m_objModel[0])); + allBlas.emplace_back(objectToVkGeometryKHR(m_objModel[1])); +```` + +Then we add the cube and add the motion information; the reference to the geometry at T1 and the flag for which +we want this object to have motion. + +````C + // Animated geometry + allBlas.emplace_back(objectToVkGeometryKHR(m_objModel[2])); + // Adding the m_objModel[3] as the destination of m_objModel[2] + VkAccelerationStructureGeometryMotionTrianglesDataNV motionTriangles{ + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_MOTION_TRIANGLES_DATA_NV}; + motionTriangles.vertexData.deviceAddress = nvvk::getBufferDeviceAddress(m_device, m_objModel[3].vertexBuffer.buffer); + allBlas[2].asGeometry[0].geometry.triangles.pNext = &motionTriangles; + // Telling that this geometry has motion + allBlas[2].flags = VK_BUILD_ACCELERATION_STRUCTURE_MOTION_BIT_NV; +```` + +Building all the BLAS stays the same. + +````C + + m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); +} +```` + +## Instance Motion + +Instance motion describes motion in the TLAS, where objects move as a whole. There are 3 types: + +* Static +* Matrix motion +* SRT motion + +The array of instances uses [`VkAccelerationStructureMotionInstanceNV`](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkAccelerationStructureMotionInstanceNV.html) instead of `VkAccelerationStructureInstanceKHR`. + +````C +std::vector tlas; +```` + +### Matrix Motion + +The moving matrix needs to fill the [`VkAccelerationStructureMatrixMotionInstanceNV`](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkAccelerationStructureMatrixMotionInstanceNV.html) structure. + + +```` C + // Cube (moving/matrix translation) + objId = 0; + { + // Position of the instance at T0 and T1 + nvmath::mat4f matT0(1); // Identity + nvmath::mat4f matT1 = nvmath::translation_mat4(nvmath::vec3f(0.30f, 0.0f, 0.0f)); + + VkAccelerationStructureMatrixMotionInstanceNV data; + data.transformT0 = nvvk::toTransformMatrixKHR(matT0); + data.transformT1 = nvvk::toTransformMatrixKHR(matT1); + data.instanceCustomIndex = objId; // gl_InstanceCustomIndexEXT + data.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[objId].objIndex); + data.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + data.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + data.mask = 0xFF; + VkAccelerationStructureMotionInstanceNVPad rayInst; + rayInst.type = VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_MATRIX_MOTION_NV; + rayInst.data.matrixMotionInstance = data; + tlas.emplace_back(rayInst); + } +```` + +### SRT Motion + +The SRT motion uses the [`VkAccelerationStructureSRTMotionInstanceNV`](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkAccelerationStructureSRTMotionInstanceNV.html) +structure, where it interpolates between two [`VkSRTDataNV`](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSRTDataNV.html) structures. + +````C +// Cube (moving/SRT rotation) + objId = 0; + { + nvmath::quatf rot; + rot.from_euler_xyz({0, 0, 0}); + // Position of the instance at T0 and T1 + VkSRTDataNV matT0{}; // Translated to 0,0,2 + matT0.sx = 1.0f; + matT0.sy = 1.0f; + matT0.sz = 1.0f; + matT0.tz = 2.0f; + matT0.qx = rot.x; + matT0.qy = rot.y; + matT0.qz = rot.z; + matT0.qw = rot.w; + VkSRTDataNV matT1 = matT0; // Setting a rotation + rot.from_euler_xyz({deg2rad(10.0f), deg2rad(30.0f), 0.0f}); + matT1.qx = rot.x; + matT1.qy = rot.y; + matT1.qz = rot.z; + matT1.qw = rot.w; + + VkAccelerationStructureSRTMotionInstanceNV data{}; + data.transformT0 = matT0; + data.transformT1 = matT1; + data.instanceCustomIndex = objId; // gl_InstanceCustomIndexEXT + data.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[objId].objIndex); + data.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + data.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + data.mask = 0xFF; + VkAccelerationStructureMotionInstanceNVPad rayInst; + rayInst.type = VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_SRT_MOTION_NV; + rayInst.data.srtMotionInstance = data; + tlas.emplace_back(rayInst); + } +```` + +### Static + +Static instances use the same structure as we normally use with static scenes, `VkAccelerationStructureInstanceKHR`. + +```` C + // Plane (static) + objId = 1; + { + nvmath::mat4f matT0 = nvmath::translation_mat4(nvmath::vec3f(0, -1, 0)); + + VkAccelerationStructureInstanceKHR data{}; + data.transform = nvvk::toTransformMatrixKHR(matT0); // Position of the instance + data.instanceCustomIndex = objId; // gl_InstanceCustomIndexEXT + data.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[objId].objIndex); + data.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + data.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + data.mask = 0xFF; + VkAccelerationStructureMotionInstanceNVPad rayInst; + rayInst.type = VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_STATIC_NV; + rayInst.data.staticInstance = data; + tlas.emplace_back(rayInst); + } + ```` + + ### Building + + The building call is similar, only the flag is changing. + + ````C + m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_MOTION_BIT_NV, false, true); + ```` + +## Shader + +In the shader, we enable the `GL_NV_ray_tracing_motion_blur` extension. + +```` C +#extension GL_NV_ray_tracing_motion_blur : require +```` + +Then we call `traceRayMotionNV` instead of `traceRayEXT`. The `time` argument varies between 0 and 1. + +````C + traceRayMotionNV(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 + time, // time + 0 // payload (location = 0) + ); +```` + + +## Other + +We have used some technique from the [jitter cam](../ray_tracing_jitter_cam) to sampling time randomly. +Using random time value for each pixel at each frame gives a nicer look when accumulated over time then using a single time per frame. + +This is the how stuttered motion would look like. +![](images/rotary_disc_shutter.png) +https://en.wikipedia.org/wiki/Rotary_disc_shutter + + +:warning: Using motion blur pipeline with all instances static will be slower than using the static pipeline. Not by much but for performance, it's better to use the appropriate pipeline. + +:warning: Calling `traceRayEXT` from `raytrace.rchit` works, and we get motion-blurred shadows without having to call `traceRayMotionNV` in the closest-hit shader. This works only if `traceRayEXT` is called within the execution of a motion trace call. \ No newline at end of file diff --git a/ray_tracing_motionblur/hello_vulkan.cpp b/ray_tracing_motionblur/hello_vulkan.cpp new file mode 100644 index 0000000..2e77843 --- /dev/null +++ b/ray_tracing_motionblur/hello_vulkan.cpp @@ -0,0 +1,1048 @@ +/* + * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include + + +#define STB_IMAGE_IMPLEMENTATION +#include "obj_loader.h" +#include "stb_image.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/images_vk.hpp" +#include "nvvk/pipeline_vk.hpp" +#include "nvvk/renderpasses_vk.hpp" +#include "nvvk/shaders_vk.hpp" +#include "nvvk/buffers_vk.hpp" + +extern std::vector defaultSearchPaths; + +//-------------------------------------------------------------------------------------------------- +// Keep the handle on the device +// Initialize the tool to do all our allocations: buffers, images +// +void HelloVulkan::setup(const VkInstance& instance, const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t queueFamily) +{ + AppBaseVk::setup(instance, device, physicalDevice, queueFamily); + m_alloc.init(instance, device, physicalDevice); + m_debug.setup(m_device); + m_offscreenDepthFormat = nvvk::findDepthFormat(physicalDevice); +} + +//-------------------------------------------------------------------------------------------------- +// Called at each frame to update the camera matrix +// +void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) +{ + // Prepare new UBO contents on host. + const float aspectRatio = m_size.width / static_cast(m_size.height); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); + + // UBO on the device, and what stages access it. + VkBuffer deviceUBO = m_bGlobals.buffer; + auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; + + // Ensure that the modified UBO is not visible to previous frames. + VkBufferMemoryBarrier beforeBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; + beforeBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + beforeBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + beforeBarrier.buffer = deviceUBO; + beforeBarrier.offset = 0; + beforeBarrier.size = sizeof(hostUBO); + vkCmdPipelineBarrier(cmdBuf, uboUsageStages, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_DEPENDENCY_DEVICE_GROUP_BIT, 0, + nullptr, 1, &beforeBarrier, 0, nullptr); + + + // Schedule the host-to-device upload. (hostUBO is copied into the cmd + // buffer so it is okay to deallocate when the function returns). + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); + + // Making sure the updated UBO will be visible. + VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; + afterBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + afterBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + afterBarrier.buffer = deviceUBO; + afterBarrier.offset = 0; + afterBarrier.size = sizeof(hostUBO); + vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_TRANSFER_BIT, uboUsageStages, VK_DEPENDENCY_DEVICE_GROUP_BIT, 0, + nullptr, 1, &afterBarrier, 0, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Describing the layout pushed when rendering +// +void HelloVulkan::createDescriptorSetLayout() +{ + auto nbTxt = static_cast(m_textures.size()); + + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + + + 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 + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); + + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); + + // All texture samplers + std::vector diit; + for(auto& texture : m_textures) + { + diit.emplace_back(texture.descriptor); + } + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); + + // Writing the information + vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Creating the pipeline layout +// +void HelloVulkan::createGraphicsPipeline() +{ + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; + + // Creating the Pipeline Layout + VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + createInfo.setLayoutCount = 1; + createInfo.pSetLayouts = &m_descSetLayout; + createInfo.pushConstantRangeCount = 1; + createInfo.pPushConstantRanges = &pushConstantRanges; + vkCreatePipelineLayout(m_device, &createInfo, nullptr, &m_pipelineLayout); + + + // Creating the Pipeline + std::vector paths = defaultSearchPaths; + nvvk::GraphicsPipelineGeneratorCombined gpb(m_device, m_pipelineLayout, m_offscreenRenderPass); + gpb.depthStencilState.depthTestEnable = true; + gpb.addShader(nvh::loadFile("spv/vert_shader.vert.spv", true, paths, true), VK_SHADER_STAGE_VERTEX_BIT); + gpb.addShader(nvh::loadFile("spv/frag_shader.frag.spv", true, paths, true), VK_SHADER_STAGE_FRAGMENT_BIT); + gpb.addBindingDescription({0, sizeof(VertexObj)}); + gpb.addAttributeDescriptions({ + {0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexObj, pos))}, + {1, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexObj, nrm))}, + {2, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexObj, color))}, + {3, 0, VK_FORMAT_R32G32_SFLOAT, static_cast(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) +{ + 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); + } + + 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); + VkCommandBuffer cmdBuf = cmdBufGet.createCommandBuffer(); + VkBufferUsageFlags flag = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + VkBufferUsageFlags rayTracingFlags = // used also for building acceleration structures + flag | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + model.vertexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_vertices, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | rayTracingFlags); + model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); + model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); + model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); + createTextureImages(cmdBuf, loader.m_textures); + cmdBufGet.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + + std::string objNb = std::to_string(m_objModel.size()); + m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + + // Keeping transformation matrix of the instance + ObjInstance instance; + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description + m_objModel.emplace_back(model); + m_objDesc.emplace_back(desc); +} + + +//-------------------------------------------------------------------------------------------------- +// Creating the uniform buffer holding the camera matrices +// - Buffer is host visible +// +void HelloVulkan::createUniformBuffer() +{ + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); +} + +//-------------------------------------------------------------------------------------------------- +// Create a storage buffer containing the description of the scene elements +// - Which geometry is used by which instance +// - Transformation +// - Offset for texture +// +void HelloVulkan::createObjDescriptionBuffer() +{ + nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); + + auto cmdBuf = cmdGen.createCommandBuffer(); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + cmdGen.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); +} + +//-------------------------------------------------------------------------------------------------- +// Creating all textures and samplers +// +void HelloVulkan::createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures) +{ + VkSamplerCreateInfo samplerCreateInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + samplerCreateInfo.minFilter = VK_FILTER_LINEAR; + samplerCreateInfo.magFilter = VK_FILTER_LINEAR; + samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCreateInfo.maxLod = FLT_MAX; + + VkFormat format = VK_FORMAT_R8G8B8A8_SRGB; + + // 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}; + VkDeviceSize bufferSize = sizeof(color); + auto imgSize = VkExtent2D{1, 1}; + auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format); + + // Creating the dummy texture + nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, color.data(), imageCreateInfo); + VkImageViewCreateInfo 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_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + 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()); + } + + VkDeviceSize bufferSize = static_cast(texWidth) * texHeight * sizeof(uint8_t) * 4; + auto imgSize = VkExtent2D{(uint32_t)texWidth, (uint32_t)texHeight}; + auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format, VK_IMAGE_USAGE_SAMPLED_BIT, true); + + { + nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, pixels, imageCreateInfo); + nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels); + VkImageViewCreateInfo 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() +{ + vkDestroyPipeline(m_device, m_graphicsPipeline, nullptr); + vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + vkDestroyDescriptorPool(m_device, m_descPool, nullptr); + vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); + + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); + + 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_alloc.destroy(m_offscreenColor); + m_alloc.destroy(m_offscreenDepth); + vkDestroyPipeline(m_device, m_postPipeline, nullptr); + vkDestroyPipelineLayout(m_device, m_postPipelineLayout, nullptr); + vkDestroyDescriptorPool(m_device, m_postDescPool, nullptr); + vkDestroyDescriptorSetLayout(m_device, m_postDescSetLayout, nullptr); + vkDestroyRenderPass(m_device, m_offscreenRenderPass, nullptr); + vkDestroyFramebuffer(m_device, m_offscreenFramebuffer, nullptr); + + + // #VKRay + m_rtBuilder.destroy(); + vkDestroyPipeline(m_device, m_rtPipeline, nullptr); + vkDestroyPipelineLayout(m_device, m_rtPipelineLayout, nullptr); + vkDestroyDescriptorPool(m_device, m_rtDescPool, nullptr); + vkDestroyDescriptorSetLayout(m_device, m_rtDescSetLayout, nullptr); + m_alloc.destroy(m_rtSBTBuffer); + + m_alloc.deinit(); +} + +//-------------------------------------------------------------------------------------------------- +// Drawing the scene in raster mode +// +void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) +{ + VkDeviceSize offset{0}; + + m_debug.beginLabel(cmdBuf, "Rasterize"); + + // Dynamic Viewport + setViewport(cmdBuf); + + // Drawing all triangles + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_graphicsPipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); + + + for(const HelloVulkan::ObjInstance& inst : m_instances) + { + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; + + vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, + sizeof(PushConstantRaster), &m_pcRaster); + vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); + vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(cmdBuf, 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(); + resetFrame(); +} + + +////////////////////////////////////////////////////////////////////////// +// 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_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_USAGE_STORAGE_BIT); + + + nvvk::Image image = m_alloc.createImage(colorCreateInfo); + VkImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo); + VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + m_offscreenColor = m_alloc.createTexture(image, ivInfo, sampler); + m_offscreenColor.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + } + + // Creating the depth buffer + auto depthCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenDepthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); + { + nvvk::Image image = m_alloc.createImage(depthCreateInfo); + + + VkImageViewCreateInfo depthStencilView{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthStencilView.format = m_offscreenDepthFormat; + depthStencilView.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; + depthStencilView.image = 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_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); + nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenDepth.image, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_ASPECT_DEPTH_BIT); + + 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_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); + } + + + // Creating the frame buffer for offscreen + std::vector attachments = {m_offscreenColor.descriptor.imageView, m_offscreenDepth.descriptor.imageView}; + + vkDestroyFramebuffer(m_device, m_offscreenFramebuffer, nullptr); + VkFramebufferCreateInfo info{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + info.renderPass = m_offscreenRenderPass; + info.attachmentCount = 2; + info.pAttachments = attachments.data(); + info.width = m_size.width; + info.height = m_size.height; + info.layers = 1; + vkCreateFramebuffer(m_device, &info, nullptr, &m_offscreenFramebuffer); +} + +//-------------------------------------------------------------------------------------------------- +// 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 + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float)}; + + // Creating the pipeline layout + VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + createInfo.setLayoutCount = 1; + createInfo.pSetLayouts = &m_postDescSetLayout; + createInfo.pushConstantRangeCount = 1; + createInfo.pPushConstantRanges = &pushConstantRanges; + vkCreatePipelineLayout(m_device, &createInfo, nullptr, &m_postPipelineLayout); + + + // Pipeline: completely generic, no vertices + nvvk::GraphicsPipelineGeneratorCombined pipelineGenerator(m_device, m_postPipelineLayout, m_renderPass); + pipelineGenerator.addShader(nvh::loadFile("spv/passthrough.vert.spv", true, defaultSearchPaths, true), VK_SHADER_STAGE_VERTEX_BIT); + pipelineGenerator.addShader(nvh::loadFile("spv/post.frag.spv", true, defaultSearchPaths, true), VK_SHADER_STAGE_FRAGMENT_BIT); + pipelineGenerator.rasterizationState.cullMode = VK_CULL_MODE_NONE; + 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() +{ + m_postDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + 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() +{ + VkWriteDescriptorSet writeDescriptorSets = m_postDescSetLayoutBind.makeWrite(m_postDescSet, 0, &m_offscreenColor.descriptor); + vkUpdateDescriptorSets(m_device, 1, &writeDescriptorSets, 0, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Draw a full screen quad with the attached image +// +void HelloVulkan::drawPost(VkCommandBuffer cmdBuf) +{ + m_debug.beginLabel(cmdBuf, "Post"); + + setViewport(cmdBuf); + + auto aspectRatio = static_cast(m_size.width) / static_cast(m_size.height); + vkCmdPushConstants(cmdBuf, m_postPipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float), &aspectRatio); + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_postPipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_postPipelineLayout, 0, 1, &m_postDescSet, 0, nullptr); + vkCmdDraw(cmdBuf, 3, 1, 0, 0); + + m_debug.endLabel(cmdBuf); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------------------------------------------- +// Initialize Vulkan ray tracing +// #VKRay +void HelloVulkan::initRayTracing() +{ + // Requesting ray tracing properties + VkPhysicalDeviceProperties2 prop2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; + prop2.pNext = &m_rtProperties; + vkGetPhysicalDeviceProperties2(m_physicalDevice, &prop2); + + m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex); +} + +//-------------------------------------------------------------------------------------------------- +// Convert an OBJ model into the ray tracing geometry used to build the BLAS +// +auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) +{ + // BLAS builder requires raw device addresses. + VkDeviceAddress vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + VkDeviceAddress indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + + uint32_t maxPrimitiveCount = model.nbIndices / 3; + + // Describe buffer as array of VertexObj. + VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. + triangles.vertexData.deviceAddress = vertexAddress; + triangles.vertexStride = sizeof(VertexObj); + // Describe index data (32-bit unsigned int) + triangles.indexType = VK_INDEX_TYPE_UINT32; + triangles.indexData.deviceAddress = indexAddress; + // Indicate identity transform by setting transformData to null device pointer. + //triangles.transformData = {}; + triangles.maxVertex = model.nbVertices; + + // Identify the above data as containing opaque triangles. + VkAccelerationStructureGeometryKHR asGeom{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR}; + asGeom.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + asGeom.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; + asGeom.geometry.triangles = triangles; + + // The entire array will be used to build the BLAS. + VkAccelerationStructureBuildRangeInfoKHR offset; + offset.firstVertex = 0; + offset.primitiveCount = maxPrimitiveCount; + offset.primitiveOffset = 0; + offset.transformOffset = 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; +} + +//-------------------------------------------------------------------------------------------------- +// +// +void HelloVulkan::createBottomLevelAS() +{ + // Static geometries + std::vector allBlas; + allBlas.emplace_back(objectToVkGeometryKHR(m_objModel[0])); // cube multi + allBlas.emplace_back(objectToVkGeometryKHR(m_objModel[1])); // plane + + // Animated geometry + allBlas.emplace_back(objectToVkGeometryKHR(m_objModel[2])); // cube + // Adding the m_objModel[3] as the destination of m_objModel[2] + VkAccelerationStructureGeometryMotionTrianglesDataNV motionTriangles{ + VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_MOTION_TRIANGLES_DATA_NV}; + motionTriangles.vertexData.deviceAddress = nvvk::getBufferDeviceAddress(m_device, m_objModel[3].vertexBuffer.buffer); // cube_modif + allBlas[2].asGeometry[0].geometry.triangles.pNext = &motionTriangles; + // Telling that this geometry has motion + allBlas[2].flags = VK_BUILD_ACCELERATION_STRUCTURE_MOTION_BIT_NV; + + m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); +} + + +void HelloVulkan::createTopLevelAS() +{ + // This is to fix a padding issue 2021.08.13 + struct VkAccelerationStructureMotionInstanceNVPad : VkAccelerationStructureMotionInstanceNV + { + short _pad; + }; + + // #NV_Motion_blur + uint32_t objId; + std::vector tlas; + + // Cube (moving/matrix translation) + objId = 0; + { + // Position of the instance at T0 and T1 + nvmath::mat4f matT0 = m_instances[0].transform; // Identity + nvmath::mat4f matT1 = nvmath::translation_mat4(nvmath::vec3f(0.30f, 0.0f, 0.0f)) * matT0; + + VkAccelerationStructureMatrixMotionInstanceNV data; + data.transformT0 = nvvk::toTransformMatrixKHR(matT0); + data.transformT1 = nvvk::toTransformMatrixKHR(matT1); + data.instanceCustomIndex = objId; // gl_InstanceCustomIndexEXT + data.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_instances[objId].objIndex); + data.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + data.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + data.mask = 0xFF; + VkAccelerationStructureMotionInstanceNVPad rayInst; + rayInst.type = VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_MATRIX_MOTION_NV; + rayInst.data.matrixMotionInstance = data; + tlas.emplace_back(rayInst); + } + + // Cube (moving/SRT rotation) + objId = 0; + { + // m_instance[3].transform -> no matrix to SRT + nvmath::quatf rot; + rot.from_euler_xyz({0, 0, 0}); + // Position of the instance at T0 and T1 + VkSRTDataNV matT0{}; // Translated to 0,0,2 + matT0.sx = 1.0f; + matT0.sy = 1.0f; + matT0.sz = 1.0f; + matT0.tz = 2.0f; + matT0.qx = rot.x; + matT0.qy = rot.y; + matT0.qz = rot.z; + matT0.qw = rot.w; + VkSRTDataNV matT1 = matT0; // Setting a rotation + rot.from_euler_xyz({deg2rad(10.0f), deg2rad(30.0f), 0.0f}); + matT1.qx = rot.x; + matT1.qy = rot.y; + matT1.qz = rot.z; + matT1.qw = rot.w; + + VkAccelerationStructureSRTMotionInstanceNV data{}; + data.transformT0 = matT0; + data.transformT1 = matT1; + data.instanceCustomIndex = objId; // gl_InstanceCustomIndexEXT + data.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_instances[objId].objIndex); + data.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + data.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + data.mask = 0xFF; + VkAccelerationStructureMotionInstanceNVPad rayInst; + rayInst.type = VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_SRT_MOTION_NV; + rayInst.data.srtMotionInstance = data; + tlas.emplace_back(rayInst); + } + + // Plane (static) + objId = 1; + { + nvmath::mat4f matT0 = m_instances[1].transform; + + VkAccelerationStructureInstanceKHR data{}; + data.transform = nvvk::toTransformMatrixKHR(matT0); // Position of the instance + data.instanceCustomIndex = objId; // gl_InstanceCustomIndexEXT + data.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_instances[objId].objIndex); + data.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + data.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + data.mask = 0xFF; + VkAccelerationStructureMotionInstanceNVPad rayInst; + rayInst.type = VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_STATIC_NV; + rayInst.data.staticInstance = data; + tlas.emplace_back(rayInst); + } + + + // Cube+Cubemodif (static) + objId = 2; + { + nvmath::mat4f matT0 = m_instances[2].transform; + + VkAccelerationStructureInstanceKHR data{}; + data.transform = nvvk::toTransformMatrixKHR(matT0); // Position of the instance + data.instanceCustomIndex = objId; // gl_InstanceCustomIndexEXT + data.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_instances[objId].objIndex); + data.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects + data.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + data.mask = 0xFF; + VkAccelerationStructureMotionInstanceNVPad rayInst; + rayInst.type = VK_ACCELERATION_STRUCTURE_MOTION_INSTANCE_TYPE_STATIC_NV; + rayInst.data.staticInstance = data; + tlas.emplace_back(rayInst); + } + + + m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_MOTION_BIT_NV, false, true); +} + +//-------------------------------------------------------------------------------------------------- +// This descriptor set holds the Acceleration structure and the output image +// +void HelloVulkan::createRtDescriptorSet() +{ + // Top-level acceleration structure, usable by both the ray generation and the closest hit (to shoot shadow rays) + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image + + m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); + m_rtDescSetLayout = m_rtDescSetLayoutBind.createLayout(m_device); + + VkDescriptorSetAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocateInfo.descriptorPool = m_rtDescPool; + allocateInfo.descriptorSetCount = 1; + allocateInfo.pSetLayouts = &m_rtDescSetLayout; + vkAllocateDescriptorSets(m_device, &allocateInfo, &m_rtDescSet); + + + VkAccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure(); + VkWriteDescriptorSetAccelerationStructureKHR descASInfo{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR}; + descASInfo.accelerationStructureCount = 1; + descASInfo.pAccelerationStructures = &tlas; + VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; + + std::vector writes; + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); + vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Writes the output image to the descriptor set +// - Required when changing resolution +// +void HelloVulkan::updateRtDescriptorSet() +{ + // (1) Output buffer + VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); + vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Pipeline for the ray tracer: all shaders, raygen, chit, miss +// +void HelloVulkan::createRtPipeline() +{ + enum StageIndices + { + eRaygen, + eMiss, + eMiss2, + eClosestHit, + eShaderGroupCount + }; + + // All stages + std::array stages{}; + VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + stage.pName = "main"; // All the same entry point + // Raygen + stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rgen.spv", true, defaultSearchPaths, true)); + stage.stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR; + stages[eRaygen] = stage; + // Miss + stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rmiss.spv", true, defaultSearchPaths, true)); + stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; + stages[eMiss] = stage; + // The second miss shader is invoked when a shadow ray misses the geometry. It simply indicates that no occlusion has been found + stage.module = + nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytraceShadow.rmiss.spv", true, defaultSearchPaths, true)); + stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; + stages[eMiss2] = stage; + // Hit Group - Closest Hit + stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rchit.spv", true, defaultSearchPaths, true)); + stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + stages[eClosestHit] = stage; + + + // Shader groups + VkRayTracingShaderGroupCreateInfoKHR group{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + group.anyHitShader = VK_SHADER_UNUSED_KHR; + group.closestHitShader = VK_SHADER_UNUSED_KHR; + group.generalShader = VK_SHADER_UNUSED_KHR; + group.intersectionShader = VK_SHADER_UNUSED_KHR; + + // Raygen + group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + group.generalShader = eRaygen; + m_rtShaderGroups.push_back(group); + + // Miss + group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + group.generalShader = eMiss; + m_rtShaderGroups.push_back(group); + + // Shadow Miss + group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + group.generalShader = eMiss2; + m_rtShaderGroups.push_back(group); + + // closest hit shader + group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; + group.generalShader = VK_SHADER_UNUSED_KHR; + group.closestHitShader = eClosestHit; + m_rtShaderGroups.push_back(group); + + // Push constant: we want to be able to update constants used by the shaders + VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, + 0, sizeof(PushConstantRay)}; + + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pipelineLayoutCreateInfo.pushConstantRangeCount = 1; + pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstant; + + // Descriptor sets: one specific to ray tracing, and one shared with the rasterization pipeline + std::vector rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout}; + pipelineLayoutCreateInfo.setLayoutCount = static_cast(rtDescSetLayouts.size()); + pipelineLayoutCreateInfo.pSetLayouts = rtDescSetLayouts.data(); + + vkCreatePipelineLayout(m_device, &pipelineLayoutCreateInfo, nullptr, &m_rtPipelineLayout); + + + // Assemble the shader stages and recursion depth info into the ray tracing pipeline + VkRayTracingPipelineCreateInfoKHR rayPipelineInfo{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR}; + rayPipelineInfo.stageCount = static_cast(stages.size()); // Stages are shaders + rayPipelineInfo.pStages = stages.data(); + // #NV_Motion_blur + rayPipelineInfo.flags = VK_PIPELINE_CREATE_RAY_TRACING_ALLOW_MOTION_BIT_NV; + + // In this case, m_rtShaderGroups.size() == 4: we have one raygen group, + // two miss shader groups, and one hit group. + rayPipelineInfo.groupCount = static_cast(m_rtShaderGroups.size()); + rayPipelineInfo.pGroups = m_rtShaderGroups.data(); + + // The ray tracing process can shoot rays from the camera, and a shadow ray can be shot from the + // hit points of the camera rays, hence a recursion level of 2. This number should be kept as low + // as possible for performance reasons. Even recursive ray tracing should be flattened into a loop + // in the ray generation to avoid deep recursion. + rayPipelineInfo.maxPipelineRayRecursionDepth = 2; // Ray depth + rayPipelineInfo.layout = m_rtPipelineLayout; + + vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline); + + + // 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)"); + } + + for(auto& s : stages) + vkDestroyShaderModule(m_device, s.module, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// 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()); // 4 shaders: raygen, 2 miss, 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. + // The order of handles follow the stage entry. + std::vector shaderHandleStorage(sbtSize); + auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); + + assert(result == VK_SUCCESS); + + // Allocate a buffer for storing the SBT. Give it a debug name for NSight. + m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); + + // 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(); +} + +//-------------------------------------------------------------------------------------------------- +// Ray Tracing the scene +// +void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) +{ + updateFrame(); + if(m_pcRay.frame >= m_maxFrames) + return; + + m_debug.beginLabel(cmdBuf, "Ray trace"); + // Initializing push constant values + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; + + std::vector descSets{m_rtDescSet, m_descSet}; + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipelineLayout, 0, + (uint32_t)descSets.size(), descSets.data(), 0, nullptr); + vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, + VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, + 0, sizeof(PushConstantRay), &m_pcRay); + + + // Size of a program identifier + uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); + uint32_t groupStride = groupSize; + + VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); + + using Stride = VkStridedDeviceAddressRegionKHR; + std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen + Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 2}, // miss + Stride{sbtAddress + 3u * groupSize, groupStride, groupSize * 1}, // hit + Stride{0u, 0u, 0u}}; // callable + + + vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], + m_size.width, m_size.height, 1); + + + m_debug.endLabel(cmdBuf); +} + +//-------------------------------------------------------------------------------------------------- +// If the camera matrix or the the fov has changed, resets the frame. +// otherwise, increments frame. +// +void HelloVulkan::updateFrame() +{ + static nvmath::mat4f refCamMatrix; + static float refFov{CameraManip.getFov()}; + + const auto& m = CameraManip.getMatrix(); + const auto fov = CameraManip.getFov(); + + if(memcmp(&refCamMatrix.a00, &m.a00, sizeof(nvmath::mat4f)) != 0 || refFov != fov) + { + resetFrame(); + refCamMatrix = m; + refFov = fov; + } + m_pcRay.frame++; +} + +void HelloVulkan::resetFrame() +{ + m_pcRay.frame = -1; +} diff --git a/ray_tracing_motionblur/hello_vulkan.h b/ray_tracing_motionblur/hello_vulkan.h new file mode 100644 index 0000000..e22cd44 --- /dev/null +++ b/ray_tracing_motionblur/hello_vulkan.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "nvvk/appbase_vk.hpp" +#include "nvvk/debug_util_vk.hpp" +#include "nvvk/descriptorsets_vk.hpp" +#include "nvvk/memallocator_dma_vk.hpp" +#include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" + +// #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::AppBaseVk +{ +public: + void setup(const VkInstance& instance, const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t queueFamily) override; + void createDescriptorSetLayout(); + void createGraphicsPipeline(); + void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); + void updateDescriptorSet(); + void createUniformBuffer(); + void createObjDescriptionBuffer(); + void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); + void updateUniformBuffer(const VkCommandBuffer& cmdBuf); + void onResize(int /*w*/, int /*h*/) override; + void destroyResources(); + void rasterize(const VkCommandBuffer& 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 + { + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference + }; + + + // Information pushed at each draw call + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {9.5f, 5.5f, -6.5f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type + }; + + // Array of objects and instances in the scene + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + + + // Graphic pipeline + VkPipelineLayout m_pipelineLayout; + VkPipeline m_graphicsPipeline; + nvvk::DescriptorSetBindings m_descSetLayoutBind; + VkDescriptorPool m_descPool; + VkDescriptorSetLayout m_descSetLayout; + VkDescriptorSet m_descSet; + + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions + + std::vector m_textures; // vector of all textures of the scene + + + nvvk::ResourceAllocatorDma m_alloc; // Allocator for buffer, images, acceleration structures + nvvk::DebugUtil m_debug; // Utility to name objects + + + // #Post - Draw the rendered image on a quad using a tonemapper + void createOffscreenRender(); + void createPostPipeline(); + void createPostDescriptor(); + void updatePostDescriptorSet(); + void drawPost(VkCommandBuffer cmdBuf); + + nvvk::DescriptorSetBindings m_postDescSetLayoutBind; + VkDescriptorPool m_postDescPool{VK_NULL_HANDLE}; + VkDescriptorSetLayout m_postDescSetLayout{VK_NULL_HANDLE}; + VkDescriptorSet m_postDescSet{VK_NULL_HANDLE}; + VkPipeline m_postPipeline{VK_NULL_HANDLE}; + VkPipelineLayout m_postPipelineLayout{VK_NULL_HANDLE}; + VkRenderPass m_offscreenRenderPass{VK_NULL_HANDLE}; + VkFramebuffer m_offscreenFramebuffer{VK_NULL_HANDLE}; + nvvk::Texture m_offscreenColor; + nvvk::Texture m_offscreenDepth; + VkFormat m_offscreenColorFormat{VK_FORMAT_R32G32B32A32_SFLOAT}; + VkFormat m_offscreenDepthFormat{VK_FORMAT_X8_D24_UNORM_PACK32}; + + // #VKRay + void initRayTracing(); + auto objectToVkGeometryKHR(const ObjModel& model); + void createBottomLevelAS(); + void createTopLevelAS(); + void createRtDescriptorSet(); + void updateRtDescriptorSet(); + void createRtPipeline(); + void createRtShaderBindingTable(); + void raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor); + void updateFrame(); + void resetFrame(); + + + VkPhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR}; + nvvk::RaytracingBuilderKHR m_rtBuilder; + nvvk::DescriptorSetBindings m_rtDescSetLayoutBind; + VkDescriptorPool m_rtDescPool; + VkDescriptorSetLayout m_rtDescSetLayout; + VkDescriptorSet m_rtDescSet; + std::vector m_rtShaderGroups; + VkPipelineLayout m_rtPipelineLayout; + VkPipeline m_rtPipeline; + nvvk::Buffer m_rtSBTBuffer; + + // Push constant for ray tracer + PushConstantRay m_pcRay{}; + + int m_maxFrames{1000}; +}; diff --git a/ray_tracing_motionblur/images/motionblur.png b/ray_tracing_motionblur/images/motionblur.png new file mode 100644 index 0000000000000000000000000000000000000000..abf50e59f1a1f5d3fa1835d5a493105cbbb05e2c GIT binary patch literal 95520 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 RKLY~;gQu&X%Q~loCIAYM#_|9F literal 0 HcmV?d00001 diff --git a/ray_tracing_motionblur/images/rotary_disc_shutter.png b/ray_tracing_motionblur/images/rotary_disc_shutter.png new file mode 100644 index 0000000000000000000000000000000000000000..8d8e1eec836945c248de69a396e541ba65acd2c1 GIT binary patch literal 114990 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%0A>lzc|t&pjEbZ?r-Cu1L`qP;<3ow;J0 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 af9|~;-|uPrZZa@1FnGH9xvX + +#include "backends/imgui_impl_glfw.h" +#include "imgui.h" + +#include "hello_vulkan.h" +#include "imgui/imgui_camera_widget.h" +#include "nvh/cameramanipulator.hpp" +#include "nvh/fileoperations.hpp" +#include "nvpsystem.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) +{ + bool changed{false}; + ImGuiH::CameraWidget(); + if(ImGui::CollapsingHeader("Light")) + { + auto& pc = helloVk.m_pcRaster; + changed |= ImGui::RadioButton("Point", &pc.lightType, 0); + ImGui::SameLine(); + changed |= ImGui::RadioButton("Infinite", &pc.lightType, 1); + + changed |= ImGui::SliderFloat3("Position", &pc.lightPosition.x, -20.f, 20.f); + changed |= ImGui::SliderFloat("Intensity", &pc.lightIntensity, 0.f, 150.f); + } + + + changed |= ImGui::SliderInt("Max Frames", &helloVk.m_maxFrames, 1, 100); + if(changed) + helloVk.resetFrame(); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +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({3.445, 2.151, -2.098}, {0.435, -0.431, 0.705}, {0.000, 1.000, 0.000}); + + // 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(PROJECT_NAME); + + // Search path for shaders and other media + defaultSearchPaths = { + NVPSystem::exePath() + PROJECT_RELDIRECTORY, + NVPSystem::exePath() + PROJECT_RELDIRECTORY "..", + std::string(PROJECT_NAME), + }; + + // Vulkan required extensions + assert(glfwVulkanSupported() == 1); + uint32_t count{0}; + auto reqExtensions = glfwGetRequiredInstanceExtensions(&count); + + // Requesting Vulkan extensions and layers + nvvk::ContextCreateInfo contextInfo; + contextInfo.setVersion(1, 2); // Using Vulkan 1.2 + for(uint32_t ext_id = 0; ext_id < count; ext_id++) // Adding required extensions (surface, win32, linux, ..) + contextInfo.addInstanceExtension(reqExtensions[ext_id]); + contextInfo.addInstanceLayer("VK_LAYER_LUNARG_monitor", true); // FPS in titlebar + contextInfo.addInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, true); // Allow debug names + contextInfo.addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); // Enabling ability to present rendering + + // #VKRay: Activate the ray tracing extension + VkPhysicalDeviceAccelerationStructureFeaturesKHR accelFeature{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR}; + contextInfo.addDeviceExtension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, false, &accelFeature); // To build acceleration structures + VkPhysicalDeviceRayTracingPipelineFeaturesKHR rtPipelineFeature{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR}; + contextInfo.addDeviceExtension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, false, &rtPipelineFeature); // To use vkCmdTraceRaysKHR + contextInfo.addDeviceExtension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); // Required by ray tracing pipeline + + // #NV_Motion_blur + VkPhysicalDeviceRayTracingMotionBlurFeaturesNV rtMotionBlurFeatures{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_MOTION_BLUR_FEATURES_NV}; + contextInfo.addDeviceExtension(VK_NV_RAY_TRACING_MOTION_BLUR_EXTENSION_NAME, false, &rtMotionBlurFeatures); // Required for motion blur + + + // Creating Vulkan base application + nvvk::Context vkctx{}; + vkctx.ignoreDebugMessage(0x79de34d4); // Missing Device Extension "VK_NV_ray_tracing_motion_blur" + 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 VkSurfaceKHR 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/cube_multi.obj", defaultSearchPaths, true)); + helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); + helloVk.loadModel(nvh::findFile("media/scenes/cube.obj", defaultSearchPaths, true)); + helloVk.loadModel(nvh::findFile("media/scenes/cube_modif.obj", defaultSearchPaths, true)); + + // Set the positions of the instances and reuse the last instance (cube_modif) to use cube_multi instead + helloVk.m_instances[1].transform = nvmath::translation_mat4(nvmath::vec3f(0, -1, 0)); + helloVk.m_instances[2].transform = nvmath::translation_mat4(nvmath::vec3f(2, 0, 2)); + helloVk.m_instances[3].objIndex = 0; + helloVk.m_instances[3].transform = nvmath::translation_mat4(nvmath::vec3f(0, 0, 2)); // SRT - unused + + + helloVk.createOffscreenRender(); + helloVk.createDescriptorSetLayout(); + helloVk.createGraphicsPipeline(); + helloVk.createUniformBuffer(); + helloVk.createObjDescriptionBuffer(); + helloVk.updateDescriptorSet(); + + // #VKRay + helloVk.initRayTracing(); + helloVk.createBottomLevelAS(); + helloVk.createTopLevelAS(); + helloVk.createRtDescriptorSet(); + helloVk.createRtPipeline(); + 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(); + bool changed = false; + // Edit 3 floats representing a color + changed |= ImGui::ColorEdit3("Clear color", reinterpret_cast(&clearColor)); + // Switch between raster and ray tracing + changed |= ImGui::Checkbox("Ray Tracer mode", &useRaytracer); + if(changed) + helloVk.resetFrame(); + + 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 VkCommandBuffer& cmdBuf = helloVk.getCommandBuffers()[curFrame]; + + VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(cmdBuf, &beginInfo); + + // Updating camera buffer + helloVk.updateUniformBuffer(cmdBuf); + + // Clearing screen + std::array clearValues{}; + clearValues[0].color = {{clearColor[0], clearColor[1], clearColor[2], clearColor[3]}}; + clearValues[1].depthStencil = {1.0f, 0}; + + // Offscreen render pass + { + VkRenderPassBeginInfo offscreenRenderPassBeginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + offscreenRenderPassBeginInfo.clearValueCount = 2; + offscreenRenderPassBeginInfo.pClearValues = clearValues.data(); + offscreenRenderPassBeginInfo.renderPass = helloVk.m_offscreenRenderPass; + offscreenRenderPassBeginInfo.framebuffer = helloVk.m_offscreenFramebuffer; + offscreenRenderPassBeginInfo.renderArea = {{0, 0}, helloVk.getSize()}; + + // Rendering Scene + if(useRaytracer) + { + helloVk.raytrace(cmdBuf, clearColor); + } + else + { + vkCmdBeginRenderPass(cmdBuf, &offscreenRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + helloVk.rasterize(cmdBuf); + vkCmdEndRenderPass(cmdBuf); + } + } + + // 2nd rendering pass: tone mapper, UI + { + VkRenderPassBeginInfo postRenderPassBeginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + postRenderPassBeginInfo.clearValueCount = 2; + postRenderPassBeginInfo.pClearValues = clearValues.data(); + postRenderPassBeginInfo.renderPass = helloVk.getRenderPass(); + postRenderPassBeginInfo.framebuffer = helloVk.getFramebuffers()[curFrame]; + postRenderPassBeginInfo.renderArea = {{0, 0}, helloVk.getSize()}; + + // Rendering tonemapper + vkCmdBeginRenderPass(cmdBuf, &postRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + helloVk.drawPost(cmdBuf); + // Rendering UI + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmdBuf); + vkCmdEndRenderPass(cmdBuf); + } + + // Submit for display + vkEndCommandBuffer(cmdBuf); + helloVk.submitFrame(); + } + + // Cleanup + vkDeviceWaitIdle(helloVk.getDevice()); + + helloVk.destroyResources(); + helloVk.destroy(); + vkctx.deinit(); + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} diff --git a/ray_tracing_motionblur/shaders/frag_shader.frag b/ray_tracing_motionblur/shaders/frag_shader.frag new file mode 100644 index 0000000..0930980 --- /dev/null +++ b/ray_tracing_motionblur/shaders/frag_shader.frag @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#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 + +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require +#extension GL_EXT_buffer_reference2 : require + +#include "wavefront.glsl" + + +layout(push_constant) uniform _PushConstantRaster +{ + PushConstantRaster pcRaster; +}; + +// clang-format off +// Incoming +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; +// Outgoing +layout(location = 0) out vec4 o_color; + +layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object +layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices +layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object +layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle + +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; +// clang-format on + + +void main() +{ + // Material of the object + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; + MatIndices matIndices = MatIndices(objResource.materialIndexAddress); + Materials materials = Materials(objResource.materialAddress); + + int matIndex = matIndices.i[gl_PrimitiveID]; + WaveFrontMaterial mat = materials.m[matIndex]; + + vec3 N = normalize(i_worldNrm); + + // Vector toward light + vec3 L; + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) + { + vec3 lDir = pcRaster.lightPosition - i_worldPos; + float d = length(lDir); + lightIntensity = pcRaster.lightIntensity / (d * d); + L = normalize(lDir); + } + else + { + L = normalize(pcRaster.lightPosition); + } + + + // Diffuse + vec3 diffuse = computeDiffuse(mat, L, N); + if(mat.textureId >= 0) + { + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; + uint txtId = txtOffset + mat.textureId; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; + diffuse *= diffuseTxt; + } + + // Specular + vec3 specular = computeSpecular(mat, i_viewDir, L, N); + + // Result + o_color = vec4(lightIntensity * (diffuse + specular), 1); +} diff --git a/ray_tracing_motionblur/shaders/host_device.h b/ray_tracing_motionblur/shaders/host_device.h new file mode 100644 index 0000000..926e29d --- /dev/null +++ b/ray_tracing_motionblur/shaders/host_device.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int frame; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_motionblur/shaders/passthrough.vert b/ray_tracing_motionblur/shaders/passthrough.vert new file mode 100644 index 0000000..65c3460 --- /dev/null +++ b/ray_tracing_motionblur/shaders/passthrough.vert @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#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_motionblur/shaders/post.frag b/ray_tracing_motionblur/shaders/post.frag new file mode 100644 index 0000000..85faa58 --- /dev/null +++ b/ray_tracing_motionblur/shaders/post.frag @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#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_motionblur/shaders/random.glsl b/ray_tracing_motionblur/shaders/random.glsl new file mode 100644 index 0000000..ef41f54 --- /dev/null +++ b/ray_tracing_motionblur/shaders/random.glsl @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +// Generate a random unsigned int from two unsigned int values, using 16 pairs +// of rounds of the Tiny Encryption Algorithm. See Zafar, Olano, and Curtis, +// "GPU Random Numbers via the Tiny Encryption Algorithm" +uint tea(uint val0, uint val1) +{ + uint v0 = val0; + uint v1 = val1; + uint s0 = 0; + + for(uint n = 0; n < 16; n++) + { + s0 += 0x9e3779b9; + v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4); + v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e); + } + + return v0; +} + +// Generate a random unsigned int in [0, 2^24) given the previous RNG state +// using the Numerical Recipes linear congruential generator +uint lcg(inout uint prev) +{ + uint LCG_A = 1664525u; + uint LCG_C = 1013904223u; + prev = (LCG_A * prev + LCG_C); + return prev & 0x00FFFFFF; +} + +// Generate a random float in [0, 1) given the previous RNG state +float rnd(inout uint prev) +{ + return (float(lcg(prev)) / float(0x01000000)); +} diff --git a/ray_tracing_motionblur/shaders/raycommon.glsl b/ray_tracing_motionblur/shaders/raycommon.glsl new file mode 100644 index 0000000..b896c84 --- /dev/null +++ b/ray_tracing_motionblur/shaders/raycommon.glsl @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +struct hitPayload +{ + vec3 hitValue; +}; diff --git a/ray_tracing_motionblur/shaders/raytrace.rchit b/ray_tracing_motionblur/shaders/raytrace.rchit new file mode 100644 index 0000000..2eb634e --- /dev/null +++ b/ray_tracing_motionblur/shaders/raytrace.rchit @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#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 + +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require +#extension GL_EXT_buffer_reference2 : require + +#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(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object +layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices +layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object +layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; + +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + + +void main() +{ + // Object data + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; + MatIndices matIndices = MatIndices(objResource.materialIndexAddress); + Materials materials = Materials(objResource.materialAddress); + Indices indices = Indices(objResource.indexAddress); + Vertices vertices = Vertices(objResource.vertexAddress); + + // Indices of the triangle + ivec3 ind = indices.i[gl_PrimitiveID]; + + // Vertex of the triangle + Vertex v0 = vertices.v[ind.x]; + Vertex v1 = vertices.v[ind.y]; + Vertex v2 = vertices.v[ind.z]; + + const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); + + // Computing the coordinates of the hit position + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space + + // Vector toward the light + vec3 L; + float lightIntensity = pcRay.lightIntensity; + float lightDistance = 100000.0; + // Point light + if(pcRay.lightType == 0) + { + vec3 lDir = pcRay.lightPosition - worldPos; + lightDistance = length(lDir); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); + L = normalize(lDir); + } + else // Directional light + { + L = normalize(pcRay.lightPosition); + } + + // Material of the object + int matIdx = matIndices.i[gl_PrimitiveID]; + WaveFrontMaterial mat = materials.m[matIdx]; + + + // Diffuse + vec3 diffuse = computeDiffuse(mat, L, worldNrm); + if(mat.textureId >= 0) + { + uint txtId = mat.textureId + objDesc.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(worldNrm, L) > 0) + { + float tMin = 0.001; + float tMax = lightDistance; + vec3 origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; + vec3 rayDir = L; + uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT; + isShadowed = true; + 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) + ); + + if(isShadowed) + { + attenuation = 0.3; + } + else + { + // Specular + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); + } + } + + prd.hitValue = vec3(lightIntensity * attenuation * (diffuse + specular)); +} diff --git a/ray_tracing_motionblur/shaders/raytrace.rgen b/ray_tracing_motionblur/shaders/raytrace.rgen new file mode 100644 index 0000000..2bc9fef --- /dev/null +++ b/ray_tracing_motionblur/shaders/raytrace.rgen @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require +#extension GL_NV_ray_tracing_motion_blur : require + +#include "raycommon.glsl" +#include "host_device.h" +#include "random.glsl" + +// clang-format off +layout(location = 0) rayPayloadEXT hitPayload prd; + +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on + +const int NBSAMPLES = 10; + +void main() +{ + // Initialize the random number + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, pcRay.frame); + + vec3 hitValues = vec3(0); + + for(int smpl = 0; smpl < NBSAMPLES; smpl++) + { + + float r1 = rnd(seed); + float r2 = rnd(seed); + // Subpixel jitter: send the ray through a different position inside the pixel + // each time, to provide antialiasing. + vec2 subpixel_jitter = pcRay.frame == 0 ? vec2(0.5f, 0.5f) : vec2(r1, r2); + + const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + subpixel_jitter; + const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); + vec2 d = inUV * 2.0 - 1.0; + + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); + + uint rayFlags = gl_RayFlagsOpaqueEXT; + float tMin = 0.001; + float tMax = 10000.0; + float time = rnd(seed); + // float time = float(smpl)/float(NBSAMPLES); // stuttered motion + prd.hitValue = vec3(0, 0, 0); + + traceRayMotionNV(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 + time, // time + 0 // payload (location = 0) + ); + hitValues += prd.hitValue; + } + prd.hitValue = hitValues / NBSAMPLES; + + // Do accumulation over time + if(pcRay.frame > 0) + { + float a = 1.0f / float(pcRay.frame + 1); + vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, prd.hitValue, a), 1.f)); + } + else + { + // First frame, replace the value in the buffer + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.f)); + } +} diff --git a/ray_tracing_motionblur/shaders/raytrace.rmiss b/ray_tracing_motionblur/shaders/raytrace.rmiss new file mode 100644 index 0000000..368a93f --- /dev/null +++ b/ray_tracing_motionblur/shaders/raytrace.rmiss @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +#include "raycommon.glsl" +#include "wavefront.glsl" + +layout(location = 0) rayPayloadInEXT hitPayload prd; + +layout(push_constant) uniform _PushConstantRay +{ + PushConstantRay pcRay; +}; + +void main() +{ + prd.hitValue = pcRay.clearColor.xyz * 0.8; +} diff --git a/ray_tracing_motionblur/shaders/raytraceShadow.rmiss b/ray_tracing_motionblur/shaders/raytraceShadow.rmiss new file mode 100644 index 0000000..bf99caf --- /dev/null +++ b/ray_tracing_motionblur/shaders/raytraceShadow.rmiss @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 1) rayPayloadInEXT bool isShadowed; + +void main() +{ + isShadowed = false; +} diff --git a/ray_tracing_motionblur/shaders/vert_shader.vert b/ray_tracing_motionblur/shaders/vert_shader.vert new file mode 100644 index 0000000..40baa80 --- /dev/null +++ b/ray_tracing_motionblur/shaders/vert_shader.vert @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_GOOGLE_include_directive : enable + +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +#include "wavefront.glsl" + +layout(binding = 0) uniform _GlobalUniforms +{ + GlobalUniforms uni; +}; + +layout(push_constant) uniform _PushConstantRaster +{ + PushConstantRaster pcRaster; +}; + +layout(location = 0) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; + + +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + + +void main() +{ + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); + + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; + + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); +} diff --git a/ray_tracing_motionblur/shaders/wavefront.glsl b/ray_tracing_motionblur/shaders/wavefront.glsl new file mode 100644 index 0000000..b326f8a --- /dev/null +++ b/ray_tracing_motionblur/shaders/wavefront.glsl @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "host_device.h" + +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); +} diff --git a/ray_tracing_rayquery/README.md b/ray_tracing_rayquery/README.md index c3014ac..030343d 100644 --- a/ray_tracing_rayquery/README.md +++ b/ray_tracing_rayquery/README.md @@ -1,23 +1,21 @@ # Ray Query - Tutorial - ![](images/rayquery.png) ## Tutorial ([Setup](../docs/setup.md)) This is an extension of the Vulkan ray tracing [tutorial](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR). - -This extension is allowing to execute ray intersection queries in any shader stages. In this example, we will add +This extension is allowing to execute ray intersection queries in any shader stages. In this example, we will add ray queries [(GLSL_EXT_ray_query)](https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_query.txt) to the fragment shader to cast shadow rays. -In the contrary to all other examples, with this one, we are removing code. There are no need to have a SBT and a raytracing pipeline, the only thing that +In the contrary to all other examples, with this one, we are removing code. There are no need to have a SBT and a raytracing pipeline, the only thing that will matter, is the creation of the acceleration structure. -Starting from the end of the tutorial, [ray_tracing__simple](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing__simple) we will remove +Starting from the end of the tutorial, [ray_tracing__simple](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing__simple) we will remove all functions that were dedicated to ray tracing and keep only the construction of the BLAS and TLAS. -## Cleanup +## Cleanup First, let's remove all extra code @@ -34,7 +32,7 @@ Remove most functions and members to keep only what is need to create the accele VkPhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR}; nvvk::RaytracingBuilderKHR m_rtBuilder; -~~~~ +~~~~ ### hello_vulkan (source) @@ -42,8 +40,7 @@ From the source code, remove the code for all functions that was previously remo ### Shaders -You can safely remove all raytrace.* shaders - +You can safely remove all raytrace.* shaders ## Support for Fragment shader @@ -51,8 +48,17 @@ In `HelloVulkan::createDescriptorSetLayout`, add the acceleration structure to t ~~~~ C++ // The top level acceleration structure - m_descSetLayoutBind.addBinding(3, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT); -~~~~ + m_descSetLayoutBind.addBinding(eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT); +~~~~ + +But since we will use only one descriptor set, change the binding value or `eTlas` to 3, such that binding will look like this + +~~~~ C++ + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2, // Access to textures + eTlas = 3 // Top-level acceleration structure +~~~~ In `HelloVulkan::updateDescriptorSet`, write the value to the descriptor set. @@ -61,11 +67,10 @@ In `HelloVulkan::updateDescriptorSet`, write the value to the descriptor set. VkWriteDescriptorSetAccelerationStructureKHR descASInfo{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR}; descASInfo.accelerationStructureCount = 1; descASInfo.pAccelerationStructures = &tlas; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 3, &descASInfo)); -~~~~ + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, eTlas, &descASInfo)); +~~~~ - -### Shader +### Shader The last modification is in the fragment shader, where we will add the ray intersection query to trace shadow rays. @@ -73,7 +78,7 @@ First, the version has bumpped to 460 ~~~~ C++ #version 460 -~~~~ +~~~~ Then we need to add new extensions @@ -85,24 +90,22 @@ Then we need to add new extensions We have to add the layout to access the top level acceleration structure. ~~~~ C++ -layout(binding = 3, set = 0) uniform accelerationStructureEXT topLevelAS; +layout(binding = eTlas) uniform accelerationStructureEXT topLevelAS; ~~~~ - -Ad the end of the shader, add the following code to initiate the ray query. As we are only interested to know if the ray +At the end of the shader, add the following code to initiate the ray query. As we are only interested to know if the ray has hit something, we can keep the minimal. ~~~~ C++ // Ray Query for shadow -vec3 origin = worldPos; +vec3 origin = i_worldPos; vec3 direction = L; // vector to light float tMin = 0.01f; float tMax = lightDistance; // Initializes a ray query object but does not start traversal rayQueryEXT rayQuery; -rayQueryInitializeEXT(rayQuery, topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT, 0xFF, origin, tMin, - direction, tMax); +rayQueryInitializeEXT(rayQuery, topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT, 0xFF, origin, tMin, direction, tMax); // Start traversal: return false if traversal is complete while(rayQueryProceedEXT(rayQuery)) @@ -113,7 +116,6 @@ while(rayQueryProceedEXT(rayQuery)) if(rayQueryGetIntersectionTypeEXT(rayQuery, true) != gl_RayQueryCommittedIntersectionNoneEXT) { // Got an intersection == Shadow - outColor *= 0.1; + o_color *= 0.1; } ~~~~ - diff --git a/ray_tracing_rayquery/hello_vulkan.cpp b/ray_tracing_rayquery/hello_vulkan.cpp index 07adcd4..8ef66b0 100644 --- a/ray_tracing_rayquery/hello_vulkan.cpp +++ b/ray_tracing_rayquery/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,17 +105,18 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // The top level acceleration structure - m_descSetLayoutBind.addBinding(3, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + m_descSetLayoutBind.addBinding(eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_FRAGMENT_BIT); m_descSetLayout = m_descSetLayoutBind.createLayout(m_device); m_descPool = m_descSetLayoutBind.createPool(m_device, 1); @@ -140,11 +131,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -152,13 +143,13 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); VkAccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure(); VkWriteDescriptorSetAccelerationStructureKHR descASInfo{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR}; descASInfo.accelerationStructureCount = 1; descASInfo.pAccelerationStructures = &tlas; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 3, &descASInfo)); + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, eTlas, &descASInfo)); // Writing the information @@ -171,7 +162,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -231,30 +222,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -264,9 +260,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -275,15 +271,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -369,8 +365,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -418,14 +414,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -614,7 +610,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -663,19 +659,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); diff --git a/ray_tracing_rayquery/hello_vulkan.h b/ray_tracing_rayquery/hello_vulkan.h index 367f0e9..77eea3b 100644 --- a/ray_tracing_rayquery/hello_vulkan.h +++ b/ray_tracing_rayquery/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -44,7 +45,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -62,32 +63,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -97,8 +93,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -107,7 +103,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); diff --git a/ray_tracing_rayquery/main.cpp b/ray_tracing_rayquery/main.cpp index 411451d..081b48d 100644 --- a/ray_tracing_rayquery/main.cpp +++ b/ray_tracing_rayquery/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -164,7 +164,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); // #VKRay helloVk.initRayTracing(); diff --git a/ray_tracing_rayquery/shaders/frag_shader.frag b/ray_tracing_rayquery/shaders/frag_shader.frag index 5ab486b..701fd39 100644 --- a/ray_tracing_rayquery/shaders/frag_shader.frag +++ b/ray_tracing_rayquery/shaders/frag_shader.frag @@ -30,23 +30,19 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object @@ -54,39 +50,39 @@ layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indice layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; -layout(binding = 3) uniform accelerationStructureEXT topLevelAS; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; +layout(binding = eTlas) uniform accelerationStructureEXT topLevelAS; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; float lightDistance; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); lightDistance = d; } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); lightDistance = 10000; } @@ -95,21 +91,21 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); // Ray Query for shadow - vec3 origin = worldPos; + vec3 origin = i_worldPos; vec3 direction = L; // vector to light float tMin = 0.01f; float tMax = lightDistance; @@ -127,6 +123,6 @@ void main() if(rayQueryGetIntersectionTypeEXT(rayQuery, true) != gl_RayQueryCommittedIntersectionNoneEXT) { // Got an intersection == Shadow - outColor *= 0.1; + o_color *= 0.1; } } diff --git a/ray_tracing_rayquery/shaders/host_device.h b/ray_tracing_rayquery/shaders/host_device.h new file mode 100644 index 0000000..7671bc5 --- /dev/null +++ b/ray_tracing_rayquery/shaders/host_device.h @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2, // Access to textures + eTlas = 3 // Top-level acceleration structure +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_rayquery/shaders/vert_shader.vert b/ray_tracing_rayquery/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_rayquery/shaders/vert_shader.vert +++ b/ray_tracing_rayquery/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_rayquery/shaders/wavefront.glsl b/ray_tracing_rayquery/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_rayquery/shaders/wavefront.glsl +++ b/ray_tracing_rayquery/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_reflections/README.md b/ray_tracing_reflections/README.md index 7b27bba..3692c18 100644 --- a/ray_tracing_reflections/README.md +++ b/ray_tracing_reflections/README.md @@ -211,34 +211,27 @@ In `raytrace.rgen`, we can now make the maximum ray depth significantly larger - As an extra, we can also add UI to control the maximum depth. -In the `RtPushConstant` structure, we can add a new `maxDepth` member to pass to the shader. +In the `PushConstantRay` structure, we can add a new `maxDepth` member to pass to the shader. ~~~~ C++ - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity; - int lightType; - int maxDepth{10}; - } m_rtPushConstants; -~~~~ - -In the `raytrace.rgen` shader, we will collect the push constant data - -~~~~ C++ -layout(push_constant) uniform Constants +struct PushConstantRay { vec4 clearColor; vec3 lightPosition; float lightIntensity; int lightType; int maxDepth; -} -pushC; -~~~~ +}; +~~~~ -Then test for the value for when to stop +And we can set a default value to 10, in `hello_vulkan.h` + +~~~~ C++ +PushConstantRay m_pcRay{{}, {}, 0, 0, 10}; +~~~~ + + +In the `raytrace.rgen` shader, we test for the value for when to stop ~~~~ C++ if(prd.done == 1 || prd.depth >= pushC.maxDepth) @@ -248,6 +241,6 @@ Then test for the value for when to stop Finally, in `main.cpp` after the `renderUI()` function call, we will add a slider to control the depth value. ~~~~ C++ - ImGui::SliderInt("Max Depth", &helloVk.m_rtPushConstants.maxDepth, 1, 50); + ImGui::SliderInt("Max Depth", &helloVk.m_pcRay.maxDepth, 1, 50); ~~~~ diff --git a/ray_tracing_reflections/hello_vulkan.cpp b/ray_tracing_reflections/hello_vulkan.cpp index b4317b5..698aebb 100644 --- a/ray_tracing_reflections/hello_vulkan.cpp +++ b/ray_tracing_reflections/hello_vulkan.cpp @@ -40,17 +40,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -70,16 +59,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -95,7 +85,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -115,13 +105,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -138,11 +129,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -150,7 +141,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -162,7 +153,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -222,30 +213,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -255,9 +251,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -266,15 +262,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -360,8 +356,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -415,14 +411,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -612,7 +608,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -661,19 +657,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -686,9 +685,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -708,8 +707,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -722,7 +721,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -794,7 +793,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -860,7 +859,7 @@ void HelloVulkan::createRtShaderBindingTable() VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); - m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT").c_str()); + m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Map the SBT buffer and write in the handles. void* mapped = m_alloc.map(m_rtSBTBuffer); @@ -881,10 +880,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; @@ -893,7 +892,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); // Size of a program identifier diff --git a/ray_tracing_reflections/hello_vulkan.h b/ray_tracing_reflections/hello_vulkan.h index 9642631..f1f70ef 100644 --- a/ray_tracing_reflections/hello_vulkan.h +++ b/ray_tracing_reflections/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -44,7 +45,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -62,32 +63,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -97,8 +93,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -107,7 +103,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -150,12 +146,6 @@ public: VkPipeline m_rtPipeline; nvvk::Buffer m_rtSBTBuffer; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - int maxDepth{10}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{{}, {}, 0, 0, 10}; }; diff --git a/ray_tracing_reflections/main.cpp b/ray_tracing_reflections/main.cpp index 43b3a7f..5f6ad94 100644 --- a/ray_tracing_reflections/main.cpp +++ b/ray_tracing_reflections/main.cpp @@ -56,12 +56,12 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } } @@ -168,7 +168,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay @@ -211,7 +211,7 @@ int main(int argc, char** argv) ImGui::Checkbox("Ray Tracer mode", &useRaytracer); // Switch between raster and ray tracing renderUI(helloVk); - ImGui::SliderInt("Max Depth", &helloVk.m_rtPushConstants.maxDepth, 1, 50); + ImGui::SliderInt("Max Depth", &helloVk.m_pcRay.maxDepth, 1, 50); 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(); diff --git a/ray_tracing_reflections/shaders/frag_shader.frag b/ray_tracing_reflections/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_reflections/shaders/frag_shader.frag +++ b/ray_tracing_reflections/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_reflections/shaders/host_device.h b/ray_tracing_reflections/shaders/host_device.h new file mode 100644 index 0000000..b8de537 --- /dev/null +++ b/ray_tracing_reflections/shaders/host_device.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int maxDepth; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_reflections/shaders/raytrace.rchit b/ray_tracing_reflections/shaders/raytrace.rchit index db13b0b..ba9a116 100644 --- a/ray_tracing_reflections/shaders/raytrace.rchit +++ b/ray_tracing_reflections/shaders/raytrace.rchit @@ -39,25 +39,18 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; -// clang-format on +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; -} -pushC; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -73,32 +66,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -107,10 +97,10 @@ void main() // Diffuse - vec3 diffuse = computeDiffuse(mat, L, normal); + vec3 diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -119,7 +109,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { float tMin = 0.001; float tMax = lightDistance; @@ -147,7 +137,7 @@ void main() else { // Specular - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } @@ -155,7 +145,7 @@ void main() if(mat.illum == 3) { vec3 origin = worldPos; - vec3 rayDir = reflect(gl_WorldRayDirectionEXT, normal); + vec3 rayDir = reflect(gl_WorldRayDirectionEXT, worldNrm); prd.attenuation *= mat.specular; prd.done = 0; prd.rayOrigin = origin; diff --git a/ray_tracing_reflections/shaders/raytrace.rgen b/ray_tracing_reflections/shaders/raytrace.rgen index e715e86..e759534 100644 --- a/ray_tracing_reflections/shaders/raytrace.rgen +++ b/ray_tracing_reflections/shaders/raytrace.rgen @@ -20,31 +20,22 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; + +// clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int maxDepth; -} -pushC; void main() { @@ -52,9 +43,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; @@ -85,7 +76,7 @@ void main() hitValue += prd.hitValue * prd.attenuation; prd.depth++; - if(prd.done == 1 || prd.depth >= pushC.maxDepth) + if(prd.done == 1 || prd.depth >= pcRay.maxDepth) break; origin.xyz = prd.rayOrigin; diff --git a/ray_tracing_reflections/shaders/raytrace.rmiss b/ray_tracing_reflections/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_reflections/shaders/raytrace.rmiss +++ b/ray_tracing_reflections/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_reflections/shaders/vert_shader.vert b/ray_tracing_reflections/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_reflections/shaders/vert_shader.vert +++ b/ray_tracing_reflections/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_reflections/shaders/wavefront.glsl b/ray_tracing_reflections/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_reflections/shaders/wavefront.glsl +++ b/ray_tracing_reflections/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) { diff --git a/ray_tracing_specialization/README.md b/ray_tracing_specialization/README.md index f7046b1..18b654a 100644 --- a/ray_tracing_specialization/README.md +++ b/ray_tracing_specialization/README.md @@ -2,8 +2,8 @@ ![](images/specialization.png) -In Vulkans, shaders are compiled to Spir-V, but the driver completes optimization during pipeline creation. -Having specialization constants in a shader, is like having #defines that can be changed when pipeline creation is submitted. +In Vulkans, shaders are compiled to Spir-V, but the driver completes optimization during pipeline creation. +Having specialization constants in a shader, is like having #defines that can be changed when pipeline creation is submitted. In this example, we will add three specialization constants with all possible permutations. @@ -13,7 +13,7 @@ In this example, we will add three specialization constants with all possible pe ## Shader -In the closest hit shader (`raytrace.chit`), we will add the three constants. Note that we are using `int` only because the tiny helper class later is made for `int` values. +In the closest hit shader (`raytrace.chit`), we will add the three constants. Note that we are using `int` only because the tiny helper class later is made for `int` values. ~~~~ C layout(constant_id = 0) const int USE_DIFFUSE = 1; @@ -130,12 +130,12 @@ private: std::vector spec_entries; VkSpecializationInfo spec_info; }; -~~~~ +~~~~ In `HelloVulkan::createRtPipeline()`, we will create 8 specialization of the closest hit shader. So the number of stages, will be 11 instead of 4. -~~~~ C +~~~~ C enum StageIndices { eRaygen, @@ -158,7 +158,7 @@ Then create a `Specialization` for each of the 8 on/off permutations of the 3 co int c = ((i >> 0) % 2) == 1; specializations[i].add({{0, a}, {1, b}, {2, c}}); } -~~~~ +~~~~ Now the shader group will be created 8 times, each with a different specialization. @@ -174,10 +174,9 @@ Now the shader group will be created 8 times, each with a different specializati } ~~~~ -**Tip** : We can avoid to create 8 shader modules, but we would have to properly deal with the +**Tip** : We can avoid to create 8 shader modules, but we would have to properly deal with the deletion of them at the end of the function. - We will also modify the creation of the hit group to create as many HIT shader groups as we have specializations. This will give us the ability later to choose which 'specialization' we want to use. ~~~~ C @@ -193,50 +192,42 @@ We will also modify the creation of the hit group to create as many HIT shader g ~~~~ **Note**, it is important that the data and structures are not created on the stack inside the loop, -because we are passing the data address and specialization information, so all this would become +because we are passing the data address and specialization information, so all this would become invalid when the pipeline is created. -# Using Specialization +## Using Specialization If you would run the sample, nothing would have changed. This is because each TLAS's `hitGroupId` is set to `0`. -A quick test would be to change the value to `4`, corresponding to only using diffuse. +A quick test would be to change the value to `4`, corresponding to only using diffuse. ~~~~ C rayInst.hitGroupId = 4; // We will use the same hit group for all objects -~~~~ +~~~~ Knowing the type of material each object is using, it would be possible to choose the appropriate specialization for each object. ## Interactive Change -In our example, we will allow to choose globally the specialization for all objects. To do this, we will add -a new entry to the push constants structure, for both `ObjPushConstant` and `RtPushConstant`. +In our example, we will allow to choose globally the specialization for all objects. To do this, we will add +a new entry to the push constants structure of `PushConstantRay`. -At the end of both structures, add +At the end of the structures, add ~~~~ C -int specialization{7}; // All in use -~~~~ +int specialization; +~~~~ + +and in the `hello_vulkan.h`, initialize the member to 7 (all) + +~~~~C + PushConstantRay m_pcRay{{}, {}, 0, 0, 7}; +~~~~ In `raytrace.rgen`, we will use this new value to offset the hit group. Instead of always taking the hit group 0, it will use the one we choose. -Add the `specialization` to the push constant layout. - -~~~~ C -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int specialization; -} -pushC; -~~~~ - -Then where we trace, we will use the specialization value to change the SBT offset. +When we call trace, we will use the specialization value to change the SBT offset. ~~~~ C traceRayEXT(topLevelAS, // acceleration structure @@ -259,25 +250,19 @@ In main.cpp `renderUI()`, add the following code. ~~~~ C // Specialization - ImGui::SliderInt("Specialization", &helloVk.m_pushConstant.specialization, 0, 7); - int s = helloVk.m_pushConstant.specialization; + ImGui::SliderInt("Specialization", &helloVk.m_pcRay.specialization, 0, 7); + int s = helloVk.m_pcRay.specialization; int a = ((s >> 2) % 2) == 1; int b = ((s >> 1) % 2) == 1; int c = ((s >> 0) % 2) == 1; ImGui::Checkbox("Use Diffuse", (bool*)&a); ImGui::Checkbox("Use Specular", (bool*)&b); ImGui::Checkbox("Trace shadow", (bool*)&c); - helloVk.m_pushConstant.specialization = (a << 2) + (b << 1) + c; -~~~~ - - - - - + helloVk.m_pcRay.specialization = (a << 2) + (b << 1) + c; +~~~~ ## References * Pipelines [Specialization Constants](https://www.khronos.org/registry/vulkan/specs/1.1-khr-extensions/html/chap10.html#pipelines-specialization-constants) * [VkSpecializationInfo](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSpecializationInfo.html) * [VkSpecializationMapEntry](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkSpecializationMapEntry.html) - diff --git a/ray_tracing_specialization/hello_vulkan.cpp b/ray_tracing_specialization/hello_vulkan.cpp index b7b307b..ea98960 100644 --- a/ray_tracing_specialization/hello_vulkan.cpp +++ b/ray_tracing_specialization/hello_vulkan.cpp @@ -41,17 +41,6 @@ extern std::vector defaultSearchPaths; -// 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 @@ -71,16 +60,17 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& 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); + GlobalUniforms hostUBO = {}; + const auto& view = CameraManip.getMatrix(); + const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + + hostUBO.viewProj = proj * view; + hostUBO.viewInverse = nvmath::invert(view); + hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. - VkBuffer deviceUBO = m_cameraMat.buffer; + VkBuffer deviceUBO = m_bGlobals.buffer; auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; // Ensure that the modified UBO is not visible to previous frames. @@ -96,7 +86,7 @@ void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) // Schedule the host-to-device upload. (hostUBO is copied into the cmd // buffer so it is okay to deallocate when the function returns). - vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + vkCmdUpdateBuffer(cmdBuf, m_bGlobals.buffer, 0, sizeof(GlobalUniforms), &hostUBO); // Making sure the updated UBO will be visible. VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; @@ -116,13 +106,14 @@ void HelloVulkan::createDescriptorSetLayout() { auto nbTxt = static_cast(m_textures.size()); - // Camera matrices (binding = 0) - m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); - // Scene description (binding = 1) - m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + // Camera matrices + m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Obj descriptions + m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); - // Textures (binding = 2) - m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + // Textures + m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); @@ -139,11 +130,11 @@ void HelloVulkan::updateDescriptorSet() std::vector writes; // Camera matrices and scene description - VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiUnif{m_bGlobals.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); - VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; - writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 1, &dbiSceneDesc)); + VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; @@ -151,7 +142,7 @@ void HelloVulkan::updateDescriptorSet() { diit.emplace_back(texture.descriptor); } - writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 2, diit.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, diit.data())); // Writing the information vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); @@ -163,7 +154,7 @@ void HelloVulkan::updateDescriptorSet() // void HelloVulkan::createGraphicsPipeline() { - VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster)}; // Creating the Pipeline Layout VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -223,30 +214,35 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); - // Creates all textures found - uint32_t txtOffset = static_cast(m_textures.size()); + // Creates all textures found and find the offset for this model + auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); - 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_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); + // Keeping transformation matrix of the instance ObjInstance instance; - instance.objIndex = static_cast(m_objModel.size()); - instance.transform = transform; - instance.transformIT = nvmath::transpose(nvmath::invert(transform)); - instance.txtOffset = txtOffset; - instance.vertices = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); - instance.indices = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); - instance.materials = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); - instance.materialIndices = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + instance.transform = transform; + instance.objIndex = static_cast(m_objModel.size()); + m_instances.push_back(instance); + // Creating information for device access + ObjDesc desc; + desc.txtOffset = txtOffset; + desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); + desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); + desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); + desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); + + // Keeping the obj host model and device description m_objModel.emplace_back(model); - m_objInstance.emplace_back(instance); + m_objDesc.emplace_back(desc); } @@ -256,9 +252,9 @@ void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform // void HelloVulkan::createUniformBuffer() { - m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); + m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- @@ -267,15 +263,15 @@ void HelloVulkan::createUniformBuffer() // - Transformation // - Offset for texture // -void HelloVulkan::createSceneDescriptionBuffer() +void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); - m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); - m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); + m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- @@ -361,8 +357,8 @@ void HelloVulkan::destroyResources() vkDestroyDescriptorPool(m_device, m_descPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); - m_alloc.destroy(m_cameraMat); - m_alloc.destroy(m_sceneDesc); + m_alloc.destroy(m_bGlobals); + m_alloc.destroy(m_bObjDesc); for(auto& m : m_objModel) { @@ -416,14 +412,14 @@ void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); - for(int i = 0; i < m_objInstance.size(); ++i) + for(const HelloVulkan::ObjInstance& inst : m_instances) { - auto& inst = m_objInstance[i]; - auto& model = m_objModel[inst.objIndex]; - m_pushConstant.instanceId = i; // Telling which instance is drawn + auto& model = m_objModel[inst.objIndex]; + m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn + m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, - sizeof(ObjPushConstant), &m_pushConstant); + sizeof(PushConstantRaster), &m_pcRaster); vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); @@ -614,7 +610,7 @@ auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) // Describe buffer as array of VertexObj. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; - triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(VertexObj); // Describe index data (32-bit unsigned int) @@ -663,19 +659,22 @@ void HelloVulkan::createBottomLevelAS() m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } +//-------------------------------------------------------------------------------------------------- +// +// void HelloVulkan::createTopLevelAS() { std::vector tlas; - tlas.reserve(m_objInstance.size()); - for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + tlas.reserve(m_instances.size()); + for(const HelloVulkan::ObjInstance& inst : m_instances) { - VkAccelerationStructureInstanceKHR rayInst; - rayInst.transform = nvvk::toTransformMatrixKHR(m_objInstance[i].transform); // Position of the instance - rayInst.instanceCustomIndex = i; // gl_InstanceCustomIndexEXT - rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(m_objInstance[i].objIndex); + VkAccelerationStructureInstanceKHR rayInst{}; + rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance + rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT + rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects - rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; - rayInst.mask = 0xFF; tlas.emplace_back(rayInst); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); @@ -688,9 +687,9 @@ void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) - m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS - m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); @@ -710,8 +709,8 @@ void HelloVulkan::createRtDescriptorSet() VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; 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, RtxBindings::eTlas, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } @@ -724,7 +723,7 @@ void HelloVulkan::updateRtDescriptorSet() { // (1) Output buffer VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; - VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } @@ -763,7 +762,7 @@ public: private: std::vector spec_values; std::vector spec_entries; - VkSpecializationInfo spec_info; + VkSpecializationInfo spec_info{}; }; @@ -854,7 +853,7 @@ void HelloVulkan::createRtPipeline() // Push constant: we want to be able to update constants used by the shaders VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant)}; + 0, sizeof(PushConstantRay)}; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; @@ -907,11 +906,10 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c { m_debug.beginLabel(cmdBuf, "Ray trace"); // Initializing 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.specialization = m_pushConstant.specialization; + m_pcRay.clearColor = clearColor; + m_pcRay.lightPosition = m_pcRaster.lightPosition; + m_pcRay.lightIntensity = m_pcRaster.lightIntensity; + m_pcRay.lightType = m_pcRaster.lightType; std::vector descSets{m_rtDescSet, m_descSet}; vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); @@ -919,7 +917,7 @@ void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& c (uint32_t)descSets.size(), descSets.data(), 0, nullptr); vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, - 0, sizeof(RtPushConstant), &m_rtPushConstants); + 0, sizeof(PushConstantRay), &m_pcRay); auto& regions = m_sbtWrapper.getRegions(); vkCmdTraceRaysKHR(cmdBuf, ®ions[0], ®ions[1], ®ions[2], ®ions[3], m_size.width, m_size.height, 1); diff --git a/ray_tracing_specialization/hello_vulkan.h b/ray_tracing_specialization/hello_vulkan.h index ce1b62b..3068d57 100644 --- a/ray_tracing_specialization/hello_vulkan.h +++ b/ray_tracing_specialization/hello_vulkan.h @@ -24,6 +24,7 @@ #include "nvvk/descriptorsets_vk.hpp" #include "nvvk/memallocator_dma_vk.hpp" #include "nvvk/resourceallocator_vk.hpp" +#include "shaders/host_device.h" // #VKRay #include "nvvk/raytraceKHR_vk.hpp" @@ -45,7 +46,7 @@ public: void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); void updateDescriptorSet(); void createUniformBuffer(); - void createSceneDescriptionBuffer(); + void createObjDescriptionBuffer(); void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); void updateUniformBuffer(const VkCommandBuffer& cmdBuf); void onResize(int /*w*/, int /*h*/) override; @@ -63,33 +64,27 @@ public: nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' }; - // Instance of the OBJ struct ObjInstance { - nvmath::mat4f transform{1}; // Position of the instance - nvmath::mat4f transformIT{1}; // Inverse transpose - uint32_t objIndex{0}; // Reference to the `m_objModel` - uint32_t txtOffset{0}; // Offset in `m_textures` - VkDeviceAddress vertices; - VkDeviceAddress indices; - VkDeviceAddress materials; - VkDeviceAddress materialIndices; + nvmath::mat4f transform; // Matrix of the instance + uint32_t objIndex{0}; // Model index reference }; + // 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{100.f}; - int lightType{0}; // 0: point, 1: infinite - int specialization{7}; // all in use + PushConstantRaster m_pcRaster{ + {1}, // Identity matrix + {10.f, 15.f, 8.f}, // light position + 0, // instance Id + 100.f, // light intensity + 0 // light type }; - ObjPushConstant m_pushConstant; // Array of objects and instances in the scene - std::vector m_objModel; - std::vector m_objInstance; + std::vector m_objModel; // Model on host + std::vector m_objDesc; // Model description for device access + std::vector m_instances; // Scene model instances + // Graphic pipeline VkPipelineLayout m_pipelineLayout; @@ -99,8 +94,8 @@ public: VkDescriptorSetLayout m_descSetLayout; VkDescriptorSet m_descSet; - nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices - nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + nvvk::Buffer m_bGlobals; // Device-Host of the camera matrices + nvvk::Buffer m_bObjDesc; // Device buffer of the OBJ descriptions std::vector m_textures; // vector of all textures of the scene @@ -109,7 +104,7 @@ public: nvvk::DebugUtil m_debug; // Utility to name objects - // #Post + // #Post - Draw the rendered image on a quad using a tonemapper void createOffscreenRender(); void createPostPipeline(); void createPostDescriptor(); @@ -151,12 +146,6 @@ public: VkPipeline m_rtPipeline; nvvk::SBTWrapper m_sbtWrapper; - struct RtPushConstant - { - nvmath::vec4f clearColor; - nvmath::vec3f lightPosition; - float lightIntensity{100.0f}; - int lightType{0}; - int specialization{7}; - } m_rtPushConstants; + // Push constant for ray tracer + PushConstantRay m_pcRay{{}, {}, 0, 0, 7}; }; diff --git a/ray_tracing_specialization/main.cpp b/ray_tracing_specialization/main.cpp index 138f06a..7c47bfe 100644 --- a/ray_tracing_specialization/main.cpp +++ b/ray_tracing_specialization/main.cpp @@ -56,24 +56,24 @@ void renderUI(HelloVulkan& helloVk) ImGuiH::CameraWidget(); if(ImGui::CollapsingHeader("Light")) { - ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::RadioButton("Point", &helloVk.m_pcRaster.lightType, 0); ImGui::SameLine(); - ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + ImGui::RadioButton("Infinite", &helloVk.m_pcRaster.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::SliderFloat3("Position", &helloVk.m_pcRaster.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pcRaster.lightIntensity, 0.f, 150.f); } // Specialization - ImGui::SliderInt("Specialization", &helloVk.m_pushConstant.specialization, 0, 7); - int s = helloVk.m_pushConstant.specialization; + ImGui::SliderInt("Specialization", &helloVk.m_pcRay.specialization, 0, 7); + int s = helloVk.m_pcRay.specialization; int a = ((s >> 2) % 2) == 1; int b = ((s >> 1) % 2) == 1; int c = ((s >> 0) % 2) == 1; ImGui::Checkbox("Use Diffuse", (bool*)&a); ImGui::Checkbox("Use Specular", (bool*)&b); ImGui::Checkbox("Trace shadow", (bool*)&c); - helloVk.m_pushConstant.specialization = (a << 2) + (b << 1) + c; + helloVk.m_pcRay.specialization = (a << 2) + (b << 1) + c; } ////////////////////////////////////////////////////////////////////////// @@ -175,7 +175,7 @@ int main(int argc, char** argv) helloVk.createDescriptorSetLayout(); helloVk.createGraphicsPipeline(); helloVk.createUniformBuffer(); - helloVk.createSceneDescriptionBuffer(); + helloVk.createObjDescriptionBuffer(); helloVk.updateDescriptorSet(); // #VKRay diff --git a/ray_tracing_specialization/shaders/frag_shader.frag b/ray_tracing_specialization/shaders/frag_shader.frag index 7c3b8bc..0930980 100644 --- a/ray_tracing_specialization/shaders/frag_shader.frag +++ b/ray_tracing_specialization/shaders/frag_shader.frag @@ -29,59 +29,55 @@ #include "wavefront.glsl" -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; // clang-format off // Incoming -layout(location = 1) in vec2 fragTexCoord; -layout(location = 2) in vec3 fragNormal; -layout(location = 3) in vec3 viewDir; -layout(location = 4) in vec3 worldPos; +layout(location = 1) in vec3 i_worldPos; +layout(location = 2) in vec3 i_worldNrm; +layout(location = 3) in vec3 i_viewDir; +layout(location = 4) in vec2 i_texCoord; // Outgoing -layout(location = 0) out vec4 outColor; +layout(location = 0) out vec4 o_color; layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object layout(buffer_reference, scalar) buffer Indices {uint i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2) uniform sampler2D[] textureSamplers; +layout(binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(binding = eTextures) uniform sampler2D[] textureSamplers; // clang-format on void main() { // Material of the object - SceneDesc objResource = sceneDesc.i[pushC.instanceId]; + ObjDesc objResource = objDesc.i[pcRaster.objIndex]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); int matIndex = matIndices.i[gl_PrimitiveID]; WaveFrontMaterial mat = materials.m[matIndex]; - vec3 N = normalize(fragNormal); + vec3 N = normalize(i_worldNrm); // Vector toward light vec3 L; - float lightIntensity = pushC.lightIntensity; - if(pushC.lightType == 0) + float lightIntensity = pcRaster.lightIntensity; + if(pcRaster.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRaster.lightPosition - i_worldPos; float d = length(lDir); - lightIntensity = pushC.lightIntensity / (d * d); + lightIntensity = pcRaster.lightIntensity / (d * d); L = normalize(lDir); } else { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRaster.lightPosition); } @@ -89,15 +85,15 @@ void main() vec3 diffuse = computeDiffuse(mat, L, N); if(mat.textureId >= 0) { - int txtOffset = sceneDesc.i[pushC.instanceId].txtOffset; + int txtOffset = objDesc.i[pcRaster.objIndex].txtOffset; uint txtId = txtOffset + mat.textureId; - vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], i_texCoord).xyz; diffuse *= diffuseTxt; } // Specular - vec3 specular = computeSpecular(mat, viewDir, L, N); + vec3 specular = computeSpecular(mat, i_viewDir, L, N); // Result - outColor = vec4(lightIntensity * (diffuse + specular), 1); + o_color = vec4(lightIntensity * (diffuse + specular), 1); } diff --git a/ray_tracing_specialization/shaders/host_device.h b/ray_tracing_specialization/shaders/host_device.h new file mode 100644 index 0000000..e7e235e --- /dev/null +++ b/ray_tracing_specialization/shaders/host_device.h @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#ifndef COMMON_HOST_DEVICE +#define COMMON_HOST_DEVICE + +#ifdef __cplusplus +#include "nvmath/nvmath.h" +// GLSL Type +using vec2 = nvmath::vec2f; +using vec3 = nvmath::vec3f; +using vec4 = nvmath::vec4f; +using mat4 = nvmath::mat4f; +using uint = unsigned int; +#endif + +// clang-format off +#ifdef __cplusplus // Descriptor binding helper for C++ and GLSL + #define START_BINDING(a) enum a { + #define END_BINDING() } +#else + #define START_BINDING(a) const uint + #define END_BINDING() +#endif + +START_BINDING(SceneBindings) + eGlobals = 0, // Global uniform containing camera matrices + eObjDescs = 1, // Access to the object descriptions + eTextures = 2 // Access to textures +END_BINDING(); + +START_BINDING(RtxBindings) + eTlas = 0, // Top-level acceleration structure + eOutImage = 1 // Ray tracer output image +END_BINDING(); +// clang-format on + + +// Information of a obj model when referenced in a shader +struct ObjDesc +{ + int txtOffset; // Texture index offset in the array of textures + uint64_t vertexAddress; // Address of the Vertex buffer + uint64_t indexAddress; // Address of the index buffer + uint64_t materialAddress; // Address of the material buffer + uint64_t materialIndexAddress; // Address of the triangle material index buffer +}; + +// Uniform buffer set at each frame +struct GlobalUniforms +{ + mat4 viewProj; // Camera view * projection + mat4 viewInverse; // Camera inverse view matrix + mat4 projInverse; // Camera inverse projection matrix +}; + +// Push constant structure for the raster +struct PushConstantRaster +{ + mat4 modelMatrix; // matrix of the instance + vec3 lightPosition; + uint objIndex; + float lightIntensity; + int lightType; +}; + + +// Push constant structure for the ray tracer +struct PushConstantRay +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int specialization; +}; + +struct Vertex // See ObjLoader, copy of VertexObj, could be compressed for device +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial // See ObjLoader, copy of MaterialObj, could be compressed for device +{ + 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; +}; + + +#endif diff --git a/ray_tracing_specialization/shaders/raytrace.rchit b/ray_tracing_specialization/shaders/raytrace.rchit index 45484aa..0f6b022 100644 --- a/ray_tracing_specialization/shaders/raytrace.rchit +++ b/ray_tracing_specialization/shaders/raytrace.rchit @@ -39,9 +39,11 @@ layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of layout(buffer_reference, scalar) buffer Indices {ivec3 i[]; }; // Triangle indices layout(buffer_reference, scalar) buffer Materials {WaveFrontMaterial m[]; }; // Array of all materials on an object layout(buffer_reference, scalar) buffer MatIndices {int i[]; }; // Material ID for each triangle -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -layout(binding = 2, set = 1) uniform sampler2D textureSamplers[]; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 1, binding = eObjDescs, scalar) buffer ObjDesc_ { ObjDesc i[]; } objDesc; +layout(set = 1, binding = eTextures) uniform sampler2D textureSamplers[]; + +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; layout(constant_id = 0) const int USE_DIFFUSE = 1; layout(constant_id = 1) const int USE_SPECULAR = 1; @@ -49,21 +51,11 @@ layout(constant_id = 2) const int TRACE_SHADOW = 1; // clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int specialization; -} -pushC; - void main() { // Object data - SceneDesc objResource = sceneDesc.i[gl_InstanceCustomIndexEXT]; + ObjDesc objResource = objDesc.i[gl_InstanceCustomIndexEXT]; MatIndices matIndices = MatIndices(objResource.materialIndexAddress); Materials materials = Materials(objResource.materialAddress); Indices indices = Indices(objResource.indexAddress); @@ -79,32 +71,29 @@ void main() 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(sceneDesc.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(sceneDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + const vec3 pos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + const vec3 worldPos = vec3(gl_ObjectToWorldEXT * vec4(pos, 1.0)); // Transforming the position to world space + + // Computing the normal at hit position + const vec3 nrm = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + const vec3 worldNrm = normalize(vec3(nrm * gl_WorldToObjectEXT)); // Transforming the normal to world space // Vector toward the light vec3 L; - float lightIntensity = pushC.lightIntensity; + float lightIntensity = pcRay.lightIntensity; float lightDistance = 100000.0; // Point light - if(pushC.lightType == 0) + if(pcRay.lightType == 0) { - vec3 lDir = pushC.lightPosition - worldPos; + vec3 lDir = pcRay.lightPosition - worldPos; lightDistance = length(lDir); - lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + lightIntensity = pcRay.lightIntensity / (lightDistance * lightDistance); L = normalize(lDir); } else // Directional light { - L = normalize(pushC.lightPosition - vec3(0)); + L = normalize(pcRay.lightPosition); } // Material of the object @@ -116,10 +105,10 @@ void main() vec3 diffuse = vec3(0); if(USE_DIFFUSE == 1) { - diffuse = computeDiffuse(mat, L, normal); + diffuse = computeDiffuse(mat, L, worldNrm); if(mat.textureId >= 0) { - uint txtId = mat.textureId + sceneDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + uint txtId = mat.textureId + objDesc.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; } @@ -129,7 +118,7 @@ void main() float attenuation = 1; // Tracing shadow ray only if the light is visible from the surface - if(dot(normal, L) > 0) + if(dot(worldNrm, L) > 0) { if(TRACE_SHADOW == 1) { @@ -164,7 +153,7 @@ void main() // Specular if(USE_SPECULAR == 1) { - specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, worldNrm); } } } diff --git a/ray_tracing_specialization/shaders/raytrace.rgen b/ray_tracing_specialization/shaders/raytrace.rgen index 655e6d0..c2ba199 100644 --- a/ray_tracing_specialization/shaders/raytrace.rgen +++ b/ray_tracing_specialization/shaders/raytrace.rgen @@ -20,31 +20,21 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + + #include "raycommon.glsl" +#include "wavefront.glsl" -layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; -layout(binding = 1, set = 0, rgba32f) uniform image2D image; - +// clang-format off layout(location = 0) rayPayloadEXT hitPayload prd; -layout(binding = 0, set = 1) uniform CameraProperties -{ - mat4 view; - mat4 proj; - mat4 viewInverse; - mat4 projInverse; -} -cam; +layout(set = 0, binding = eTlas) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = eOutImage, rgba32f) uniform image2D image; +layout(set = 1, binding = eGlobals) uniform _GlobalUniforms { GlobalUniforms uni; }; +layout(push_constant) uniform _PushConstantRay { PushConstantRay pcRay; }; +// clang-format on -layout(push_constant) uniform Constants -{ - vec4 clearColor; - vec3 lightPosition; - float lightIntensity; - int lightType; - int specialization; -} -pushC; void main() { @@ -52,9 +42,9 @@ void main() const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); 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); + vec4 origin = uni.viewInverse * vec4(0, 0, 0, 1); + vec4 target = uni.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = uni.viewInverse * vec4(normalize(target.xyz), 0); uint rayFlags = gl_RayFlagsOpaqueEXT; float tMin = 0.001; @@ -63,7 +53,7 @@ void main() traceRayEXT(topLevelAS, // acceleration structure rayFlags, // rayFlags 0xFF, // cullMask - pushC.specialization, // sbtRecordOffset + pcRay.specialization, // sbtRecordOffset 0, // sbtRecordStride 0, // missIndex origin.xyz, // ray origin diff --git a/ray_tracing_specialization/shaders/raytrace.rmiss b/ray_tracing_specialization/shaders/raytrace.rmiss index 92c7706..368a93f 100644 --- a/ray_tracing_specialization/shaders/raytrace.rmiss +++ b/ray_tracing_specialization/shaders/raytrace.rmiss @@ -20,16 +20,19 @@ #version 460 #extension GL_EXT_ray_tracing : require #extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + #include "raycommon.glsl" +#include "wavefront.glsl" layout(location = 0) rayPayloadInEXT hitPayload prd; -layout(push_constant) uniform Constants +layout(push_constant) uniform _PushConstantRay { - vec4 clearColor; + PushConstantRay pcRay; }; void main() { - prd.hitValue = clearColor.xyz * 0.8; + prd.hitValue = pcRay.clearColor.xyz * 0.8; } diff --git a/ray_tracing_specialization/shaders/vert_shader.vert b/ray_tracing_specialization/shaders/vert_shader.vert index c79820d..40baa80 100644 --- a/ray_tracing_specialization/shaders/vert_shader.vert +++ b/ray_tracing_specialization/shaders/vert_shader.vert @@ -26,38 +26,26 @@ #include "wavefront.glsl" -// clang-format off -layout(binding = 1, scalar) buffer SceneDesc_ { SceneDesc i[]; } sceneDesc; -// clang-format on - -layout(binding = 0) uniform UniformBufferObject +layout(binding = 0) uniform _GlobalUniforms { - mat4 view; - mat4 proj; - mat4 viewI; -} -ubo; + GlobalUniforms uni; +}; -layout(push_constant) uniform shaderInformation +layout(push_constant) uniform _PushConstantRaster { - vec3 lightPosition; - uint instanceId; - float lightIntensity; - int lightType; -} -pushC; + PushConstantRaster pcRaster; +}; -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) in vec3 i_position; +layout(location = 1) in vec3 i_normal; +layout(location = 2) in vec3 i_color; +layout(location = 3) in vec2 i_texCoord; -//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; +layout(location = 1) out vec3 o_worldPos; +layout(location = 2) out vec3 o_worldNrm; +layout(location = 3) out vec3 o_viewDir; +layout(location = 4) out vec2 o_texCoord; out gl_PerVertex { @@ -67,16 +55,12 @@ out gl_PerVertex void main() { - mat4 objMatrix = sceneDesc.i[pushC.instanceId].transfo; - mat4 objMatrixIT = sceneDesc.i[pushC.instanceId].transfoIT; + vec3 origin = vec3(uni.viewInverse * vec4(0, 0, 0, 1)); - vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + o_worldPos = vec3(pcRaster.modelMatrix * vec4(i_position, 1.0)); + o_viewDir = vec3(o_worldPos - origin); + o_texCoord = i_texCoord; + o_worldNrm = mat3(pcRaster.modelMatrix) * i_normal; - 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); + gl_Position = uni.viewProj * vec4(o_worldPos, 1.0); } diff --git a/ray_tracing_specialization/shaders/wavefront.glsl b/ray_tracing_specialization/shaders/wavefront.glsl index 3c321f3..b326f8a 100644 --- a/ray_tracing_specialization/shaders/wavefront.glsl +++ b/ray_tracing_specialization/shaders/wavefront.glsl @@ -17,40 +17,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -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 -{ - mat4 transfo; - mat4 transfoIT; - int objId; - int txtOffset; - uint64_t vertexAddress; - uint64_t indexAddress; - uint64_t materialAddress; - uint64_t materialIndexAddress; -}; - +#include "host_device.h" vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) {