diff --git a/base/VulkanglTFModel.cpp b/base/VulkanglTFModel.cpp index 9d4af8cf..4505fb0f 100644 --- a/base/VulkanglTFModel.cpp +++ b/base/VulkanglTFModel.cpp @@ -1,4 +1,3 @@ - /* * Vulkan glTF model and texture loading class based on tinyglTF (https://github.com/syoyo/tinygltf) * @@ -987,6 +986,7 @@ void vkglTF::Model::loadImages(tinygltf::Model &gltfModel, vks::VulkanDevice *de for (tinygltf::Image &image : gltfModel.images) { vkglTF::Texture texture; texture.fromglTfImage(image, path, device, transferQueue); + texture.index = static_cast(textures.size()); textures.push_back(texture); } // Create an empty texture to be used for empty material images diff --git a/base/VulkanglTFModel.h b/base/VulkanglTFModel.h index dfcbfad7..76a156c1 100644 --- a/base/VulkanglTFModel.h +++ b/base/VulkanglTFModel.h @@ -63,6 +63,7 @@ namespace vkglTF uint32_t layerCount; VkDescriptorImageInfo descriptor; VkSampler sampler; + uint32_t index; void updateDescriptor(); void destroy(); void fromglTfImage(tinygltf::Image& gltfimage, std::string path, vks::VulkanDevice* device, VkQueue copyQueue); diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8e7ef554..947ba9d2 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -134,6 +134,7 @@ set(EXAMPLES rayquery raytracingbasic raytracingcallable + raytracinggltf raytracingintersection raytracingreflections raytracingsbtdata diff --git a/examples/raytracinggltf/raytracinggltf.cpp b/examples/raytracinggltf/raytracinggltf.cpp new file mode 100644 index 00000000..9cac21c9 --- /dev/null +++ b/examples/raytracinggltf/raytracinggltf.cpp @@ -0,0 +1,790 @@ +/* + * Vulkan Example - Rendering a glTF model using hardware accelerated ray tracing example /for proper transparency, this sample does frame accumulation) + * + * Copyright (C) 2023 by Sascha Willems - www.saschawillems.de + * + * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) + */ + +/* + * @todo + */ + +#include "VulkanRaytracingSample.h" +#define VK_GLTF_MATERIAL_IDS +#include "VulkanglTFModel.h" + +class VulkanExample : public VulkanRaytracingSample +{ +public: + AccelerationStructure bottomLevelAS{}; + AccelerationStructure topLevelAS{}; + + vks::Buffer vertexBuffer; + vks::Buffer indexBuffer; + uint32_t indexCount; + vks::Buffer transformBuffer; + + struct GeometryNode { + uint64_t vertexBufferDeviceAddress; + uint64_t indexBufferDeviceAddress; + int32_t textureIndexBaseColor; + int32_t textureIndexOcclusion; + }; + vks::Buffer geometryNodesBuffer; + + std::vector shaderGroups{}; + struct ShaderBindingTables { + ShaderBindingTable raygen; + ShaderBindingTable miss; + ShaderBindingTable hit; + } shaderBindingTables; + + vks::Texture2D texture; + + struct UniformData { + glm::mat4 viewInverse; + glm::mat4 projInverse; + uint32_t frame{ 0 }; + } uniformData; + vks::Buffer ubo; + + VkPipeline pipeline; + VkPipelineLayout pipelineLayout; + VkDescriptorSet descriptorSet; + VkDescriptorSetLayout descriptorSetLayout; + + vkglTF::Model model; + + VkPhysicalDeviceDescriptorIndexingFeaturesEXT physicalDeviceDescriptorIndexingFeatures{}; + + VulkanExample() : VulkanRaytracingSample() + { + title = "Ray tracing glTF model"; + settings.overlay = false; + camera.type = Camera::CameraType::lookat; + //camera.type = Camera::CameraType::firstperson; + 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.1f, -1.0f)); + + enableExtensions(); + + // Buffer device address requires the 64-bit integer feature to be enabled + enabledFeatures.shaderInt64 = VK_TRUE; + + enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE3_EXTENSION_NAME); + enabledDeviceExtensions.push_back(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); + physicalDeviceDescriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT; + physicalDeviceDescriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = VK_TRUE; + physicalDeviceDescriptorIndexingFeatures.runtimeDescriptorArray = VK_TRUE; + physicalDeviceDescriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = VK_TRUE; + + deviceCreatepNextChain = &physicalDeviceDescriptorIndexingFeatures; + } + + ~VulkanExample() + { + vkDestroyPipeline(device, pipeline, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + deleteStorageImage(); + deleteAccelerationStructure(bottomLevelAS); + deleteAccelerationStructure(topLevelAS); + vertexBuffer.destroy(); + indexBuffer.destroy(); + transformBuffer.destroy(); + shaderBindingTables.raygen.destroy(); + shaderBindingTables.miss.destroy(); + shaderBindingTables.hit.destroy(); + ubo.destroy(); + } + + void createAccelerationStructureBuffer(AccelerationStructure &accelerationStructure, VkAccelerationStructureBuildSizesInfoKHR buildSizeInfo) + { + VkBufferCreateInfo bufferCreateInfo{}; + bufferCreateInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + bufferCreateInfo.size = buildSizeInfo.accelerationStructureSize; + bufferCreateInfo.usage = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &accelerationStructure.buffer)); + VkMemoryRequirements memoryRequirements{}; + vkGetBufferMemoryRequirements(device, accelerationStructure.buffer, &memoryRequirements); + VkMemoryAllocateFlagsInfo memoryAllocateFlagsInfo{}; + memoryAllocateFlagsInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + memoryAllocateFlagsInfo.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT_KHR; + VkMemoryAllocateInfo memoryAllocateInfo{}; + memoryAllocateInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + memoryAllocateInfo.pNext = &memoryAllocateFlagsInfo; + memoryAllocateInfo.allocationSize = memoryRequirements.size; + memoryAllocateInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memoryAllocateInfo, nullptr, &accelerationStructure.memory)); + VK_CHECK_RESULT(vkBindBufferMemory(device, accelerationStructure.buffer, accelerationStructure.memory, 0)); + } + + /* + Create the bottom level acceleration structure that contains the scene's actual geometry (vertices, triangles) + */ + void createBottomLevelAccelerationStructure() + { + // Use transform matrices from the glTF nodes + std::vector transformMatrices{}; + for (auto node : model.linearNodes) { + if (node->mesh) { + for (auto primitive : node->mesh->primitives) { + if (primitive->indexCount > 0) { + VkTransformMatrixKHR transformMatrix{}; + auto m = glm::mat3x4(glm::transpose(node->getMatrix())); + memcpy(&transformMatrix, (void*)&m, sizeof(glm::mat3x4)); + transformMatrices.push_back(transformMatrix); + } + } + } + } + + // Transform buffer + 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, + &transformBuffer, + static_cast(transformMatrices.size()) * sizeof(VkTransformMatrixKHR), + transformMatrices.data())); + + // Build + // One geometry per glTF node, so we can index materials using gl_GeometryIndexEXT + uint32_t maxPrimCount{ 0 }; + std::vector maxPrimitiveCounts{}; + std::vector geometries{}; + std::vector buildRangeInfos{}; + std::vector pBuildRangeInfos{}; + std::vector geometryNodes{}; + for (auto node : model.linearNodes) { + if (node->mesh) { + for (auto primitive : node->mesh->primitives) { + if (primitive->indexCount > 0) { + VkDeviceOrHostAddressConstKHR vertexBufferDeviceAddress{}; + VkDeviceOrHostAddressConstKHR indexBufferDeviceAddress{}; + VkDeviceOrHostAddressConstKHR transformBufferDeviceAddress{}; + + vertexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(model.vertices.buffer);// +primitive->firstVertex * sizeof(vkglTF::Vertex); + indexBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(model.indices.buffer) + primitive->firstIndex * sizeof(uint32_t); + transformBufferDeviceAddress.deviceAddress = getBufferDeviceAddress(transformBuffer.buffer) + static_cast(geometryNodes.size()) * sizeof(VkTransformMatrixKHR); + + VkAccelerationStructureGeometryKHR geometry{}; + geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + geometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; + geometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; + geometry.geometry.triangles.vertexData = vertexBufferDeviceAddress; + geometry.geometry.triangles.maxVertex = model.vertices.count; + //geometry.geometry.triangles.maxVertex = primitive->vertexCount; + geometry.geometry.triangles.vertexStride = sizeof(vkglTF::Vertex); + geometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32; + geometry.geometry.triangles.indexData = indexBufferDeviceAddress; + geometry.geometry.triangles.transformData = transformBufferDeviceAddress; + geometries.push_back(geometry); + maxPrimitiveCounts.push_back(primitive->indexCount / 3); + maxPrimCount += primitive->indexCount / 3; + + VkAccelerationStructureBuildRangeInfoKHR buildRangeInfo{}; + buildRangeInfo.firstVertex = 0; + buildRangeInfo.primitiveOffset = 0; // primitive->firstIndex * sizeof(uint32_t); + buildRangeInfo.primitiveCount = primitive->indexCount / 3; + buildRangeInfo.transformOffset = 0; + buildRangeInfos.push_back(buildRangeInfo); + + GeometryNode geometryNode{}; + geometryNode.vertexBufferDeviceAddress = vertexBufferDeviceAddress.deviceAddress; + geometryNode.indexBufferDeviceAddress = indexBufferDeviceAddress.deviceAddress; + geometryNode.textureIndexBaseColor = primitive->material.baseColorTexture->index; + geometryNode.textureIndexOcclusion = primitive->material.occlusionTexture ? primitive->material.occlusionTexture->index : -1; + // @todo: map material id to global texture array + geometryNodes.push_back(geometryNode); + } + } + } + } + for (auto& rangeInfo : buildRangeInfos) { + pBuildRangeInfos.push_back(&rangeInfo); + } + + // @todo: stage to device + VK_CHECK_RESULT(vulkanDevice->createBuffer( + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, + &geometryNodesBuffer, + static_cast(geometryNodes.size()) * sizeof(GeometryNode), + geometryNodes.data())); + + // Get size info + VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo{}; + accelerationStructureBuildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + accelerationStructureBuildGeometryInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + accelerationStructureBuildGeometryInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + accelerationStructureBuildGeometryInfo.geometryCount = geometries.size(); + accelerationStructureBuildGeometryInfo.pGeometries = geometries.data(); + + const uint32_t numTriangles = maxPrimitiveCounts[0]; + + VkAccelerationStructureBuildSizesInfoKHR accelerationStructureBuildSizesInfo{}; + accelerationStructureBuildSizesInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR; + vkGetAccelerationStructureBuildSizesKHR( + device, + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &accelerationStructureBuildGeometryInfo, + maxPrimitiveCounts.data(), + &accelerationStructureBuildSizesInfo); + + createAccelerationStructureBuffer(bottomLevelAS, accelerationStructureBuildSizesInfo); + + VkAccelerationStructureCreateInfoKHR accelerationStructureCreateInfo{}; + accelerationStructureCreateInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR; + accelerationStructureCreateInfo.buffer = bottomLevelAS.buffer; + accelerationStructureCreateInfo.size = accelerationStructureBuildSizesInfo.accelerationStructureSize; + accelerationStructureCreateInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + vkCreateAccelerationStructureKHR(device, &accelerationStructureCreateInfo, nullptr, &bottomLevelAS.handle); + + // Create a small scratch buffer used during build of the bottom level acceleration structure + ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize); + + accelerationStructureBuildGeometryInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + accelerationStructureBuildGeometryInfo.dstAccelerationStructure = bottomLevelAS.handle; + accelerationStructureBuildGeometryInfo.scratchData.deviceAddress = scratchBuffer.deviceAddress; + + const VkAccelerationStructureBuildRangeInfoKHR* buildOffsetInfo = buildRangeInfos.data(); + + // 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, + &accelerationStructureBuildGeometryInfo, + pBuildRangeInfos.data()); + vulkanDevice->flushCommandBuffer(commandBuffer, queue); + + VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{}; + accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR; + accelerationDeviceAddressInfo.accelerationStructure = bottomLevelAS.handle; + bottomLevelAS.deviceAddress = vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo); + + deleteScratchBuffer(scratchBuffer); + } + + /* + The top level acceleration structure contains the scene's object instances + */ + void createTopLevelAccelerationStructure() + { + // We flip the matrix [1][1] = -1.0f to accomodate for the glTF up vector + 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{}; + accelerationStructureGeometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR; + 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 + /* + The pSrcAccelerationStructure, dstAccelerationStructure, and mode members of pBuildInfo are ignored. Any VkDeviceOrHostAddressKHR members of pBuildInfo are ignored by this command, except that the hostAddress member of VkAccelerationStructureGeometryTrianglesDataKHR::transformData will be examined to check if it is NULL.* + */ + VkAccelerationStructureBuildGeometryInfoKHR accelerationStructureBuildGeometryInfo{}; + accelerationStructureBuildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + 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{}; + accelerationStructureBuildSizesInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR; + vkGetAccelerationStructureBuildSizesKHR( + device, + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &accelerationStructureBuildGeometryInfo, + &primitive_count, + &accelerationStructureBuildSizesInfo); + + createAccelerationStructureBuffer(topLevelAS, accelerationStructureBuildSizesInfo); + + VkAccelerationStructureCreateInfoKHR accelerationStructureCreateInfo{}; + accelerationStructureCreateInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR; + accelerationStructureCreateInfo.buffer = topLevelAS.buffer; + accelerationStructureCreateInfo.size = accelerationStructureBuildSizesInfo.accelerationStructureSize; + accelerationStructureCreateInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + vkCreateAccelerationStructureKHR(device, &accelerationStructureCreateInfo, nullptr, &topLevelAS.handle); + + // Create a small scratch buffer used during build of the top level acceleration structure + ScratchBuffer scratchBuffer = createScratchBuffer(accelerationStructureBuildSizesInfo.buildScratchSize); + + VkAccelerationStructureBuildGeometryInfoKHR accelerationBuildGeometryInfo{}; + accelerationBuildGeometryInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR; + 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); + + VkAccelerationStructureDeviceAddressInfoKHR accelerationDeviceAddressInfo{}; + accelerationDeviceAddressInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR; + accelerationDeviceAddressInfo.accelerationStructure = topLevelAS.handle; + topLevelAS.deviceAddress = vkGetAccelerationStructureDeviceAddressKHR(device, &accelerationDeviceAddressInfo); + + 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 + shadow | + |-----------| + | hit + any | + \-----------/ + + */ + 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, 2); + createShaderBindingTable(shaderBindingTables.hit, 1); + + // Copy handles + memcpy(shaderBindingTables.raygen.mapped, shaderHandleStorage.data(), handleSize); + // We are using two miss shaders, so we need to get two handles for the miss shader binding table + memcpy(shaderBindingTables.miss.mapped, shaderHandleStorage.data() + handleSizeAligned, handleSize * 2); + memcpy(shaderBindingTables.hit.mapped, shaderHandleStorage.data() + handleSizeAligned * 3, handleSize); + } + + /* + Create our ray tracing pipeline + */ + void createRayTracingPipeline() + { + // @todo: + uint32_t imageCount{ 0 }; + imageCount = static_cast(model.textures.size()); + + std::vector setLayoutBindings = { + // Binding 0: Top level 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: Ray tracing result 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, 2), + // Binding 3: Texture image + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 3), + // Binding 4: Geometry node information SSBO + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 4), + // Binding 5: All images used by the glTF model + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, 5, imageCount) + }; + + // Unbound set + VkDescriptorSetLayoutBindingFlagsCreateInfoEXT setLayoutBindingFlags{}; + setLayoutBindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT; + setLayoutBindingFlags.bindingCount = 6; + std::vector descriptorBindingFlags = { + 0, + 0, + 0, + 0, + 0, + 0, + VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT_EXT + }; + setLayoutBindingFlags.pBindingFlags = descriptorBindingFlags.data(); + + VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCI = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); + descriptorSetLayoutCI.pNext = &setLayoutBindingFlags; + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorSetLayoutCI, nullptr, &descriptorSetLayout)); + + VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayout)); + + /* + Setup ray tracing shader groups + */ + std::vector shaderStages; + + // Ray generation group + { + shaderStages.push_back(loadShader(getShadersPath() + "raytracinggltf/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() + "raytracinggltf/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); + // Second shader for shadows + shaderStages.push_back(loadShader(getShadersPath() + "raytracinggltf/shadow.rmiss.spv", VK_SHADER_STAGE_MISS_BIT_KHR)); + shaderGroup.generalShader = static_cast(shaderStages.size()) - 1; + shaderGroups.push_back(shaderGroup); + } + + // Closest hit group for doing texture lookups + { + shaderStages.push_back(loadShader(getShadersPath() + "raytracinggltf/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_TRIANGLES_HIT_GROUP_KHR; + shaderGroup.generalShader = VK_SHADER_UNUSED_KHR; + shaderGroup.closestHitShader = static_cast(shaderStages.size()) - 1; + shaderGroup.intersectionShader = VK_SHADER_UNUSED_KHR; + // This group also uses an anyhit shader for doing transparency (see anyhit.rahit for details) + shaderStages.push_back(loadShader(getShadersPath() + "raytracinggltf/anyhit.rahit.spv", VK_SHADER_STAGE_ANY_HIT_BIT_KHR)); + shaderGroup.anyHitShader = static_cast(shaderStages.size()) - 1; + shaderGroups.push_back(shaderGroup); + } + + /* + Create the ray tracing pipeline + */ + VkRayTracingPipelineCreateInfoKHR rayTracingPipelineCI{}; + rayTracingPipelineCI.sType = VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR; + rayTracingPipelineCI.stageCount = static_cast(shaderStages.size()); + rayTracingPipelineCI.pStages = shaderStages.data(); + rayTracingPipelineCI.groupCount = static_cast(shaderGroups.size()); + rayTracingPipelineCI.pGroups = shaderGroups.data(); + rayTracingPipelineCI.maxPipelineRayRecursionDepth = 1; + rayTracingPipelineCI.layout = pipelineLayout; + VK_CHECK_RESULT(vkCreateRayTracingPipelinesKHR(device, VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &rayTracingPipelineCI, nullptr, &pipeline)); + } + + /* + Create the descriptor sets used for the ray tracing dispatch + */ + void createDescriptorSets() + { + // @todo + uint32_t imageCount{ 0 }; + imageCount = static_cast(model.textures.size()); + + 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_COMBINED_IMAGE_SAMPLER, 1 } + }; + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); + + VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variableDescriptorCountAllocInfo{}; + uint32_t variableDescCounts[] = { imageCount }; + variableDescriptorCountAllocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT; + variableDescriptorCountAllocInfo.descriptorSetCount = 1; + variableDescriptorCountAllocInfo.pDescriptorCounts = variableDescCounts; + + VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + descriptorSetAllocateInfo.pNext = &variableDescriptorCountAllocInfo; + 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; + + VkDescriptorImageInfo storageImageDescriptor{ VK_NULL_HANDLE, storageImage.view, VK_IMAGE_LAYOUT_GENERAL }; + + 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 4: Geometry node information SSBO + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 4, &geometryNodesBuffer.descriptor), + }; + + // Image descriptors for the image array + std::vector textureDescriptors{}; + for (auto texture : model.textures) { + VkDescriptorImageInfo descriptor{}; + descriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + descriptor.sampler = texture.sampler;; + descriptor.imageView = texture.view; + textureDescriptors.push_back(descriptor); + } + + VkWriteDescriptorSet writeDescriptorImgArray{}; + writeDescriptorImgArray.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + writeDescriptorImgArray.dstBinding = 5; + writeDescriptorImgArray.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + writeDescriptorImgArray.descriptorCount = imageCount; + writeDescriptorImgArray.dstSet = descriptorSet; + writeDescriptorImgArray.pImageInfo = textureDescriptors.data(); + writeDescriptorSets.push_back(writeDescriptorImgArray); + + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, VK_NULL_HANDLE); + } + + /* + 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); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void updateUniformBuffers() + { + uniformData.projInverse = glm::inverse(camera.matrices.perspective); + uniformData.viewInverse = glm::inverse(camera.matrices.view); + uniformData.frame++; + 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; + + enabledFeatures.samplerAnisotropy = VK_TRUE; + } + + void loadAssets() + { + vkglTF::memoryPropertyFlags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; + model.loadFromFile(getAssetPath() + "models/FlightHelmet/glTF/FlightHelmet.gltf", vulkanDevice, queue); + } + + void prepare() + { + VulkanRaytracingSample::prepare(); + + loadAssets(); + + // 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; + updateUniformBuffers(); + draw(); + } + + virtual void viewChanged() + { + uniformData.frame = -1; + } +}; + +VULKAN_EXAMPLE_MAIN() diff --git a/examples/texturesparseresidency/texturesparseresidency.cpp b/examples/texturesparseresidency/texturesparseresidency.cpp index 9bc069d2..5602013c 100644 --- a/examples/texturesparseresidency/texturesparseresidency.cpp +++ b/examples/texturesparseresidency/texturesparseresidency.cpp @@ -7,7 +7,7 @@ */ /* -* Note : This sample is work-in-progress and works basically, but it's not yet finished +* Important note : This sample is work-in-progress and works basically, but it's not finished */ #include "texturesparseresidency.h" diff --git a/shaders/glsl/raytracinggltf/anyhit.rahit b/shaders/glsl/raytracinggltf/anyhit.rahit new file mode 100644 index 00000000..da54723c --- /dev/null +++ b/shaders/glsl/raytracinggltf/anyhit.rahit @@ -0,0 +1,49 @@ +/* Copyright (c) 2023, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ +#version 460 + +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_buffer_reference2 : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +layout(location = 0) rayPayloadInEXT vec3 hitValue; +layout(location = 3) rayPayloadInEXT uint payloadSeed; + +hitAttributeEXT vec2 attribs; + +layout(binding = 3, set = 0) uniform sampler2D image; + +struct GeometryNode { + uint64_t vertexBufferDeviceAddress; + uint64_t indexBufferDeviceAddress; + int textureIndexBaseColor; + int textureIndexOcclusion; +}; +layout(binding = 4, set = 0) buffer GeometryNodes { GeometryNode nodes[]; } geometryNodes; + +layout(binding = 5, set = 0) uniform sampler2D textures[]; + +#include "bufferreferences.glsl" +#include "geometrytypes.glsl" +#include "random.glsl" + +void main() +{ + Triangle tri = unpackTriangle(gl_PrimitiveID, 112); + GeometryNode geometryNode = geometryNodes.nodes[gl_GeometryIndexEXT]; + vec4 color = texture(textures[nonuniformEXT(geometryNode.textureIndexBaseColor)], tri.uv); + // If the alpha value of the texture at the current UV coordinates is below a given threshold, we'll ignore this intersection + // That way ray traversal will be stopped and the miss shader will be invoked +// if (color.a < 0.9) { + //if (((gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x) % 4) == 0) { + if(rnd(payloadSeed) > color.a) { + ignoreIntersectionEXT; + } +// } +} \ No newline at end of file diff --git a/shaders/glsl/raytracinggltf/anyhit.rahit.spv b/shaders/glsl/raytracinggltf/anyhit.rahit.spv new file mode 100644 index 00000000..61b05eb8 Binary files /dev/null and b/shaders/glsl/raytracinggltf/anyhit.rahit.spv differ diff --git a/shaders/glsl/raytracinggltf/bufferreferences.glsl b/shaders/glsl/raytracinggltf/bufferreferences.glsl new file mode 100644 index 00000000..e2b0771f --- /dev/null +++ b/shaders/glsl/raytracinggltf/bufferreferences.glsl @@ -0,0 +1,15 @@ +/* Copyright (c) 2023, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +layout(push_constant) uniform BufferReferences { + uint64_t vertices; + uint64_t indices; + uint64_t bufferAddress; +} bufferReferences; + +layout(buffer_reference, scalar) buffer Vertices {vec4 v[]; }; +layout(buffer_reference, scalar) buffer Indices {uint i[]; }; +layout(buffer_reference, scalar) buffer Data {vec4 f[]; }; \ No newline at end of file diff --git a/shaders/glsl/raytracinggltf/closesthit.rchit b/shaders/glsl/raytracinggltf/closesthit.rchit new file mode 100644 index 00000000..981b2c8b --- /dev/null +++ b/shaders/glsl/raytracinggltf/closesthit.rchit @@ -0,0 +1,63 @@ +/* Copyright (c) 2023, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +#version 460 + +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_EXT_buffer_reference2 : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require + +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 = 3, set = 0) uniform sampler2D image; + +struct GeometryNode { + uint64_t vertexBufferDeviceAddress; + uint64_t indexBufferDeviceAddress; + int textureIndexBaseColor; + int textureIndexOcclusion; +}; +layout(binding = 4, set = 0) buffer GeometryNodes { GeometryNode nodes[]; } geometryNodes; + +layout(binding = 5, set = 0) uniform sampler2D textures[]; + +#include "bufferreferences.glsl" +#include "geometrytypes.glsl" + +void main() +{ + Triangle tri = unpackTriangle(gl_PrimitiveID, 112); + hitValue = vec3(tri.normal); + + GeometryNode geometryNode = geometryNodes.nodes[gl_GeometryIndexEXT]; + + vec3 color = texture(textures[nonuniformEXT(geometryNode.textureIndexBaseColor)], tri.uv).rgb; + if (geometryNode.textureIndexOcclusion > -1) { + float occlusion = texture(textures[nonuniformEXT(geometryNode.textureIndexOcclusion)], tri.uv).r; + color *= occlusion; + } + + hitValue = color; + + // Shadow casting + float tmin = 0.001; + float tmax = 10000.0; + float epsilon = 0.001; + vec3 origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT + tri.normal * epsilon; + shadowed = true; + vec3 lightVector = vec3(-5.0, -2.5, -5.0); + // Trace shadow ray and offset indices to match shadow hit/miss shader group indices +// traceRayEXT(topLevelAS, gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT, 0xFF, 0, 0, 1, origin, tmin, lightVector, tmax, 2); +// if (shadowed) { +// hitValue *= 0.7; +// } +} diff --git a/shaders/glsl/raytracinggltf/closesthit.rchit.spv b/shaders/glsl/raytracinggltf/closesthit.rchit.spv new file mode 100644 index 00000000..0fb5193b Binary files /dev/null and b/shaders/glsl/raytracinggltf/closesthit.rchit.spv differ diff --git a/shaders/glsl/raytracinggltf/geometrytypes.glsl b/shaders/glsl/raytracinggltf/geometrytypes.glsl new file mode 100644 index 00000000..cfff9f14 --- /dev/null +++ b/shaders/glsl/raytracinggltf/geometrytypes.glsl @@ -0,0 +1,50 @@ +/* Copyright (c) 2023, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +struct Vertex +{ + vec3 pos; + vec3 normal; + vec2 uv; +}; + +struct Triangle { + Vertex vertices[3]; + vec3 normal; + vec2 uv; +}; + +// This function will unpack our vertex buffer data into a single triangle and calculates uv coordinates +Triangle unpackTriangle(uint index, int vertexSize) { + Triangle tri; + const uint triIndex = index * 3; + + GeometryNode geometryNode = geometryNodes.nodes[gl_GeometryIndexEXT]; + + Indices indices = Indices(geometryNode.indexBufferDeviceAddress); + Vertices vertices = Vertices(geometryNode.vertexBufferDeviceAddress); + + // Unpack vertices + // Data is packed as vec4 so we can map to the glTF vertex structure from the host side + // We match vkglTF::Vertex: pos.xyz+normal.x, normalyz+uv.xy + // glm::vec3 pos; + // glm::vec3 normal; + // glm::vec2 uv; + // ... + for (uint i = 0; i < 3; i++) { + const uint offset = indices.i[triIndex + i] * 6; + vec4 d0 = vertices.v[offset + 0]; // pos.xyz, n.x + vec4 d1 = vertices.v[offset + 1]; // n.yz, uv.xy + tri.vertices[i].pos = d0.xyz; + tri.vertices[i].normal = vec3(d0.w, d1.xy); + tri.vertices[i].uv = d1.zw; + } + // Calculate values at barycentric coordinates + vec3 barycentricCoords = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y); + tri.uv = tri.vertices[0].uv * barycentricCoords.x + tri.vertices[1].uv * barycentricCoords.y + tri.vertices[2].uv * barycentricCoords.z; + tri.normal = tri.vertices[0].normal * barycentricCoords.x + tri.vertices[1].normal * barycentricCoords.y + tri.vertices[2].normal * barycentricCoords.z; + return tri; +} \ No newline at end of file diff --git a/shaders/glsl/raytracinggltf/miss.rmiss b/shaders/glsl/raytracinggltf/miss.rmiss new file mode 100644 index 00000000..dacf6eac --- /dev/null +++ b/shaders/glsl/raytracinggltf/miss.rmiss @@ -0,0 +1,15 @@ +/* Copyright (c) 2023, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +#version 460 +#extension GL_EXT_ray_tracing : enable + +layout(location = 0) rayPayloadInEXT vec3 hitValue; + +void main() +{ + hitValue = vec3(1.0); +} \ No newline at end of file diff --git a/shaders/glsl/raytracinggltf/miss.rmiss.spv b/shaders/glsl/raytracinggltf/miss.rmiss.spv new file mode 100644 index 00000000..6c508af2 Binary files /dev/null and b/shaders/glsl/raytracinggltf/miss.rmiss.spv differ diff --git a/shaders/glsl/raytracinggltf/random.glsl b/shaders/glsl/raytracinggltf/random.glsl new file mode 100644 index 00000000..f35e62c1 --- /dev/null +++ b/shaders/glsl/raytracinggltf/random.glsl @@ -0,0 +1,37 @@ +/* Copyright (c) 2023, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +// Tiny Encryption Algorithm +// By Fahad Zafar, Marc Olano and Aaron Curtis, see https://www.highperformancegraphics.org/previous/www_2010/media/GPUAlgorithms/HPG2010_GPUAlgorithms_Zafar.pdf +uint tea(uint val0, uint val1) +{ + uint sum = 0; + uint v0 = val0; + uint v1 = val1; + for (uint n = 0; n < 16; n++) + { + sum += 0x9E3779B9; + v0 += ((v1 << 4) + 0xA341316C) ^ (v1 + sum) ^ ((v1 >> 5) + 0xC8013EA4); + v1 += ((v0 << 4) + 0xAD90777D) ^ (v0 + sum) ^ ((v0 >> 5) + 0x7E95761E); + } + return v0; +} + +// Linear congruential generator based on the previous RNG state +// See https://en.wikipedia.org/wiki/Linear_congruential_generator +uint lcg(inout uint previous) +{ + const uint multiplier = 1664525u; + const uint increment = 1013904223u; + previous = (multiplier * previous + increment); + return previous & 0x00FFFFFF; +} + +// Generate a random float in [0, 1) given the previous RNG state +float rnd(inout uint previous) +{ + return (float(lcg(previous)) / float(0x01000000)); +} \ No newline at end of file diff --git a/shaders/glsl/raytracinggltf/raygen.rgen b/shaders/glsl/raytracinggltf/raygen.rgen new file mode 100644 index 00000000..e3b47295 --- /dev/null +++ b/shaders/glsl/raytracinggltf/raygen.rgen @@ -0,0 +1,77 @@ +/* Copyright (c) 2023, Sascha Willems + * + * SPDX-License-Identifier: MIT + * + */ + +#version 460 +#extension GL_EXT_ray_tracing : enable +#extension GL_GOOGLE_include_directive : 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; + uint frame; +} cam; + +layout(location = 0) rayPayloadEXT vec3 hitValue; +layout(location = 3) rayPayloadEXT uint payloadSeed; + +#include "random.glsl" + +void main() +{ + uint seed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, cam.frame); + + 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 = cam.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; + +// 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), 0.0) ; + + float tmin = 0.001; + float tmax = 10000.0; + + hitValue = vec3(0.0); + vec3 hitValues = vec3(0); + + const int samples = 4; + + // Trace multiple rays for e.g. transparency + for(int smpl = 0; smpl < samples; smpl++) { + payloadSeed = tea(gl_LaunchIDEXT.y * gl_LaunchSizeEXT.x + gl_LaunchIDEXT.x, cam.frame); + traceRayEXT(topLevelAS, gl_RayFlagsNoneEXT, 0xff, 0, 0, 0, origin.xyz, tmin, direction.xyz, tmax, 0); + hitValues += hitValue; + } + +// imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValues / float(samples), 0.0)); + + vec3 hitVal = hitValues / float(samples); + + if(cam.frame > 0) + { + float a = 1.0f / float(cam.frame + 1); + vec3 old_color = imageLoad(image, ivec2(gl_LaunchIDEXT.xy)).xyz; + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(mix(old_color, hitVal, a), 1.f)); + } + else + { + // First frame, replace the value in the buffer + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitVal, 1.f)); + } +} diff --git a/shaders/glsl/raytracinggltf/raygen.rgen.spv b/shaders/glsl/raytracinggltf/raygen.rgen.spv new file mode 100644 index 00000000..6ad71428 Binary files /dev/null and b/shaders/glsl/raytracinggltf/raygen.rgen.spv differ diff --git a/shaders/glsl/raytracinggltf/shadow.rmiss b/shaders/glsl/raytracinggltf/shadow.rmiss new file mode 100644 index 00000000..36d9b7ba --- /dev/null +++ b/shaders/glsl/raytracinggltf/shadow.rmiss @@ -0,0 +1,9 @@ +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 2) rayPayloadInEXT bool shadowed; + +void main() +{ + shadowed = false; +} \ No newline at end of file diff --git a/shaders/glsl/raytracinggltf/shadow.rmiss.spv b/shaders/glsl/raytracinggltf/shadow.rmiss.spv new file mode 100644 index 00000000..9026dbea Binary files /dev/null and b/shaders/glsl/raytracinggltf/shadow.rmiss.spv differ