From ebc41274725d549dfeabbc69f52a7c2a9ef24fb6 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sat, 13 May 2023 12:22:04 +0200 Subject: [PATCH] Started working on ray tracing intersection shader example --- README.md | 4 + .../raytracingintersection.cpp | 604 ++++++++++++++++++ .../raytracingintersection/closesthit.rchit | 35 + .../closesthit.rchit.spv | Bin 0 -> 2600 bytes .../glsl/raytracingintersection/miss.rmiss | 9 + .../raytracingintersection/miss.rmiss.spv | Bin 0 -> 368 bytes .../glsl/raytracingintersection/raygen.rgen | 33 + .../raytracingintersection/raygen.rgen.spv | Bin 0 -> 3080 bytes 8 files changed, 685 insertions(+) create mode 100644 examples/raytracingintersection/raytracingintersection.cpp create mode 100644 shaders/glsl/raytracingintersection/closesthit.rchit create mode 100644 shaders/glsl/raytracingintersection/closesthit.rchit.spv create mode 100644 shaders/glsl/raytracingintersection/miss.rmiss create mode 100644 shaders/glsl/raytracingintersection/miss.rmiss.spv create mode 100644 shaders/glsl/raytracingintersection/raygen.rgen create mode 100644 shaders/glsl/raytracingintersection/raygen.rgen.spv diff --git a/README.md b/README.md index 3620efe1..421d1963 100644 --- a/README.md +++ b/README.md @@ -345,6 +345,10 @@ Renders a texture mapped quad with transparency using the new ray tracing extens Callable shaders can be dynamically invoked from within other ray tracing shaders to execute different shaders based on dynamic conditions. The example ray traces multiple geometries, with each calling a different callable shader from the closest hit shader. +#### [Ray tracing intersection shaders](examples/raytracingintersection) + +Uses an intersection shader for procedural geometry. Instead of using actual geometry, this sample on passes bounding boxes and object definitions. An intersection shader is then used to trace against the procedural objects. + #### [Ray query](examples/rayquery) Ray queries add acceleration structure intersection functionality to non ray tracing shader stages. This allows for combining ray tracing with rasterization. This example makes uses ray queries to add ray casted shadows to a rasterized sample in the fragment shader. diff --git a/examples/raytracingintersection/raytracingintersection.cpp b/examples/raytracingintersection/raytracingintersection.cpp new file mode 100644 index 00000000..4b076c79 --- /dev/null +++ b/examples/raytracingintersection/raytracingintersection.cpp @@ -0,0 +1,604 @@ +/* + * Vulkan Example - Hardware accelerated ray tracing intersection shader samples + * + * Copyright (C) 2023 by Sascha Willems - www.saschawillems.de + * + * This sample uses intersection shaders for doing prodcedural ray traced geometry + * Instead of passing actual geometry, this samples only passes bounding boxes and sphere descriptions + * The bounding boxes are used for the ray traversal and the sphere intersections are done + * within the intersection shader + * + * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + */ + +#include "VulkanRaytracingSample.h" + +class VulkanExample : public VulkanRaytracingSample +{ +public: + AccelerationStructure bottomLevelAS; + AccelerationStructure topLevelAS; + + std::vector shaderGroups{}; + struct ShaderBindingTables { + ShaderBindingTable raygen; + ShaderBindingTable miss; + ShaderBindingTable hit; + } shaderBindingTables; + + struct UniformData { + glm::mat4 viewInverse; + glm::mat4 projInverse; + glm::vec4 lightPos; + } uniformData; + vks::Buffer ubo; + + VkPipeline pipeline; + VkPipelineLayout pipelineLayout; + VkDescriptorSet descriptorSet; + VkDescriptorSetLayout descriptorSetLayout; + + struct Sphere { + glm::vec3 center; + float radius; + glm::vec4 color; + }; + + struct AABB { + glm::vec3 min; + glm::vec3 max; + }; + + vks::Buffer spheresBuffer; + vks::Buffer aabbsBuffer; + uint32_t aabbCount{ 0 }; + + // This sample is derived from an extended base class that saves most of the ray tracing setup boiler plate + VulkanExample() : VulkanRaytracingSample() + { + title = "Ray tracing intersection shaders"; + timerSpeed *= 0.25f; + camera.type = Camera::CameraType::lookat; + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f); + camera.setRotation(glm::vec3(0.0f, 0.0f, 0.0f)); + camera.setTranslation(glm::vec3(0.0f, 0.0f, -10.0f)); + enableExtensions(); + } + + ~VulkanExample() + { + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + deleteStorageImage(); + deleteAccelerationStructure(bottomLevelAS); + deleteAccelerationStructure(topLevelAS); + shaderBindingTables.raygen.destroy(); + shaderBindingTables.miss.destroy(); + shaderBindingTables.hit.destroy(); + ubo.destroy(); + } + + void createBuffers() + { + // We'll be using two buffers to describe the procedural geometry + + // A buffer with sphere descriptions (center, radius, material) that'll be passed to the ray tracing shaders as a shader storage buffer object + std::vector spheres{}; + spheres.push_back({ glm::vec3(0.0f), 2.5f, glm::vec4(1.0f, 0.0f, 0.0f, 1.0f) }); + spheres.push_back({ glm::vec3(2.0f), 1.5f, glm::vec4(1.0f, 1.0f, 0.0f, 1.0f) }); + + // A buffer with the (axis aligned) bounding boxes of our sphere, which is used during the ray tracing traversal for hit detection + std::vector aabbs{}; + for (auto& sphere : spheres) { + aabbs.push_back({ sphere.center - glm::vec3(sphere.radius), sphere.center + glm::vec3(sphere.radius) }); + } + aabbCount = static_cast(aabbs.size()); + + // Copy the buffer to the device for performance reasons + vks::Buffer stagingBuffer{}; + VkBufferUsageFlags usageFlags = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + + // Spheres + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, sizeof(Sphere)* spheres.size(), spheres.data())); + VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &spheresBuffer, sizeof(Sphere)* spheres.size())); + vulkanDevice->copyBuffer(&stagingBuffer, &spheresBuffer, queue); + stagingBuffer.destroy(); + + // AABBs + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, sizeof(AABB)* aabbs.size(), aabbs.data())); + VK_CHECK_RESULT(vulkanDevice->createBuffer(usageFlags, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &aabbsBuffer, sizeof(AABB)* aabbs.size())); + vulkanDevice->copyBuffer(&stagingBuffer, &aabbsBuffer, queue); + stagingBuffer.destroy(); + } + + /* + Create the bottom level acceleration structure only containing axis aligned bounding boxes for our procedural geometry + */ + void createBottomLevelAccelerationStructure() + { + // Build + VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR(); + accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; + // Instead of providing actual geometry (e.g. triangles), we only provide the axis aligned bounding boxes (AABBs) of the spheres + // The data for the actual spheres is passed elsewhere as a shader storage buffer object + accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_AABBS_KHR; + accelerationStructureGeometry.geometry.aabbs.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_AABBS_DATA_KHR; + accelerationStructureGeometry.geometry.aabbs.data.deviceAddress = getBufferDeviceAddress(aabbsBuffer.buffer); + accelerationStructureGeometry.geometry.aabbs.stride = sizeof(AABB); + + // Get size info + VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); + accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + accelerationStructureBuildGeometryInfo.geometryCount = 1; + accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; + + VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR(); + vkGetAccelerationStructureBuildSizesKHR( + device, + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &accelerationStructureBuildGeometryInfo, + &aabbCount, + &accelerationStructureBuildSizesInfo); + + createAccelerationStructure(bottomLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR, accelerationStructureBuildSizesInfo); + + // Create a small scratch buffer used during build of the bottom level acceleration structure + ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize); + + VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); + accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + accelerationBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle; + accelerationBuildGeometryInfo.geometryCount = 1; + accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; + accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress; + + VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{}; + accelerationStructureBuildRangeInfo.primitiveCount = aabbCount; + std::vector accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo }; + + // Build the acceleration structure on the device via a one-time command buffer submission + // Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds + VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vkCmdBuildAccelerationStructuresKHR( + commandBuffer, + 1, + &accelerationBuildGeometryInfo, + accelerationBuildStructureRangeInfos.data()); + vulkanDevice->flushCommandBuffer(commandBuffer, queue); + + deleteScratchBuffer(scratchBuffer); + } + + /* + The top level acceleration structure contains the scene's object instances + */ + void createTopLevelAccelerationStructure() + { + VkTransformMatrixKHR transformMatrix = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f }; + + VkAccelerationStructureInstanceKHR instance{}; + instance.transform = transformMatrix; + instance.instanceCustomIndex = 0; + instance.mask = 0xFF; + instance.instanceShaderBindingTableRecordOffset = 0; + instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + instance.accelerationStructureReference = bottomLevelAS.deviceAddress; + + // Buffer for instance data + vks::Buffer instancesBuffer; + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &instancesBuffer, + sizeof(VkAccelerationStructureInstanceKHR), + &instance)); + + VkDeviceOrHostAddressConstKHR instanceDataDeviceAddress{}; + instanceDataDeviceAddress.deviceAddress = getBufferDeviceAddress(instancesBuffer.buffer); + + VkAccelerationStructureGeometryKHR accelerationStructureGeometry = vks::initializers::accelerationStructureGeometryKHR(); + accelerationStructureGeometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; + accelerationStructureGeometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; + accelerationStructureGeometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR; + accelerationStructureGeometry.geometry.instances.arrayOfPointers = VK_FALSE; + accelerationStructureGeometry.geometry.instances.data = instanceDataDeviceAddress; + + // Get size info + VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); + accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + accelerationStructureBuildGeometryInfo.geometryCount = 1; + accelerationStructureBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; + + uint32_t primitive_count = 1; + + VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo = vks::initializers::accelerationStructureBuildSizesInfoKHR(); + vkGetAccelerationStructureBuildSizesKHR( + device, + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &accelerationStructureBuildGeometryInfo, + &primitive_count, + &accelerationStructureBuildSizesInfo); + + createAccelerationStructure(topLevelAS, VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR, accelerationStructureBuildSizesInfo); + + // Create a small scratch buffer used during build of the top level acceleration structure + ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize); + + VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo = vks::initializers::accelerationStructureBuildGeometryInfoKHR(); + accelerationBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + accelerationBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + accelerationBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + accelerationBuildGeometryInfo.dstAccelerationStructure = topLevelAS.handle; + accelerationBuildGeometryInfo.geometryCount = 1; + accelerationBuildGeometryInfo.pGeometries = &accelerationStructureGeometry; + accelerationBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress; + + VkAccelerationStructureBuildRangeInfoKHR accelerationStructureBuildRangeInfo{}; + accelerationStructureBuildRangeInfo.primitiveCount = 1; + accelerationStructureBuildRangeInfo.primitiveOffset = 0; + accelerationStructureBuildRangeInfo.firstVertex = 0; + accelerationStructureBuildRangeInfo.transformOffset = 0; + std::vector accelerationBuildStructureRangeInfos = { &accelerationStructureBuildRangeInfo }; + + // Build the acceleration structure on the device via a one-time command buffer submission + // Some implementations may support acceleration structure building on the host (VkPhysicalDeviceAccelerationStructureFeaturesKHR->accelerationStructureHostCommands), but we prefer device builds + VkCommandBuffer commandBuffer = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vkCmdBuildAccelerationStructuresKHR( + commandBuffer, + 1, + &accelerationBuildGeometryInfo, + accelerationBuildStructureRangeInfos.data()); + vulkanDevice->flushCommandBuffer(commandBuffer, queue); + + deleteScratchBuffer(scratchBuffer); + instancesBuffer.destroy(); + } + + + /* + Create the Shader Binding Tables that binds the programs and top-level acceleration structure + + SBT Layout used in this sample: + + /-----------\ + | raygen | + |-----------| + | miss | + |-----------| + | hit + int | + \-----------/ + + */ + void createShaderBindingTables() { + const uint32_t handleSize = rayTracingPipelineProperties.shaderGroupHandleSize; + const uint32_t handleSizeAligned = vks::tools::alignedSize(rayTracingPipelineProperties.shaderGroupHandleSize, rayTracingPipelineProperties.shaderGroupHandleAlignment); + const uint32_t groupCount = static_cast(shaderGroups.size()); + const uint32_t sbtSize = groupCount * handleSizeAligned; + + std::vector shaderHandleStorage(sbtSize); + VK_CHECK_RESULT(vkGetRayTracingShaderGroupHandlesKHR(device, pipeline, 0, groupCount, sbtSize, shaderHandleStorage.data())); + + createShaderBindingTable(shaderBindingTables.raygen, 1); + createShaderBindingTable(shaderBindingTables.miss, 1); + createShaderBindingTable(shaderBindingTables.hit, 1); + + // Copy handles + memcpy(shaderBindingTables.raygen.mapped, shaderHandleStorage.data(), handleSize); + memcpy(shaderBindingTables.miss.mapped, shaderHandleStorage.data() + handleSizeAligned, handleSize); + memcpy(shaderBindingTables.hit.mapped, shaderHandleStorage.data() + handleSizeAligned * 2, handleSize); + } + + /* + Create the descriptor sets used for the ray tracing dispatch + */ + void createDescriptorSets() + { + std::vector poolSizes = { + { VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1 }, + { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2 } + }; + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); + + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &descriptorSetAllocateInfo, &descriptorSet)); + + VkWriteDescriptorSetAccelerationStructureKHR descriptorAccelerationStructureInfo = vks::initializers::writeDescriptorSetAccelerationStructureKHR(); + descriptorAccelerationStructureInfo.accelerationStructureCount = 1; + descriptorAccelerationStructureInfo.pAccelerationStructures = &topLevelAS.handle; + + VkWriteDescriptorSet accelerationStructureWrite{}; + accelerationStructureWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + // The specialized acceleration structure descriptor has to be chained + accelerationStructureWrite.pNext = &descriptorAccelerationStructureInfo; + accelerationStructureWrite.dstSet = descriptorSet; + accelerationStructureWrite.dstBinding = 0; + accelerationStructureWrite.descriptorCount = 1; + accelerationStructureWrite.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + + // We pass the sphere descriptions as shader storage buffer, so the ray tracing shaders can source properties from it + VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL }; + VkDescriptorBufferInfo spheresBufferDescriptor{ spheresBuffer.buffer, 0, VK_WHOLE_SIZE }; + + std::vector writeDescriptorSets = { + // Binding 0: Top level acceleration structure + accelerationStructureWrite, + // Binding 1: Ray tracing result image + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor), + // Binding 2: Uniform data + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &ubo.descriptor), + // Binding 3: Spheres buffer + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &spheresBufferDescriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); + } + + /* + Create our ray tracing pipeline + */ + void createRayTracingPipeline() + { + std::vector setLayoutBindings = { + // Binding 0: Acceleration structure + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR, 0), + // Binding 1: Storage image + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_RAYGEN_BIT_KHR, 1), + // Binding 2: Uniform buffer + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR, 2), + // Binding 3: Spheres buffer + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_INTERSECTION_BIT_KHR, 3), + }; + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout)); + + VkPipelineLayoutCreateInfo pPipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCI, nullptr, &pipelineLayout)); + + /* + Setup ray tracing shader groups + */ + std::vector shaderStages; + + // Ray generation group + { + shaderStages.push_back(loadShader(getShadersPath() + "raytracingintersection/raygen.rgen.spv", VK_SHADER_STAGE_RAYGEN_BIT_KHR)); + VkRayTracingShaderGroupCreateInfoKHR shaderGroup{}; + shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; + shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + shaderGroup.generalShader = static_cast(shaderStages.size()) - 1; + shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR; + shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR; + shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; + shaderGroups.push_back(shaderGroup); + } + + // Miss group + { + shaderStages.push_back(loadShader(getShadersPath() + "raytracingintersection/miss.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR)); + VkRayTracingShaderGroupCreateInfoKHR shaderGroup{}; + shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; + shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + shaderGroup.generalShader = static_cast(shaderStages.size()) - 1; + shaderGroup.closestHitShader = VK_SHADER_UNUSED_KHR; + shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR; + shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; + shaderGroups.push_back(shaderGroup); + } + + // Closest hit group (procedural) + { + shaderStages.push_back(loadShader(getShadersPath() + "raytracingintersection/closesthit.rchit.spv", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR)); + VkRayTracingShaderGroupCreateInfoKHR shaderGroup{}; + shaderGroup.sType = VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR; + shaderGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR; + shaderGroup.generalShader = VK_SHADER_UNUSED_KHR; + shaderGroup.closestHitShader = static_cast(shaderStages.size()) - 1; + shaderGroup.anyHitShader = VK_SHADER_UNUSED_KHR; + // This group als uses an intersection shader for proedural geometry (see interseciton.rint for details) + shaderStages.push_back(loadShader(getShadersPath() + "raytracingintersection/intersection.rint.spv", VK_SHADER_STAGE_INTERSECTION_BIT_KHR)); + shaderGroup.intersectionShader = static_cast(shaderStages.size()) - 1; + shaderGroups.push_back(shaderGroup); + } + + VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI = vks::initializers::rayTracingPipelineCreateInfoKHR(); + rayTracingPipelineCI.stageCount = static_cast(shaderStages.size()); + rayTracingPipelineCI.pStages = shaderStages.data(); + rayTracingPipelineCI.groupCount = static_cast(shaderGroups.size()); + rayTracingPipelineCI.pGroups = shaderGroups.data(); + rayTracingPipelineCI.maxPipelineRayRecursionDepth = 2; + rayTracingPipelineCI.layout = pipelineLayout; + VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline)); + } + + /* + Create the uniform buffer used to pass matrices to the ray tracing ray generation shader + */ + void createUniformBuffer() + { + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &ubo, + sizeof(uniformData), + &uniformData)); + VK_CHECK_RESULT(ubo.map()); + + updateUniformBuffers(); + } + + /* + If the window has been resized, we need to recreate the storage image and it's descriptor + */ + void handleResize() + { + // Recreate image + createStorageImage(swapChain.colorFormat, { width, height, 1 }); + // Update descriptor + VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL }; + VkWriteDescriptorSet resultImageWrite = vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &storageImageDescriptor); + vkUpdateDescriptorSets(device, 1, &resultImageWrite, 0, VK_NULL_HANDLE); + resized = false; + } + + /* + Command buffer generation + */ + void buildCommandBuffers() + { + if (resized) + { + handleResize(); + } + + VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); + + VkImageSubresourceRange subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + for (int32_t i = 0; i < drawCmdBuffers.size(); ++i) + { + VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); + + /* + Dispatch the ray tracing commands + */ + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline); + vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipelineLayout, 0, 1, &descriptorSet, 0, 0); + + VkStridedDeviceAddressRegionKHR emptySbtEntry = {}; + vkCmdTraceRaysKHR( + drawCmdBuffers[i], + &shaderBindingTables.raygen.stridedDeviceAddressRegion, + &shaderBindingTables.miss.stridedDeviceAddressRegion, + &shaderBindingTables.hit.stridedDeviceAddressRegion, + &emptySbtEntry, + width, + height, + 1); + + /* + Copy ray tracing output to swap chain image + */ + + // Prepare current swap chain image as transfer destination + vks::tools::setImageLayout( + drawCmdBuffers[i], + swapChain.images[i], + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + subresourceRange); + + // Prepare ray tracing output image as transfer source + vks::tools::setImageLayout( + drawCmdBuffers[i], + storageImage.image, + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + subresourceRange); + + VkImageCopy copyRegion{}; + copyRegion.srcSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.srcOffset = { 0, 0, 0 }; + copyRegion.dstSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + copyRegion.dstOffset = { 0, 0, 0 }; + copyRegion.extent = { width, height, 1 }; + vkCmdCopyImage(drawCmdBuffers[i], storageImage.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, swapChain.images[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©Region); + + // Transition swap chain image back for presentation + vks::tools::setImageLayout( + drawCmdBuffers[i], + swapChain.images[i], + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + subresourceRange); + + // Transition ray tracing output image back to general layout + vks::tools::setImageLayout( + drawCmdBuffers[i], + storageImage.image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_GENERAL, + subresourceRange); + + drawUI(drawCmdBuffers[i], frameBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void updateUniformBuffers() + { + uniformData.projInverse = glm::inverse(camera.matrices.perspective); + uniformData.viewInverse = glm::inverse(camera.matrices.view); + uniformData.lightPos = glm::vec4(cos(glm::radians(timer * 360.0f)) * 40.0f, -50.0f + sin(glm::radians(timer * 360.0f)) * 20.0f, 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f, 0.0f); + // Pass the vertex size to the shader for unpacking vertices + //uniformData.vertexSize = sizeof(vkglTF::Vertex); + memcpy(ubo.mapped, &uniformData, sizeof(uniformData)); + } + + void getEnabledFeatures() + { + // Enable features required for ray tracing using feature chaining via pNext + enabledBufferDeviceAddresFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; + enabledBufferDeviceAddresFeatures.bufferDeviceAddress = VK_TRUE; + + enabledRayTracingPipelineFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR; + enabledRayTracingPipelineFeatures.rayTracingPipeline = VK_TRUE; + enabledRayTracingPipelineFeatures.pNext = &enabledBufferDeviceAddresFeatures; + + enabledAccelerationStructureFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR; + enabledAccelerationStructureFeatures.accelerationStructure = VK_TRUE; + enabledAccelerationStructureFeatures.pNext = &enabledRayTracingPipelineFeatures; + + deviceCreatepNextChain = &enabledAccelerationStructureFeatures; + } + + void prepare() + { + VulkanRaytracingSample::prepare(); + + createBuffers(); + + // Create the acceleration structures used to render the ray traced scene + createBottomLevelAccelerationStructure(); + createTopLevelAccelerationStructure(); + + createStorageImage(swapChain.colorFormat, { width, height, 1 }); + createUniformBuffer(); + createRayTracingPipeline(); + createShaderBindingTables(); + createDescriptorSets(); + buildCommandBuffers(); + prepared = true; + } + + void draw() + { + VulkanExampleBase::prepareFrame(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + VulkanExampleBase::submitFrame(); + } + + virtual void render() + { + if (!prepared) + return; + draw(); + if (!paused || camera.updated) + updateUniformBuffers(); + } +}; + +VULKAN_EXAMPLE_MAIN() diff --git a/shaders/glsl/raytracingintersection/closesthit.rchit b/shaders/glsl/raytracingintersection/closesthit.rchit new file mode 100644 index 00000000..59cff6bb --- /dev/null +++ b/shaders/glsl/raytracingintersection/closesthit.rchit @@ -0,0 +1,35 @@ +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_EXT_nonuniform_qualifier : enable + +layout(location = 0) rayPayloadInEXT vec3 hitValue; +layout(location = 2) rayPayloadEXT bool shadowed; +hitAttributeEXT vec2 attribs; + +layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; +layout(binding = 2, set = 0) uniform UBO +{ + mat4 viewInverse; + mat4 projInverse; + vec4 lightPos; +} ubo; + +struct Sphere { + vec3 center; + float radius; + vec4 color; +}; +layout(binding = 3, set = 0) buffer Spheres { Sphere s[]; } spheres; + +void main() +{ + Sphere sphere = spheres.s[gl_PrimitiveID]; + + vec3 worldPos = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; + vec3 worldNrm = normalize(worldPos - sphere.center); + + // Basic lighting + vec3 lightVector = normalize(ubo.lightPos.xyz); + float dot_product = max(dot(lightVector, worldNrm), 0.2); + hitValue = sphere.color.rgb * dot_product; +} \ No newline at end of file diff --git a/shaders/glsl/raytracingintersection/closesthit.rchit.spv b/shaders/glsl/raytracingintersection/closesthit.rchit.spv new file mode 100644 index 0000000000000000000000000000000000000000..b956e8a65297fad9bbc0d3e565ffe5d9e4b93672 GIT binary patch literal 2600 zcmZ9NU2_yg6oz}VJ4=KZ1j2_O!~_&nFrosYKu9pS8?z=Ug3-IJVRuN{+U&&H*`&Bq z%X`ZUf08#+=nsI>-{Fl_zE4kgnA%Pqy5IMlK7GD+sE(aKWNH<2#EhFYQ>>C@6KJlc7>ahrs^D^JF36mr;MrT zyc63#idQ6}%vs5sl5-L+-jOsVE0R@7RexjJpUP29aWz`)d*422_tXBcZy%-EPW$^| z+_R5tl1X0=`f`XSlBWDN_$B$=*xgRD#OPM!uqe|>`ng;kaKxX*T{|4S;E1P__EMQS z-$%3$yf1uD2$y|>S!LgFqrTxreNT!jd<_&+O?54n!f|g?$KvgQaNX5(tJhx7?2gUt zUb4J|U1VOD-F}+&y6Y)d#f-b!%k~@S`|;zo%x>AfsvGq5rKDf7S<=aE>P+xS&!lhK z{DHd#cR`$cXEoa?{6yxm4n4cIop0*CsuroIbK<_fyQYZi?u_l8P4<`jdr3A(4tQ#` zo2B24^3=)sJjkc&=4bdYmH)`xkiMJdZQbn-JGtYSt!xgKMGo`19gN4#+KGH}ESN3R;{;mg|Vk{Jm$ zp{K!DL-m2<9`@X$)~tYMb*_5M?}!;Z zPnqKl;tBW2w|p<;zu;^NUvMw(6YhOuj+CI-&=oXPuB0cC+$H zeK?=eDb)M4@+B59dhoc%d?Tin4c~@fgKkDPhdqZ58~R(eIV*4U6@FUhN$p_n9`!uj zO^>M|wkL)4d6ix9J-FoYabXtsPiU`5u;V_Mp2ML(r5#@g_%-dshJfGDjxCt`U}gj^ zU}gl2{j5JzBlKYUf&Pr}``W2XsMnm%aDm6i2g1}I9X`PH5iaETp~u*Nrc|}P~U~KU{ zEn#i~p3#}P0aG)+7w06uC^PKelFUfpfAZYhI^zc&Z;&Tk@Oe>ZxJB7hhj%2zLEORS zUC;mQeZD8mJ@^Sl!rPZ570EpbdfuXj@c3rlK9(?(%pty+m8%l?H6i?5^Bg=2pY$@k zbKP^igDvmS8~70Kr#iz0{F%^z)$E^xZ4umB7gMEKECNQI&xTWdr_Zq m4tG5ta`;>r4q=5Hh=;e9B=`+|BNy(#^CrJ5xc_wXhU9;+9n@a{ literal 0 HcmV?d00001 diff --git a/shaders/glsl/raytracingintersection/miss.rmiss b/shaders/glsl/raytracingintersection/miss.rmiss new file mode 100644 index 00000000..577f7e8e --- /dev/null +++ b/shaders/glsl/raytracingintersection/miss.rmiss @@ -0,0 +1,9 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 0) rayPayloadInEXT vec3 hitValue; + +void main() +{ + hitValue = vec3(0.0, 0.0, 0.2); +} \ No newline at end of file diff --git a/shaders/glsl/raytracingintersection/miss.rmiss.spv b/shaders/glsl/raytracingintersection/miss.rmiss.spv new file mode 100644 index 0000000000000000000000000000000000000000..88ae284c9b1aec2ebc262061a53d77544180f5c5 GIT binary patch literal 368 zcmY*VO-lk%6g|#&P8wq5R%uyCi;^I47Zdarks|Gehe{3TET(bcs$bVm|3bgRP0%@$ zf!)X9-gED{XD;J9=OMfrVnk@*H(GG;(E{oSFnXG#j}OmjVb^I{*lCu(QX8N~X<|mE z|6b1e*Ed(%Y{IP!G&`Z@^Azw{FKw1|!-%i8fv2)2kd&)n}{&_PRue4k5F+uIiFU%wN1o{kuJvuX-2c($)#ZACFmYH)eRr hsrHTuX#~v%yiIrLUWLB3W5;J6lF45?-~3~@#1AV2Bo6=p literal 0 HcmV?d00001 diff --git a/shaders/glsl/raytracingintersection/raygen.rgen b/shaders/glsl/raytracingintersection/raygen.rgen new file mode 100644 index 00000000..88b5f3fe --- /dev/null +++ b/shaders/glsl/raytracingintersection/raygen.rgen @@ -0,0 +1,33 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; +layout(binding = 1, set = 0, rgba8) uniform image2D image; +layout(binding = 2, set = 0) uniform CameraProperties +{ + mat4 viewInverse; + mat4 projInverse; + vec4 lightPos; +} cam; + +layout(location = 0) rayPayloadEXT vec3 hitValue; + +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; + + 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 / target.w), 0) ; + + uint rayFlags = gl_RayFlagsOpaqueEXT; + uint cullMask = 0xff; + float tmin = 0.001; + float tmax = 10000.0; + + traceRayEXT(topLevelAS, rayFlags, cullMask, 0, 0, 0, origin.xyz, tmin, direction.xyz, tmax, 0); + + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 0.0)); +} diff --git a/shaders/glsl/raytracingintersection/raygen.rgen.spv b/shaders/glsl/raytracingintersection/raygen.rgen.spv new file mode 100644 index 0000000000000000000000000000000000000000..012d3599b5a5d9aeb947447744958c7018377518 GIT binary patch literal 3080 zcmZ9N+j10D6oz*uGZPE}3}+N^!cip#ML+~02_cz81~cSjL~&>*&7@&Yb~+I)tHk2Q zE314Yuk^wvu*&!F#?t@m*$dm*)Stc9zmC23?9E{A)R4;$xZQ4-+j6}%>T>S;QRjwT z(JfckYIo+BYH{#QEs2AA)NaVP=mz9kt}It3wvvsBOPAjva<3aq6JXmXuU`&2mzUfM zqBe#-!V%$3;l2sy@)wt6Q<0J~xu@zMt8*3*Dzg}qXgz;7=w#PG*Zrpj?&-2~QsIi$;J82&3 zGoO0UO8dSjISJxMm{7mqCZ%sgaad2H4nu{zr(NeW*9;myN5L(Lt8X`(cZ01b66KY5 z8ih+*y|X}P7)dL5+OrqZGf6t#O1Kj?rawV=6;nC9WkqE^s=C@T-=x?A{F z=Z(%8qyHv-Ub@YtAZ}h6Og|-^mF0${znpTtIOdj(Lnm%ZusFE?;(Ga+D}K*}vSvz6 zIQnD0Ck2-2W0K9zZ!>F0$Iil^@6X!NvD3!^Wg*X)YH}Np4@)X)F9}B!Pp|0MO44^} zhXaSC52w8TXyMN+;mC*oh<0khU-vp^0Ehox?f8@bymV^q7qlkFHBIUq5a592;M~Yz zF()KzO_^Rw)n}8`V7BDtPSVq9?c{|&D*ddNv7hi~4re?uZ%C)da{@h}b2f0y;VtbY z!FobJFR+Lo*N!hat;Y+JS>x)4UU-<`3?<3gd%i6_qNx>D{RuM@i}boM{Lh8_JA!A%{X`}1JVtxF`&r*K;6%m|&| zBMbY6_CIvb=m$GG_Y&@yUdTt1$xYwc#oRa>ax}aFUvi-Hn`ZH?P3_E!m@7Ib$R}pO$>4Prulw^X8d9bLG9u>Ps1&b6e@tdFSxIq5ilt z`vmUGqFC+ zcj|3{d&pg*)>-Wn0{qYN;a