From 59d31535589f3c3f01bb797aa68b8ee0534d431b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin-Karl=20Lefran=C3=A7ois?= <38076163+mklefrancois@users.noreply.github.com> Date: Mon, 9 Aug 2021 19:43:51 +0200 Subject: [PATCH] mlefrancois/rt_reflection (#274) * Adding reflection sample * 2 colored cubes + plane * Adding specular color and fixing names * Finalize reflection * Updating headers * Rename folder * Renaming again files and folders * Change license, adapting code to renaming, constructing geometry locally. * Removing tinyobjloader + better SBT creation * Revert to original * Adding new sample * Updating copyright * Adding missing `nonuniformEXT()` * Using buffer reference instead of un-sized arrays * Apply clang format * Adding documentation * Fixing typos and adding references. --- samples/CMakeLists.txt | 1 + samples/README.md | 6 + .../ray_tracing_reflection/CMakeLists.txt | 32 + .../ray_tracing_reflection/README.md | 155 +++ .../ray_tracing_reflection/img1.png | Bin 0 -> 38583 bytes .../ray_tracing_reflection/img2.png | Bin 0 -> 13993 bytes .../ray_tracing_reflection/img3.png | Bin 0 -> 34739 bytes .../ray_tracing_reflection.cpp | 978 ++++++++++++++++++ .../ray_tracing_reflection.h | 139 +++ .../ray_tracing_reflection/closesthit.rchit | 166 +++ shaders/ray_tracing_reflection/miss.rmiss | 38 + .../ray_tracing_reflection/missShadow.rmiss | 28 + shaders/ray_tracing_reflection/raygen.rgen | 73 ++ 13 files changed, 1616 insertions(+) create mode 100644 samples/extensions/ray_tracing_reflection/CMakeLists.txt create mode 100644 samples/extensions/ray_tracing_reflection/README.md create mode 100644 samples/extensions/ray_tracing_reflection/img1.png create mode 100644 samples/extensions/ray_tracing_reflection/img2.png create mode 100644 samples/extensions/ray_tracing_reflection/img3.png create mode 100644 samples/extensions/ray_tracing_reflection/ray_tracing_reflection.cpp create mode 100644 samples/extensions/ray_tracing_reflection/ray_tracing_reflection.h create mode 100644 shaders/ray_tracing_reflection/closesthit.rchit create mode 100644 shaders/ray_tracing_reflection/miss.rmiss create mode 100644 shaders/ray_tracing_reflection/missShadow.rmiss create mode 100644 shaders/ray_tracing_reflection/raygen.rgen diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index 30e8d1e3e..c81ab18cb 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -62,6 +62,7 @@ set(ORDER_LIST "fragment_shading_rate" "push_descriptors" "raytracing_basic" + "raytracing_reflection" "timeline_semaphore" "synchronization_2" "buffer_device_address" diff --git a/samples/README.md b/samples/README.md index 74787da8b..1970494b5 100644 --- a/samples/README.md +++ b/samples/README.md @@ -209,3 +209,9 @@ Demonstrates how to use descriptor indexing to enable update-after-bind and non- ### [Fragment shading rate](./extensions/fragment_shading_rate)
**Extension**: [```VK_KHR_fragment_shading_rate```](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_fragment_shading_rate.html)
Uses a special framebuffer attachment to control fragment shading rates for different framebuffer regions. This allows explicit control over the number of fragment shader invocations for each pixel covered by a fragment, which is e.g. useful for foveated rendering. + +### [Ray tracing: reflection, shadow rays](./extensions/ray_tracing_reflection)
+**Extensions**: [```VK_KHR_ray_tracing_pipeline```](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_ray_tracing_pipeline), [```VK_KHR_acceleration_structure```](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#VK_KHR_acceleration_structure), [```VK_EXT_descriptor_indexing```](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_EXT_descriptor_indexing.html), [```VK_EXT_scalar_block_layout```](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_EXT_scalar_block_layout.html) +
+Render a simple scene showing the basics of ray tracing, including reflection and shadow rays. The sample creates some geometries and create a bottom acceleration structure for each, then make instances of those, using different materials and placing them at different locations. +
diff --git a/samples/extensions/ray_tracing_reflection/CMakeLists.txt b/samples/extensions/ray_tracing_reflection/CMakeLists.txt new file mode 100644 index 000000000..2d3b004fc --- /dev/null +++ b/samples/extensions/ray_tracing_reflection/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (c) 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 + + +get_filename_component(FOLDER_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +get_filename_component(PARENT_DIR ${CMAKE_CURRENT_LIST_DIR} PATH) +get_filename_component(CATEGORY_NAME ${PARENT_DIR} NAME) + +file(GLOB GLSL_FILES "${CMAKE_SOURCE_DIR}/shaders/${FOLDER_NAME}/*.*") + +add_sample_with_tags( + ID ${FOLDER_NAME} + FILES ${GLSL_FILES} + CATEGORY ${CATEGORY_NAME} + AUTHOR "Martin-Karl Lefrançois" + NAME "Ray tracing reflection" + DESCRIPTION "Example for hardware accelerated ray tracing") + diff --git a/samples/extensions/ray_tracing_reflection/README.md b/samples/extensions/ray_tracing_reflection/README.md new file mode 100644 index 000000000..96b172de2 --- /dev/null +++ b/samples/extensions/ray_tracing_reflection/README.md @@ -0,0 +1,155 @@ + + +# Ray tracing - reflection + +![](img1.png) + +## Overview + +This sample is a extended version of the [ray tracing basic](../raytracing_basic) with the addition of multiple geometries, +instances and materials. + +In addition, this sample is showing how to cast shadow rays and reflections. + +## Geometries, bottom, and top-level acceleration structures + +The structures of the geometry are like those described in the [OBJ format](https://en.wikipedia.org/wiki/Wavefront_.obj_file). For our geometry there is a list of vertices (position and normal) and a triplet of indices for each triangle. + +Each triangle also has an index for a material. In this example, each object has its own list of materials, but we could have made it so that the materials are shared by all objects in the scene. + +The example scene has two geometries: a plane and a cube. + +You can see the creation of the scene under `RaytracingReflection::create_scene()`. + +### `create_model()` + +This function allocates and upload the geometry to the GPU. There are four buffers per geometry. + +* Vertices: the position and normal +* Indices: index of vertex to form a triangle +* Material index: material id per triangle +* Materials: a list of materials (albedo, specular, reflection) + +### `create_buffer_references()` + +In this example, buffer references are used. Instead of having a descriptor set with multiple arrays to access the buffers, we create a buffer that contains the addresses of the scene models. With this method, we can easily access the data of the model we hit in the shader. + +In the shader, `VkDeviceAddress` are `uint64_t`, therefore we will access a buffer of an array of structure `ObjBuffers`. + +````cpp +struct ObjBuffers +{ + uint64_t vertices; + uint64_t indices; + uint64_t materials; + uint64_t materialIndices; +}; +layout(set = 0, binding = 3) buffer _scene_desc { ObjBuffers i[]; } scene_desc; +```` + +The addresses correspond to buffers, so we will declare them like this: + +````cpp +layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object +layout(buffer_reference, scalar) buffer Indices {uvec3 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 +```` + +The in the shader, to access the data of an object, we will do the following: + +````cpp + ObjBuffers objResource = scene_desc.i[gl_InstanceCustomIndexEXT]; + MatIndices matIndices = MatIndices(objResource.materialIndices); + Materials materials = Materials(objResource.materials); + Indices indices = Indices(objResource.indices); + Vertices vertices = Vertices(objResource.vertices); +```` + +Note that `gl_InstanceCustomIndexEXT` was set with one of the three scene objects. See `RaytracingReflection::create_blas_instance`. + +### `create_bottom_level_acceleration_structure()` + +We build a lower-level acceleration structure (BLAS) for each geometry: a cube with one material on each face (0), a plane (1), and a mirror cube (2). + +These BLAS are instantiated by the top-level acceleration structure (TLAS) with a transformation matrix. + +In this example w are calling separately the construction of all BLAS, allocating scratch buffer each time. A [better way](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/#accelerationstructure/bottom-levelaccelerationstructure/helperdetails:raytracingbuilder::buildblas()) would be to build them, knowing the size the biggest scratch buffer and doing all at once. Also provide in [the helper](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/#accelerationstructure/bottom-levelaccelerationstructure/helperdetails:raytracingbuilder::buildblas()) , is the ability to compact the memory used to store the BLAS when using the `VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_COMPACTION_BIT_KHR` flag. + +### `create_top_level_acceleration_structure()` + +The top-level acceleration structure (TLAS) embeds multiple BLAS. It is possible to reuse the same BLAS and give it a different transformation matrix to place it in a different position in the scene. We see this in `create_scene()`, the same BLAS id is reused with different matrices. + +Note, the BLAS id will be identified by the `gl_InstanceCustomIndexEXT` in the shader. + +![](img2.png) + +## Ray tracing pipeline + +The difference with [ray tracing basic](../raytracing_basic), is the addition of the second miss-shade module. This is called from the closest-hit shader to detect if there is an object between the hit point and the light. + +More on ray tracing pipeline [here](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/#raytracingpipeline). + +### Shadows + +In the closest-hit shader, we trace a ray to determine if there is an object between the hit point and the light. For this trace ray, we use the shadow-miss shader (index 1) and a different payload (index 1) that contains only one boolean value, "`isShadowed`". We assume that an object is blocking the light, so we initialize the value to `true`. Then, if the ray hits nothing, we [set this value to false](missShadow.rmiss). + +The origin of the ray is the hit position and the direction of the ray is toward the light. Note, we are using an hardcoded infinite light **L**. + +This method for shooting shadow rays is fast because we set the trace flag to skip execution of the closest-hit and terminate on the first hit, then only execute the shadow-miss-shader and set a small payload. + +````cpp +uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT | gl_RayFlagsSkipClosestHitShaderEXT; +```` + +### Reflection + +For reflection, we do not change `maxPipelineRayRecursionDepth` and leave the value at 2. Instead of recursively looping from the closest-hit shader, we store the information of the next ray in the payload and send new rays from the ray generation shader. + +When we call `traceRayEXT` from the closest-hit shader, it must store the state of all variables needed after execution. Recursively calling `traceRayEXT` requires storing a lot of data per ray call, and that is typically slow. + +Instead, we store in the payload the ray origin and the ray direction that the ray generation shader will use. This method also removes the pipeline ray recursion depth constraint. + +Here is how the payload is defined: + +````cpp +struct hitPayload +{ + vec3 radiance; + vec3 attenuation; + int done; + vec3 rayOrigin; + vec3 rayDir; +}; +```` + +The radiance is the value at the point of impact multiplied by the attenuation. The first time the attenuation is vec3(1) (no attenuation), but the shininess of the material reduces the attenuation in the following hits. After a few passes, the radiance will be close to vec3(0). The `done` is an indication that the ray did not hit anything. The miss shader sets it to true and the loop can be terminated. The origin of the ray starts at the camera, and is replaced by the position of the target. The direction starts at the camera direction, and then is reflected purely at the surface of the object. + +The recursion limit is set in the ray-generation shader. Currently it is set to **64**, changing its value will change the number of times the ray bounces off. + +Note: we could add a test on the attenuation and exist the loop if the value is below a certain threshold. + +## Diagram of the ray pipeline + +![](img3.png) + +## Other Tutorial + +The [following tutorial](https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_reflections) is showing the limitation of hitting the recursion limits. Note, the spec does not guarantee a recursion check at runtime. If you exceed either the recursion depth you reported in the raytrace pipeline create info, or the physical device recursion limit, undefined behavior results. diff --git a/samples/extensions/ray_tracing_reflection/img1.png b/samples/extensions/ray_tracing_reflection/img1.png new file mode 100644 index 0000000000000000000000000000000000000000..7670af1e93bef63b88ddaed0c8b622a69d5c01ab GIT binary patch literal 38583 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 literal 0 HcmV?d00001 diff --git a/samples/extensions/ray_tracing_reflection/img2.png b/samples/extensions/ray_tracing_reflection/img2.png new file mode 100644 index 0000000000000000000000000000000000000000..e06b7f7aacae459956b9083a055ad0a19e9c0273 GIT binary patch literal 13993 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 QD`-eE5(?sFqHjL@54P$(4*&oF literal 0 HcmV?d00001 diff --git a/samples/extensions/ray_tracing_reflection/img3.png b/samples/extensions/ray_tracing_reflection/img3.png new file mode 100644 index 0000000000000000000000000000000000000000..50120117ea7bc5c79ba45da166561dca1d4e7771 GIT binary patch literal 34739 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 R9wOkQ`Q*9U4;7n_{|{=UHva$s literal 0 HcmV?d00001 diff --git a/samples/extensions/ray_tracing_reflection/ray_tracing_reflection.cpp b/samples/extensions/ray_tracing_reflection/ray_tracing_reflection.cpp new file mode 100644 index 000000000..b65ca5fc2 --- /dev/null +++ b/samples/extensions/ray_tracing_reflection/ray_tracing_reflection.cpp @@ -0,0 +1,978 @@ +/* + * Copyright (c) 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 + */ + +/* + * More complex example for hardware accelerated ray tracing using VK_KHR_ray_tracing_pipeline and VK_KHR_acceleration_structure + */ + +#define TINYOBJLOADER_IMPLEMENTATION + +#include "ray_tracing_reflection.h" +#include + +struct ObjPlane : ObjModelCpu +{ + ObjPlane() + { + vertices = { + {{+1, 0, +1}, {0, 1, 0}}, + {{-1, 0, +1}, {0, 1, 0}}, + {{+1, 0, -1}, {0, 1, 0}}, + {{-1, 0, -1}, {0, 1, 0}}, + }; + indices = {0, 1, 2, 1, 2, 3}; + mat_index = {0, 0}; + } +}; + +struct ObjCube : ObjModelCpu +{ + ObjCube() + { + vertices = { + {{+0.5f, +0.5f, +0.5f}, {+0.f, +1.f, +0.f}}, // Top + {{-0.5f, +0.5f, +0.5f}, {+0.f, +1.f, +0.f}}, + {{+0.5f, +0.5f, -0.5f}, {+0.f, +1.f, +0.f}}, + {{-0.5f, +0.5f, -0.5f}, {+0.f, +1.f, +0.f}}, + {{+0.5f, -0.5f, +0.5f}, {+0.f, -1.f, +0.f}}, // Bottom + {{-0.5f, -0.5f, +0.5f}, {+0.f, -1.f, +0.f}}, + {{+0.5f, -0.5f, -0.5f}, {+0.f, -1.f, +0.f}}, + {{-0.5f, -0.5f, -0.5f}, {+0.f, -1.f, +0.f}}, + {{+0.5f, +0.5f, +0.5f}, {+1.f, +0.f, +0.f}}, // Right + {{+0.5f, +0.5f, -0.5f}, {+1.f, +0.f, +0.f}}, + {{+0.5f, -0.5f, -0.5f}, {+1.f, +0.f, +0.f}}, + {{+0.5f, -0.5f, +0.5f}, {+1.f, +0.f, +0.f}}, + {{-0.5f, +0.5f, +0.5f}, {-1.f, +0.f, +0.f}}, // left + {{-0.5f, +0.5f, -0.5f}, {-1.f, +0.f, +0.f}}, + {{-0.5f, -0.5f, -0.5f}, {-1.f, +0.f, +0.f}}, + {{-0.5f, -0.5f, +0.5f}, {-1.f, +0.f, +0.f}}, + {{-0.5f, +0.5f, +0.5f}, {+0.f, +0.f, +1.f}}, // front + {{+0.5f, +0.5f, +0.5f}, {+0.f, +0.f, +1.f}}, + {{+0.5f, -0.5f, +0.5f}, {+0.f, +0.f, +1.f}}, + {{-0.5f, -0.5f, +0.5f}, {+0.f, +0.f, +1.f}}, + {{-0.5f, +0.5f, -0.5f}, {+0.f, +0.f, -1.f}}, // back + {{+0.5f, +0.5f, -0.5f}, {+0.f, +0.f, -1.f}}, + {{+0.5f, -0.5f, -0.5f}, {+0.f, +0.f, -1.f}}, + {{-0.5f, -0.5f, -0.5f}, {+0.f, +0.f, -1.f}}, + + }; + indices = { + 0, 1, 2, 1, 2, 3, /*top*/ + 4, 5, 6, 5, 6, 7, /*bottom*/ + 8, 9, 10, 8, 10, 11, /*right*/ + 12, 13, 14, 12, 14, 15, /*left*/ + 16, 17, 18, 16, 18, 19, /*front*/ + 20, 21, 22, 20, 22, 23, /*back*/ + }; + mat_index = {0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5}; + } +}; + +RaytracingReflection::RaytracingReflection() +{ + title = "Hardware accelerated ray tracing"; + + set_api_version(VK_API_VERSION_1_2); + + // Ray tracing related extensions required by this sample + add_device_extension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME); + add_device_extension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME); + + // Required by VK_KHR_acceleration_structure + add_device_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + add_device_extension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); +} + +RaytracingReflection::~RaytracingReflection() +{ + if (device) + { + vkDestroyPipeline(get_device().get_handle(), pipeline, nullptr); + vkDestroyPipelineLayout(get_device().get_handle(), pipeline_layout, nullptr); + vkDestroyDescriptorSetLayout(get_device().get_handle(), descriptor_set_layout, nullptr); + vkDestroyImageView(get_device().get_handle(), storage_image.view, nullptr); + vkDestroyImage(get_device().get_handle(), storage_image.image, nullptr); + vkFreeMemory(get_device().get_handle(), storage_image.memory, nullptr); + delete_acceleration_structure(top_level_acceleration_structure); + for (auto &b : bottom_level_acceleration_structure) + { + delete_acceleration_structure(b); + } + + for (auto &obj : obj_models) + { + obj.vertex_buffer.reset(); + obj.index_buffer.reset(); + obj.mat_color_buffer.reset(); + obj.mat_index_buffer.reset(); + } + + ubo.reset(); + } +} + +/* + Enable extension features required by this sample + These are passed to device creation via a pNext structure chain +*/ +void RaytracingReflection::request_gpu_features(vkb::PhysicalDevice &gpu) +{ + // The request is filling with the capabilities (all on by default) + auto &vulkan12_features = gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES); + auto &vulkan11_features = gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES); + + auto &ray_tracing_features = gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR); + auto &acceleration_structure_features = gpu.request_extension_features(VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR); + + // Enabling all Vulkan features (Int64) + gpu.get_mutable_requested_features() = gpu.get_features(); +} + +/* + Set up a storage image that the ray generation shader will be writing to +*/ +void RaytracingReflection::create_storage_image() +{ + storage_image.width = width; + storage_image.height = height; + + VkImageCreateInfo image{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO}; + image.imageType = VK_IMAGE_TYPE_2D; + image.format = VK_FORMAT_B8G8R8A8_UNORM; + image.extent.width = storage_image.width; + image.extent.height = storage_image.height; + image.extent.depth = 1; + image.mipLevels = 1; + image.arrayLayers = 1; + image.samples = VK_SAMPLE_COUNT_1_BIT; + image.tiling = VK_IMAGE_TILING_OPTIMAL; + image.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_STORAGE_BIT; + image.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + VK_CHECK(vkCreateImage(get_device().get_handle(), &image, nullptr, &storage_image.image)); + + VkMemoryRequirements memory_requirements; + vkGetImageMemoryRequirements(get_device().get_handle(), storage_image.image, &memory_requirements); + VkMemoryAllocateInfo memory_allocate_info{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + memory_allocate_info.allocationSize = memory_requirements.size; + memory_allocate_info.memoryTypeIndex = get_device().get_memory_type(memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + VK_CHECK(vkAllocateMemory(get_device().get_handle(), &memory_allocate_info, nullptr, &storage_image.memory)); + VK_CHECK(vkBindImageMemory(get_device().get_handle(), storage_image.image, storage_image.memory, 0)); + + VkImageViewCreateInfo color_image_view{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + color_image_view.viewType = VK_IMAGE_VIEW_TYPE_2D; + color_image_view.format = VK_FORMAT_B8G8R8A8_UNORM; + color_image_view.subresourceRange = {}; + color_image_view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + color_image_view.subresourceRange.baseMipLevel = 0; + color_image_view.subresourceRange.levelCount = 1; + color_image_view.subresourceRange.baseArrayLayer = 0; + color_image_view.subresourceRange.layerCount = 1; + color_image_view.image = storage_image.image; + VK_CHECK(vkCreateImageView(get_device().get_handle(), &color_image_view, nullptr, &storage_image.view)); + + VkCommandBuffer command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vkb::set_image_layout(command_buffer, storage_image.image, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, + {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}); + get_device().flush_command_buffer(command_buffer, queue); +} + +/* + Create the bottom level acceleration structure that contains the scene's geometry (triangles) +*/ +void RaytracingReflection::create_bottom_level_acceleration_structure(ObjModelGpu &obj_model) +{ + // Note that the buffer usage flags for buffers consumed by the bottom level acceleration structure require special flags + const VkBufferUsageFlags buffer_usage_flags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + // Setup a single transformation matrix that can be used to transform the whole geometry for a single bottom level acceleration structure + VkTransformMatrixKHR transform_matrix = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f}; + std::unique_ptr transform_matrix_buffer = std::make_unique(get_device(), sizeof(transform_matrix), buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU); + transform_matrix_buffer->update(&transform_matrix, sizeof(transform_matrix)); + + VkDeviceOrHostAddressConstKHR vertex_data_device_address{}; + VkDeviceOrHostAddressConstKHR index_data_device_address{}; + VkDeviceOrHostAddressConstKHR transform_matrix_device_address{}; + + vertex_data_device_address.deviceAddress = obj_model.vertex_buffer->get_device_address(); + index_data_device_address.deviceAddress = obj_model.index_buffer->get_device_address(); + transform_matrix_device_address.deviceAddress = transform_matrix_buffer->get_device_address(); + + VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; + triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR; + triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; + triangles.vertexData = vertex_data_device_address; + triangles.maxVertex = obj_model.nb_vertices; + triangles.vertexStride = sizeof(ObjVertex); + triangles.indexType = VK_INDEX_TYPE_UINT32; + triangles.indexData = index_data_device_address; + triangles.transformData = transform_matrix_device_address; + + // The bottom level acceleration structure contains one set of triangles as the input geometry + VkAccelerationStructureGeometryKHR acceleration_structure_geometry{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR}; + acceleration_structure_geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + acceleration_structure_geometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; + acceleration_structure_geometry.geometry.triangles = triangles; + + // Get the size requirements for buffers involved in the acceleration structure build process + VkAccelerationStructureBuildGeometryInfoKHR acceleration_structure_build_geometry_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR}; + acceleration_structure_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + acceleration_structure_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + acceleration_structure_build_geometry_info.geometryCount = 1; + acceleration_structure_build_geometry_info.pGeometries = &acceleration_structure_geometry; + + const uint32_t triangle_count = obj_model.nb_indices / 3; + + VkAccelerationStructureBuildSizesInfoKHR acceleration_structure_build_sizes_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR}; + vkGetAccelerationStructureBuildSizesKHR(device->get_handle(), + VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &acceleration_structure_build_geometry_info, + &triangle_count, + &acceleration_structure_build_sizes_info); + + // Create a buffer to hold the acceleration structure + AccelerationStructure blas; + blas.buffer = std::make_unique(get_device(), acceleration_structure_build_sizes_info.accelerationStructureSize, VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, VMA_MEMORY_USAGE_GPU_ONLY); + + // Create the acceleration structure + VkAccelerationStructureCreateInfoKHR acceleration_structure_create_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR}; + acceleration_structure_create_info.buffer = blas.buffer->get_handle(); + acceleration_structure_create_info.size = acceleration_structure_build_sizes_info.accelerationStructureSize; + acceleration_structure_create_info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + vkCreateAccelerationStructureKHR(device->get_handle(), &acceleration_structure_create_info, nullptr, &blas.handle); + + // The actual build process starts here + + // Create a scratch buffer as a temporary storage for the acceleration structure build + std::unique_ptr sc_buffer; + sc_buffer = std::make_unique(get_device(), acceleration_structure_build_sizes_info.buildScratchSize, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + + VkAccelerationStructureBuildGeometryInfoKHR acceleration_build_geometry_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR}; + acceleration_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR; + acceleration_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + acceleration_build_geometry_info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + acceleration_build_geometry_info.dstAccelerationStructure = blas.handle; + acceleration_build_geometry_info.geometryCount = 1; + acceleration_build_geometry_info.pGeometries = &acceleration_structure_geometry; + acceleration_build_geometry_info.scratchData.deviceAddress = sc_buffer->get_device_address(); + + VkAccelerationStructureBuildRangeInfoKHR acceleration_structure_build_range_info; + acceleration_structure_build_range_info.primitiveCount = triangle_count; + acceleration_structure_build_range_info.primitiveOffset = 0; + acceleration_structure_build_range_info.firstVertex = 0; + acceleration_structure_build_range_info.transformOffset = 0; + std::vector acceleration_build_structure_range_infos = {&acceleration_structure_build_range_info}; + + // 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 command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vkCmdBuildAccelerationStructuresKHR(command_buffer, + 1, + &acceleration_build_geometry_info, + acceleration_build_structure_range_infos.data()); + get_device().flush_command_buffer(command_buffer, queue); + + //delete_scratch_buffer(scratch_buffer); + sc_buffer.reset(); + + // Store the blas to be re-used as instance + bottom_level_acceleration_structure.push_back(std::move(blas)); +} + +/* + Create the top level acceleration structure containing geometry instances of the bottom level acceleration structure(s) +*/ +void RaytracingReflection::create_top_level_acceleration_structure(std::vector &blas_instances) +{ + std::unique_ptr instances_buffer = std::make_unique(get_device(), + sizeof(VkAccelerationStructureInstanceKHR) * blas_instances.size(), + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + instances_buffer->update(blas_instances.data(), sizeof(VkAccelerationStructureInstanceKHR) * blas_instances.size()); + + VkDeviceOrHostAddressConstKHR instance_data_device_address{}; + instance_data_device_address.deviceAddress = instances_buffer->get_device_address(); + + // The top level acceleration structure contains (bottom level) instance as the input geometry + VkAccelerationStructureGeometryKHR acceleration_structure_geometry{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR}; + acceleration_structure_geometry.geometryType = VK_GEOMETRY_TYPE_INSTANCES_KHR; + acceleration_structure_geometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; + acceleration_structure_geometry.geometry.instances.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_INSTANCES_DATA_KHR; + acceleration_structure_geometry.geometry.instances.arrayOfPointers = VK_FALSE; + acceleration_structure_geometry.geometry.instances.data = instance_data_device_address; + + // Get the size requirements for buffers involved in the acceleration structure build process + VkAccelerationStructureBuildGeometryInfoKHR acceleration_structure_build_geometry_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR}; + acceleration_structure_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + acceleration_structure_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + acceleration_structure_build_geometry_info.geometryCount = 1; + acceleration_structure_build_geometry_info.pGeometries = &acceleration_structure_geometry; + + const auto primitive_count = static_cast(blas_instances.size()); + + VkAccelerationStructureBuildSizesInfoKHR acceleration_structure_build_sizes_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_SIZES_INFO_KHR}; + vkGetAccelerationStructureBuildSizesKHR( + device->get_handle(), VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR, + &acceleration_structure_build_geometry_info, + &primitive_count, + &acceleration_structure_build_sizes_info); + + // Create a buffer to hold the acceleration structure + top_level_acceleration_structure.buffer = std::make_unique( + get_device(), + acceleration_structure_build_sizes_info.accelerationStructureSize, + VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT_KHR, + VMA_MEMORY_USAGE_GPU_ONLY); + + // Create the acceleration structure + VkAccelerationStructureCreateInfoKHR acceleration_structure_create_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_CREATE_INFO_KHR}; + acceleration_structure_create_info.buffer = top_level_acceleration_structure.buffer->get_handle(); + acceleration_structure_create_info.size = acceleration_structure_build_sizes_info.accelerationStructureSize; + acceleration_structure_create_info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + vkCreateAccelerationStructureKHR(device->get_handle(), &acceleration_structure_create_info, nullptr, &top_level_acceleration_structure.handle); + + // The actual build process starts here + + // Create a scratch buffer as a temporary storage for the acceleration structure build + std::unique_ptr sc_buffer; + sc_buffer = std::make_unique(get_device(), acceleration_structure_build_sizes_info.buildScratchSize, + VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, + VMA_MEMORY_USAGE_CPU_TO_GPU); + + VkAccelerationStructureBuildGeometryInfoKHR acceleration_build_geometry_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR}; + acceleration_build_geometry_info.type = VK_ACCELERATION_STRUCTURE_TYPE_TOP_LEVEL_KHR; + acceleration_build_geometry_info.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR; + acceleration_build_geometry_info.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR; + acceleration_build_geometry_info.dstAccelerationStructure = top_level_acceleration_structure.handle; + acceleration_build_geometry_info.geometryCount = 1; + acceleration_build_geometry_info.pGeometries = &acceleration_structure_geometry; + acceleration_build_geometry_info.scratchData.deviceAddress = sc_buffer->get_device_address(); + + VkAccelerationStructureBuildRangeInfoKHR acceleration_structure_build_range_info; + acceleration_structure_build_range_info.primitiveCount = primitive_count; + acceleration_structure_build_range_info.primitiveOffset = 0; + acceleration_structure_build_range_info.firstVertex = 0; + acceleration_structure_build_range_info.transformOffset = 0; + std::vector acceleration_build_structure_range_infos = {&acceleration_structure_build_range_info}; + + // 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 command_buffer = get_device().create_command_buffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + vkCmdBuildAccelerationStructuresKHR( + command_buffer, + 1, + &acceleration_build_geometry_info, + acceleration_build_structure_range_infos.data()); + get_device().flush_command_buffer(command_buffer, queue); + + //delete_scratch_buffer(scratch_buffer); + sc_buffer.reset(); +} + +inline uint32_t aligned_size(uint32_t value, uint32_t alignment) +{ + return (value + alignment - 1) & ~(alignment - 1); +} + +/* +Create the GPU representation of the model +*/ +void RaytracingReflection::create_model(ObjModelCpu &obj, const std::vector &materials) +{ + ObjModelGpu model; + model.nb_indices = static_cast(obj.indices.size()); + model.nb_vertices = static_cast(obj.vertices.size()); + + auto vertex_buffer_size = obj.vertices.size() * sizeof(ObjVertex); + auto index_buffer_size = obj.indices.size() * sizeof(uint32_t); + auto mat_index_buffer_size = obj.mat_index.size() * sizeof(int32_t); + auto mat_buffer_size = materials.size() * sizeof(ObjMaterial); + + // Making sure the material triangle index don't exceed the number of materials + auto max_index = static_cast(materials.size() - 1); + std::vector mat_index(obj.mat_index.size()); + for (auto i = 0; i < obj.mat_index.size(); i++) + mat_index[i] = std::min(max_index, obj.mat_index[i]); + + // Note that the buffer usage flags for buffers consumed by the bottom level acceleration structure require special flags + VkBufferUsageFlags buffer_usage_flags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + model.vertex_buffer = std::make_unique(get_device(), vertex_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU); + model.vertex_buffer->update(obj.vertices.data(), vertex_buffer_size); + + // Acceleration structure flag is not needed for the rest + buffer_usage_flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + + model.index_buffer = std::make_unique(get_device(), index_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU); + model.index_buffer->update(obj.indices.data(), index_buffer_size); + + model.mat_index_buffer = std::make_unique(get_device(), mat_index_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU); + model.mat_index_buffer->update(mat_index.data(), mat_index_buffer_size); + + model.mat_color_buffer = std::make_unique(get_device(), mat_buffer_size, buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU); + model.mat_color_buffer->update(reinterpret_cast(materials.data()), mat_buffer_size); + + obj_models.push_back(std::move(model)); +} + +auto RaytracingReflection::create_blas_instance(uint32_t blas_id, glm::mat4 &mat) +{ + VkTransformMatrixKHR transform_matrix; + glm::mat3x4 rtxT = glm::transpose(mat); + memcpy(&transform_matrix, glm::value_ptr(rtxT), sizeof(VkTransformMatrixKHR)); + + AccelerationStructure &blas = bottom_level_acceleration_structure[blas_id]; + + // Get the bottom acceleration structure's handle, which will be used during the top level acceleration build + VkAccelerationStructureDeviceAddressInfoKHR acceleration_device_address_info{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_DEVICE_ADDRESS_INFO_KHR}; + acceleration_device_address_info.accelerationStructure = blas.handle; + auto device_address = vkGetAccelerationStructureDeviceAddressKHR(device->get_handle(), &acceleration_device_address_info); + + VkAccelerationStructureInstanceKHR blas_instance{}; + blas_instance.transform = transform_matrix; + blas_instance.instanceCustomIndex = blas_id; + blas_instance.mask = 0xFF; + blas_instance.instanceShaderBindingTableRecordOffset = 0; + blas_instance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + blas_instance.accelerationStructureReference = device_address; + + return blas_instance; +} + +/* + Create a buffer holding the address of model buffers (buffer reference) +*/ +void RaytracingReflection::create_buffer_references() +{ + // For each model that was created, we retrieved the address of buffers + // used by them. So in the shader, we have direct access to the data + std::vector obj_data; + auto nbObj = static_cast(obj_models.size()); + for (uint32_t i = 0; i < nbObj; ++i) + { + ObjBuffers data; + data.vertices = obj_models[i].vertex_buffer->get_device_address(); + data.indices = obj_models[i].index_buffer->get_device_address(); + data.materials = obj_models[i].mat_color_buffer->get_device_address(); + data.materialIndices = obj_models[i].mat_index_buffer->get_device_address(); + obj_data.emplace_back(data); + } + VkBufferUsageFlags buffer_usage_flags = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + scene_desc = std::make_unique(get_device(), nbObj * sizeof(ObjBuffers), buffer_usage_flags, VMA_MEMORY_USAGE_CPU_TO_GPU); + scene_desc->update(obj_data.data(), nbObj * sizeof(ObjBuffers)); +} + +/* + Create scene geometry and ray tracing acceleration structures +*/ +void RaytracingReflection::create_scene() +{ + // Materials + ObjMaterial mat_red = {{1, 0, 0}, {1, 1, 1}, {0}}; + ObjMaterial mat_green = {{0, 1, 0}, {1, 1, 1}, {0}}; + ObjMaterial mat_blue = {{0, 0, 1}, {1, 1, 1}, {0}}; + ObjMaterial mat_yellow = {{1, 1, 0}, {1, 1, 1}, {0}}; + ObjMaterial mat_cyan = {{0, 1, 1}, {1, 1, 1}, {0}}; + ObjMaterial mat_magenta = {{1, 0, 1}, {1, 1, 1}, {0}}; + ObjMaterial mat_grey = {{0.7f, 0.7f, 0.7f}, {0.9f, 0.9f, 0.9f}, {0.1f}}; // Slightly reflective + ObjMaterial mat_mirror = {{0.3f, 0.9f, 1.0f}, {0.9f, 0.9f, 0.9f}, {0.9f}}; // Mirror Slightly blue + + // Geometries + auto cube = ObjCube(); + auto plane = ObjPlane(); + + // Upload geometries to GPU + create_model(cube, {mat_red, mat_green, mat_blue, mat_yellow, mat_cyan, mat_magenta}); // 6 color faces + create_model(plane, {mat_grey}); + create_model(cube, {mat_mirror}); + + // Create a buffer holding the address of model buffers (buffer reference) + create_buffer_references(); + + // Create as many bottom acceleration structures (blas) as there are geometries/models + create_bottom_level_acceleration_structure(obj_models[0]); + create_bottom_level_acceleration_structure(obj_models[1]); + create_bottom_level_acceleration_structure(obj_models[2]); + + // Matrices to position the instances + glm::mat4 m_mirror_back = glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.0f, 0.0f, -7.0f)), glm::vec3(5.0f, 5.0f, 0.1f)); + glm::mat4 m_mirror_front = glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.0f, 0.0f, 7.0f)), glm::vec3(5.0f, 5.0f, 0.1f)); + glm::mat4 m_plane = glm::scale(glm::translate(glm::mat4(1.f), glm::vec3(0.0f, -1.0f, 0.0f)), glm::vec3(15.0f, 15.0f, 15.0f)); + glm::mat4 m_cube_left = glm::translate(glm::mat4(1.f), glm::vec3(-1.0f, 0.0f, 0.0f)); + glm::mat4 m_cube_right = glm::translate(glm::mat4(1.f), glm::vec3(1.0f, 0.0f, 0.0f)); + + // Creating instances of the blas to the top level acceleration structure + std::vector blas_instances; + blas_instances.push_back(create_blas_instance(0, m_cube_left)); + blas_instances.push_back(create_blas_instance(0, m_cube_right)); + blas_instances.push_back(create_blas_instance(1, m_plane)); + blas_instances.push_back(create_blas_instance(2, m_mirror_back)); + blas_instances.push_back(create_blas_instance(2, m_mirror_front)); + + // Building the TLAS + create_top_level_acceleration_structure(blas_instances); +} + +/* + Create the Shader Binding Tables that connects the ray tracing pipelines' programs and the top-level acceleration structure + + SBT Layout used in this sample: + + /-------------\ + | raygen | + |-------------| + | miss | + |-------------| + | miss shadow | + |-------------| + | hit | + \-------------/ +*/ + +void RaytracingReflection::create_shader_binding_tables() +{ + // Index position of the groups in the generated ray tracing pipeline + // To be generic, this should be pass in parameters + std::vector rgen_index{0}; + std::vector miss_index{1, 2}; + std::vector hit_index{3}; + + const uint32_t handle_size = ray_tracing_pipeline_properties.shaderGroupHandleSize; + const uint32_t handle_alignment = ray_tracing_pipeline_properties.shaderGroupHandleAlignment; + const uint32_t handle_size_aligned = aligned_size(handle_size, handle_alignment); + + const VkBufferUsageFlags sbt_buffer_usage_flags = VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR | VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + const VmaMemoryUsage sbt_memory_usage = VMA_MEMORY_USAGE_CPU_TO_GPU; + + // Create binding table buffers for each shader type + raygen_shader_binding_table = std::make_unique(get_device(), handle_size_aligned * rgen_index.size(), sbt_buffer_usage_flags, sbt_memory_usage, 0); + miss_shader_binding_table = std::make_unique(get_device(), handle_size_aligned * miss_index.size(), sbt_buffer_usage_flags, sbt_memory_usage, 0); + hit_shader_binding_table = std::make_unique(get_device(), handle_size_aligned * hit_index.size(), sbt_buffer_usage_flags, sbt_memory_usage, 0); + + // Copy the pipeline's shader handles into a host buffer + const auto group_count = static_cast(rgen_index.size() + miss_index.size() + hit_index.size()); + const auto sbt_size = group_count * handle_size_aligned; + std::vector shader_handle_storage(sbt_size); + VK_CHECK(vkGetRayTracingShaderGroupHandlesKHR(get_device().get_handle(), pipeline, 0, group_count, sbt_size, shader_handle_storage.data())); + + // Write the handles in the SBT buffer + auto copyHandles = [&](auto &buffer, std::vector &indices, uint32_t stride) { + auto *pBuffer = static_cast(buffer->map()); + for (uint32_t index = 0; index < static_cast(indices.size()); index++) + { + auto *pStart = pBuffer; + // Copy the handle + memcpy(pBuffer, shader_handle_storage.data() + (indices[index] * handle_size), handle_size); + pBuffer = pStart + stride; // Jumping to next group + } + buffer->unmap(); + }; + + copyHandles(raygen_shader_binding_table, rgen_index, handle_size_aligned); + copyHandles(miss_shader_binding_table, miss_index, handle_size_aligned); + copyHandles(hit_shader_binding_table, hit_index, handle_size_aligned); +} + +/* + Create the descriptor sets used for the ray tracing dispatch +*/ +void RaytracingReflection::create_descriptor_sets() +{ + uint32_t nbObj = static_cast(obj_models.size()); + + std::vector pool_sizes = { + {VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1}, + {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1}, + {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1}, + {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1}, + }; + VkDescriptorPoolCreateInfo descriptor_pool_create_info = vkb::initializers::descriptor_pool_create_info(pool_sizes, 1); + VK_CHECK(vkCreateDescriptorPool(get_device().get_handle(), &descriptor_pool_create_info, nullptr, &descriptor_pool)); + + VkDescriptorSetAllocateInfo descriptor_set_allocate_info = vkb::initializers::descriptor_set_allocate_info(descriptor_pool, &descriptor_set_layout, 1); + VK_CHECK(vkAllocateDescriptorSets(get_device().get_handle(), &descriptor_set_allocate_info, &descriptor_set)); + + // Setup the descriptor for binding our top level acceleration structure to the ray tracing shaders + VkWriteDescriptorSetAccelerationStructureKHR descriptor_acceleration_structure_info{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR}; + descriptor_acceleration_structure_info.accelerationStructureCount = 1; + descriptor_acceleration_structure_info.pAccelerationStructures = &top_level_acceleration_structure.handle; + + VkWriteDescriptorSet acceleration_structure_write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET}; + acceleration_structure_write.dstSet = descriptor_set; + acceleration_structure_write.dstBinding = 0; + acceleration_structure_write.descriptorCount = 1; + acceleration_structure_write.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + // The acceleration structure descriptor has to be chained via pNext + acceleration_structure_write.pNext = &descriptor_acceleration_structure_info; + + VkDescriptorImageInfo image_descriptor{}; + image_descriptor.imageView = storage_image.view; + image_descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + + VkDescriptorBufferInfo uniform_descriptor = create_descriptor(*ubo); + VkDescriptorBufferInfo scene_descriptor = create_descriptor(*scene_desc); + + VkWriteDescriptorSet result_image_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &image_descriptor); + VkWriteDescriptorSet uniform_buffer_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniform_descriptor); + VkWriteDescriptorSet scene_buffer_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, &scene_descriptor); + + std::vector write_descriptor_sets = { + acceleration_structure_write, + result_image_write, + uniform_buffer_write, + scene_buffer_write, + }; + vkUpdateDescriptorSets(get_device().get_handle(), static_cast(write_descriptor_sets.size()), write_descriptor_sets.data(), 0, VK_NULL_HANDLE); +} + +/* + Create our ray tracing pipeline +*/ +void RaytracingReflection::create_ray_tracing_pipeline() +{ + // Slot for binding top level acceleration structures to the ray generation shader + VkDescriptorSetLayoutBinding acceleration_structure_layout_binding{}; + acceleration_structure_layout_binding.binding = 0; + acceleration_structure_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR; + acceleration_structure_layout_binding.descriptorCount = 1; + acceleration_structure_layout_binding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + + VkDescriptorSetLayoutBinding result_image_layout_binding{}; + result_image_layout_binding.binding = 1; + result_image_layout_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + result_image_layout_binding.descriptorCount = 1; + result_image_layout_binding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR; + + VkDescriptorSetLayoutBinding uniform_buffer_binding{}; + uniform_buffer_binding.binding = 2; + uniform_buffer_binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + uniform_buffer_binding.descriptorCount = 1; + uniform_buffer_binding.stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR; + + // Scene description + VkDescriptorSetLayoutBinding scene_buffer_binding{}; + scene_buffer_binding.binding = 3; + scene_buffer_binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + scene_buffer_binding.descriptorCount = 1; + scene_buffer_binding.stageFlags = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + + std::vector bindings = { + acceleration_structure_layout_binding, + result_image_layout_binding, + uniform_buffer_binding, + scene_buffer_binding, + }; + + VkDescriptorSetLayoutCreateInfo layout_info{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + layout_info.bindingCount = static_cast(bindings.size()); + layout_info.pBindings = bindings.data(); + VK_CHECK(vkCreateDescriptorSetLayout(get_device().get_handle(), &layout_info, nullptr, &descriptor_set_layout)); + + VkPipelineLayoutCreateInfo pipeline_layout_create_info{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pipeline_layout_create_info.setLayoutCount = 1; + pipeline_layout_create_info.pSetLayouts = &descriptor_set_layout; + + VK_CHECK(vkCreatePipelineLayout(get_device().get_handle(), &pipeline_layout_create_info, nullptr, &pipeline_layout)); + + // Ray tracing shaders + buffer reference require SPIR-V 1.5, so we need to set the appropriate target environment for the glslang compiler + vkb::GLSLCompiler::set_target_environment(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5); + + /* + Setup ray tracing shader groups + Each shader group points at the corresponding shader in the pipeline + */ + std::vector shader_stages; + + // Ray generation group + { + shader_stages.push_back(load_shader("ray_tracing_reflection/raygen.rgen", VK_SHADER_STAGE_RAYGEN_BIT_KHR)); + VkRayTracingShaderGroupCreateInfoKHR raygen_group_ci{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + raygen_group_ci.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + raygen_group_ci.generalShader = static_cast(shader_stages.size()) - 1; + raygen_group_ci.closestHitShader = VK_SHADER_UNUSED_KHR; + raygen_group_ci.anyHitShader = VK_SHADER_UNUSED_KHR; + raygen_group_ci.intersectionShader = VK_SHADER_UNUSED_KHR; + shader_groups.push_back(raygen_group_ci); + } + + // Ray miss group + { + shader_stages.push_back(load_shader("ray_tracing_reflection/miss.rmiss", VK_SHADER_STAGE_MISS_BIT_KHR)); + VkRayTracingShaderGroupCreateInfoKHR miss_group_ci{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + miss_group_ci.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + miss_group_ci.generalShader = static_cast(shader_stages.size()) - 1; + miss_group_ci.closestHitShader = VK_SHADER_UNUSED_KHR; + miss_group_ci.anyHitShader = VK_SHADER_UNUSED_KHR; + miss_group_ci.intersectionShader = VK_SHADER_UNUSED_KHR; + shader_groups.push_back(miss_group_ci); + } + + // Ray miss (shadow) group + { + shader_stages.push_back(load_shader("ray_tracing_reflection/missShadow.rmiss", VK_SHADER_STAGE_MISS_BIT_KHR)); + VkRayTracingShaderGroupCreateInfoKHR miss_group_ci{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + miss_group_ci.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + miss_group_ci.generalShader = static_cast(shader_stages.size()) - 1; + miss_group_ci.closestHitShader = VK_SHADER_UNUSED_KHR; + miss_group_ci.anyHitShader = VK_SHADER_UNUSED_KHR; + miss_group_ci.intersectionShader = VK_SHADER_UNUSED_KHR; + shader_groups.push_back(miss_group_ci); + } + + // Ray closest hit group + { + shader_stages.push_back(load_shader("ray_tracing_reflection/closesthit.rchit", VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR)); + VkRayTracingShaderGroupCreateInfoKHR closes_hit_group_ci{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + closes_hit_group_ci.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; + closes_hit_group_ci.generalShader = VK_SHADER_UNUSED_KHR; + closes_hit_group_ci.closestHitShader = static_cast(shader_stages.size()) - 1; + closes_hit_group_ci.anyHitShader = VK_SHADER_UNUSED_KHR; + closes_hit_group_ci.intersectionShader = VK_SHADER_UNUSED_KHR; + shader_groups.push_back(closes_hit_group_ci); + } + + /* + Create the ray tracing pipeline + */ + VkRayTracingPipelineCreateInfoKHR raytracing_pipeline_create_info{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR}; + raytracing_pipeline_create_info.stageCount = static_cast(shader_stages.size()); + raytracing_pipeline_create_info.pStages = shader_stages.data(); + raytracing_pipeline_create_info.groupCount = static_cast(shader_groups.size()); + raytracing_pipeline_create_info.pGroups = shader_groups.data(); + raytracing_pipeline_create_info.maxPipelineRayRecursionDepth = 2; + raytracing_pipeline_create_info.layout = pipeline_layout; + VK_CHECK(vkCreateRayTracingPipelinesKHR(get_device().get_handle(), VK_NULL_HANDLE, VK_NULL_HANDLE, 1, &raytracing_pipeline_create_info, nullptr, &pipeline)); +} + +/* + Deletes all resources acquired by an acceleration structure +*/ +void RaytracingReflection::delete_acceleration_structure(AccelerationStructure &acceleration_structure) +{ + if (acceleration_structure.buffer) + { + acceleration_structure.buffer.reset(); + } + + if (acceleration_structure.handle) + { + vkDestroyAccelerationStructureKHR(device->get_handle(), acceleration_structure.handle, nullptr); + } +} + +/* + Create the uniform buffer used to pass matrices to the ray tracing ray generation shader +*/ +void RaytracingReflection::create_uniform_buffer() +{ + ubo = std::make_unique(get_device(), sizeof(uniform_data), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VMA_MEMORY_USAGE_CPU_TO_GPU); + ubo->convert_and_update(uniform_data); + update_uniform_buffers(); +} + +/* + Command buffer generation +*/ +void RaytracingReflection::build_command_buffers() +{ + if (width != storage_image.width || height != storage_image.height) + { + // If the view port size has changed, we need to recreate the storage image + vkDestroyImageView(get_device().get_handle(), storage_image.view, nullptr); + vkDestroyImage(get_device().get_handle(), storage_image.image, nullptr); + vkFreeMemory(get_device().get_handle(), storage_image.memory, nullptr); + create_storage_image(); + + // The descriptor also needs to be updated to reference the new image + VkDescriptorImageInfo image_descriptor{}; + image_descriptor.imageView = storage_image.view; + image_descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + VkWriteDescriptorSet result_image_write = vkb::initializers::write_descriptor_set(descriptor_set, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, &image_descriptor); + vkUpdateDescriptorSets(get_device().get_handle(), 1, &result_image_write, 0, VK_NULL_HANDLE); + build_command_buffers(); + } + + VkCommandBufferBeginInfo command_buffer_begin_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + + VkImageSubresourceRange subresource_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + + for (int32_t i = 0; i < draw_cmd_buffers.size(); ++i) + { + VK_CHECK(vkBeginCommandBuffer(draw_cmd_buffers[i], &command_buffer_begin_info)); + + /* + Setup the strided device address regions pointing at the shader identifiers in the shader binding table + */ + + const uint32_t handle_size_aligned = aligned_size(ray_tracing_pipeline_properties.shaderGroupHandleSize, ray_tracing_pipeline_properties.shaderGroupHandleAlignment); + + VkStridedDeviceAddressRegionKHR raygen_shader_sbt_entry{}; + raygen_shader_sbt_entry.deviceAddress = raygen_shader_binding_table->get_device_address(); + raygen_shader_sbt_entry.stride = handle_size_aligned; + raygen_shader_sbt_entry.size = handle_size_aligned; + + VkStridedDeviceAddressRegionKHR miss_shader_sbt_entry{}; + miss_shader_sbt_entry.deviceAddress = miss_shader_binding_table->get_device_address(); + miss_shader_sbt_entry.stride = handle_size_aligned; + miss_shader_sbt_entry.size = handle_size_aligned * 2; + + VkStridedDeviceAddressRegionKHR hit_shader_sbt_entry{}; + hit_shader_sbt_entry.deviceAddress = hit_shader_binding_table->get_device_address(); + hit_shader_sbt_entry.stride = handle_size_aligned; + hit_shader_sbt_entry.size = handle_size_aligned; + + VkStridedDeviceAddressRegionKHR callable_shader_sbt_entry{}; + + /* + Dispatch the ray tracing commands + */ + vkCmdBindPipeline(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline); + vkCmdBindDescriptorSets(draw_cmd_buffers[i], VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pipeline_layout, 0, 1, &descriptor_set, 0, 0); + + vkCmdTraceRaysKHR( + draw_cmd_buffers[i], + &raygen_shader_sbt_entry, + &miss_shader_sbt_entry, + &hit_shader_sbt_entry, + &callable_shader_sbt_entry, + width, + height, + 1); + + /* + Copy ray tracing output to swap chain image + */ + + // Prepare current swap chain image as transfer destination + vkb::set_image_layout( + draw_cmd_buffers[i], + get_render_context().get_swapchain().get_images()[i], + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + subresource_range); + + // Prepare ray tracing output image as transfer source + vkb::set_image_layout( + draw_cmd_buffers[i], + storage_image.image, + VK_IMAGE_LAYOUT_GENERAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + subresource_range); + + VkImageCopy copy_region{}; + copy_region.srcSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + copy_region.srcOffset = {0, 0, 0}; + copy_region.dstSubresource = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1}; + copy_region.dstOffset = {0, 0, 0}; + copy_region.extent = {width, height, 1}; + vkCmdCopyImage(draw_cmd_buffers[i], storage_image.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + get_render_context().get_swapchain().get_images()[i], VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_region); + + // Transition swap chain image back for presentation + vkb::set_image_layout(draw_cmd_buffers[i], + get_render_context().get_swapchain().get_images()[i], + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + subresource_range); + + // Transition ray tracing output image back to general layout + vkb::set_image_layout(draw_cmd_buffers[i], + storage_image.image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_GENERAL, + subresource_range); + VK_CHECK(vkEndCommandBuffer(draw_cmd_buffers[i])); + } +} + +void RaytracingReflection::update_uniform_buffers() +{ + auto mat = camera.matrices.perspective; + mat[1][1] *= -1; // Flipping Y axis + + uniform_data.proj_inverse = glm::inverse(mat); + uniform_data.view_inverse = glm::inverse(camera.matrices.view); + ubo->convert_and_update(uniform_data); +} + +bool RaytracingReflection::prepare(vkb::Platform &platform) +{ + if (!ApiVulkanSample::prepare(platform)) + { + return false; + } + + // This sample copies the ray traced output to the swap chain image, so we need to enable the required image usage flags + std::set image_usage_flags = {VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_IMAGE_USAGE_TRANSFER_DST_BIT}; + get_render_context().update_swapchain(image_usage_flags); + + // Get the ray tracing pipeline properties, which we'll need later on in the sample + VkPhysicalDeviceProperties2 device_properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; + device_properties.pNext = &ray_tracing_pipeline_properties; + vkGetPhysicalDeviceProperties2(get_device().get_gpu().get_handle(), &device_properties); + + // Get the acceleration structure features, which we'll need later on in the sample + VkPhysicalDeviceFeatures2 device_features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; + device_features.pNext = &acceleration_structure_features; + vkGetPhysicalDeviceFeatures2(get_device().get_gpu().get_handle(), &device_features); + + camera.type = vkb::CameraType::LookAt; + camera.set_perspective(60.0f, (float) width / (float) height, 0.1f, 512.0f); + camera.set_rotation(glm::vec3(0.0f, 0.0f, 0.0f)); + camera.set_translation(glm::vec3(0.0f, 0.0f, -2.5f)); + + create_storage_image(); + create_scene(); + create_uniform_buffer(); + create_ray_tracing_pipeline(); + create_shader_binding_tables(); + create_descriptor_sets(); + build_command_buffers(); + prepared = true; + return true; +} + +void RaytracingReflection::draw() +{ + ApiVulkanSample::prepare_frame(); + submit_info.commandBufferCount = 1; + submit_info.pCommandBuffers = &draw_cmd_buffers[current_buffer]; + VK_CHECK(vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE)); + ApiVulkanSample::submit_frame(); +} + +void RaytracingReflection::render(float delta_time) +{ + if (!prepared) + return; + draw(); + if (camera.updated) + update_uniform_buffers(); +} + +std::unique_ptr create_ray_tracing_reflection() +{ + return std::make_unique(); +} diff --git a/samples/extensions/ray_tracing_reflection/ray_tracing_reflection.h b/samples/extensions/ray_tracing_reflection/ray_tracing_reflection.h new file mode 100644 index 000000000..ccadcac82 --- /dev/null +++ b/samples/extensions/ray_tracing_reflection/ray_tracing_reflection.h @@ -0,0 +1,139 @@ +/* + * Copyright (c) 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 + */ + +/* + * More complex example for hardware accelerated ray tracing using VK_KHR_ray_tracing_pipeline and VK_KHR_acceleration_structure + */ + +#pragma once + +#include "api_vulkan_sample.h" +#include "glsl_compiler.h" + +struct ObjMaterial +{ + glm::vec3 diffuse{0.7f, 0.7f, 0.7f}; + glm::vec3 specular{0.7f, 0.7f, 0.7f}; + float shininess{0.f}; +}; + +struct ObjVertex +{ + glm::vec3 pos; + glm::vec3 nrm; +}; + +struct ObjModelCpu +{ + std::vector vertices; + std::vector indices; + std::vector mat_index; +}; + +struct ObjModelGpu +{ + uint32_t nb_indices{0}; + uint32_t nb_vertices{0}; + std::unique_ptr vertex_buffer; // Device buffer of all 'Vertex' + std::unique_ptr index_buffer; // Device buffer of the indices forming triangles + std::unique_ptr mat_color_buffer; // Device buffer of array of 'Wavefront material' + std::unique_ptr mat_index_buffer; // Device buffer of array of 'Wavefront material' +}; + +class RaytracingReflection : public ApiVulkanSample +{ + struct AccelerationStructure + { + VkAccelerationStructureKHR handle; + std::unique_ptr buffer; + }; + + public: + VkPhysicalDeviceRayTracingPipelinePropertiesKHR ray_tracing_pipeline_properties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR}; + VkPhysicalDeviceAccelerationStructureFeaturesKHR acceleration_structure_features{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR}; + + std::vector bottom_level_acceleration_structure; + AccelerationStructure top_level_acceleration_structure; + + std::vector obj_models; // Array of objects and instances in the scene + std::vector shader_groups; // Shader groups + + // Shading Binding Table + std::unique_ptr raygen_shader_binding_table; + std::unique_ptr miss_shader_binding_table; + std::unique_ptr hit_shader_binding_table; + + struct StorageImage + { + VkDeviceMemory memory{nullptr}; + VkImage image{nullptr}; + VkImageView view{nullptr}; + VkFormat format{VK_FORMAT_UNDEFINED}; + uint32_t width{0}; + uint32_t height{0}; + } storage_image; + + struct UniformData + { + glm::mat4 view_inverse; + glm::mat4 proj_inverse; + } uniform_data; + std::unique_ptr ubo; + + struct ObjBuffers + { + VkDeviceAddress vertices; + VkDeviceAddress indices; + VkDeviceAddress materials; + VkDeviceAddress materialIndices; + } obj_buffers; + std::unique_ptr scene_desc; + + VkPipeline pipeline{nullptr}; + VkPipelineLayout pipeline_layout{nullptr}; + VkDescriptorSet descriptor_set{nullptr}; + VkDescriptorSetLayout descriptor_set_layout{nullptr}; + + RaytracingReflection(); + ~RaytracingReflection(); + + void create_storage_image(); + void create_bottom_level_acceleration_structure(ObjModelGpu &obj_model); + void create_top_level_acceleration_structure(std::vector &blas_instances); + void create_model(ObjModelCpu &obj, const std::vector &materials); + auto create_blas_instance(uint32_t blas_id, glm::mat4 &mat); + void delete_acceleration_structure(AccelerationStructure &acceleration_structure); + void create_scene(); + + void create_buffer_references(); + + void create_shader_binding_tables(); + void create_descriptor_sets(); + void create_ray_tracing_pipeline(); + void create_uniform_buffer(); + void update_uniform_buffers(); + void draw(); + + void build_command_buffers() override; + void request_gpu_features(vkb::PhysicalDevice &gpu) override; + bool prepare(vkb::Platform &platform) override; + void render(float delta_time) override; +}; + +std::unique_ptr create_ray_tracing_reflection(); diff --git a/shaders/ray_tracing_reflection/closesthit.rchit b/shaders/ray_tracing_reflection/closesthit.rchit new file mode 100644 index 000000000..7f1a6903b --- /dev/null +++ b/shaders/ray_tracing_reflection/closesthit.rchit @@ -0,0 +1,166 @@ +/* + * Copyright (c) 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 + */ + +#version 460 +#extension GL_EXT_ray_tracing : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_EXT_nonuniform_qualifier : enable + +#extension GL_EXT_shader_explicit_arithmetic_types_int64 : require +#extension GL_EXT_buffer_reference2 : require +struct hitPayload +{ + vec3 radiance; + vec3 attenuation; + int done; + vec3 rayOrigin; + vec3 rayDir; +}; + +layout(location = 0) rayPayloadInEXT hitPayload prd; +layout(location = 1) rayPayloadEXT bool isShadowed; + +hitAttributeEXT vec3 attribs; + +struct WaveFrontMaterial +{ + vec3 diffuse; + vec3 specular; + float shininess; +}; + +struct Vertex +{ + vec3 pos; + vec3 nrm; +}; + +struct ObjBuffers +{ + uint64_t vertices; + uint64_t indices; + uint64_t materials; + uint64_t materialIndices; +}; + +// clang-format off +layout(buffer_reference, scalar) buffer Vertices {Vertex v[]; }; // Positions of an object +layout(buffer_reference, scalar) buffer Indices {uvec3 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 = 0) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 3) buffer _scene_desc { ObjBuffers i[]; } scene_desc; +// clang-format on + +vec3 computeSpecular(WaveFrontMaterial mat, vec3 V, vec3 L, vec3 N) +{ + const float kPi = 3.14159265; + const float kShininess = max(mat.shininess, 4.0); + + // Specular + const float kEnergyConservation = (2.0 + kShininess) / (2.0 * kPi); + V = normalize(-V); + vec3 R = reflect(-L, N); + float specular = kEnergyConservation * pow(max(dot(V, R), 0.0), kShininess); + + return vec3(mat.specular * specular); +} + +void main() +{ + // When contructing the TLAS, we stored the model id in InstanceCustomIndexEXT, so the + // the instance can quickly have access to the data + + // Object data + ObjBuffers objResource = scene_desc.i[gl_InstanceCustomIndexEXT]; + MatIndices matIndices = MatIndices(objResource.materialIndices); + Materials materials = Materials(objResource.materials); + Indices indices = Indices(objResource.indices); + Vertices vertices = Vertices(objResource.vertices); + + // Retrieve the material used on this triangle 'PrimitiveID' + int mat_idx = matIndices.i[gl_PrimitiveID]; + WaveFrontMaterial mat = materials.m[mat_idx]; // Material for this triangle + + // Indices of the triangle + uvec3 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]; + + // Barycentric coordinates of the triangle + const vec3 barycentrics = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y); + + // Computing the normal at hit position + vec3 N = v0.nrm.xyz * barycentrics.x + v1.nrm.xyz * barycentrics.y + v2.nrm.xyz * barycentrics.z; + N = normalize(vec3(N.xyz * gl_WorldToObjectEXT)); // Transforming the normal to world space + + // Computing the coordinates of the hit position + vec3 P = v0.pos.xyz * barycentrics.x + v1.pos.xyz * barycentrics.y + v2.pos.xyz * barycentrics.z; + P = vec3(gl_ObjectToWorldEXT * vec4(P, 1.0)); // Transforming the position to world space + + // Hardocded (to) light direction + vec3 L = normalize(vec3(1, 1, 1)); + + float NdotL = dot(N, L); + + // Fake Lambertian to avoid black + vec3 diffuse = mat.diffuse * max(NdotL, 0.3); + vec3 specular = vec3(0); + + // Tracing shadow ray only if the light is visible from the surface + if (NdotL > 0) + { + float tMin = 0.001; + float tMax = 1e32; // infinite + vec3 origin = P; + 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) + diffuse *= 0.3; + else + // Add specular only if not in shadow + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, N); + } + prd.radiance = (diffuse + specular) * (1 - mat.shininess) * prd.attenuation; + + // Reflect + vec3 rayDir = reflect(gl_WorldRayDirectionEXT, N); + prd.attenuation *= vec3(mat.shininess); + prd.rayOrigin = P; + prd.rayDir = rayDir; +} diff --git a/shaders/ray_tracing_reflection/miss.rmiss b/shaders/ray_tracing_reflection/miss.rmiss new file mode 100644 index 000000000..12848e819 --- /dev/null +++ b/shaders/ray_tracing_reflection/miss.rmiss @@ -0,0 +1,38 @@ +/* + * Copyright (c) 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 + */ + +#version 460 +#extension GL_EXT_ray_tracing : enable + +struct hitPayload +{ + vec3 radiance; + vec3 attenuation; + int done; + vec3 rayOrigin; + vec3 rayDir; +}; + +layout(location = 0) rayPayloadInEXT hitPayload prd; + +void main() +{ + prd.radiance = vec3(0.3) * prd.attenuation; + prd.done = 1; +} diff --git a/shaders/ray_tracing_reflection/missShadow.rmiss b/shaders/ray_tracing_reflection/missShadow.rmiss new file mode 100644 index 000000000..8825d317f --- /dev/null +++ b/shaders/ray_tracing_reflection/missShadow.rmiss @@ -0,0 +1,28 @@ +/* + * Copyright (c) 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 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 1) rayPayloadInEXT bool isShadowed; + +void main() +{ + isShadowed = false; +} diff --git a/shaders/ray_tracing_reflection/raygen.rgen b/shaders/ray_tracing_reflection/raygen.rgen new file mode 100644 index 000000000..b3368fe41 --- /dev/null +++ b/shaders/ray_tracing_reflection/raygen.rgen @@ -0,0 +1,73 @@ +/* + * Copyright (c) 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 + */ + +#version 460 +#extension GL_EXT_ray_tracing : enable + +layout(set = 0, binding = 0) uniform accelerationStructureEXT topLevelAS; +layout(set = 0, binding = 1, rgba8) uniform image2D image; +layout(set = 0, binding = 2) uniform CameraProperties +{ + mat4 viewInverse; + mat4 projInverse; +} +cam; + +struct hitPayload +{ + vec3 radiance; + vec3 attenuation; + int done; + vec3 rayOrigin; + vec3 rayDir; +}; + +layout(location = 0) rayPayloadEXT hitPayload prd; + +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), 0); + + float tmin = 0.001; + float tmax = 1e32; + + prd.rayOrigin = origin.xyz; + prd.rayDir = direction.xyz; + prd.radiance = vec3(0.0); + prd.attenuation = vec3(1.0); + prd.done = 0; + + vec3 hitValue = vec3(0); + + for (int depth = 0; depth < 64; depth++) + { + traceRayEXT(topLevelAS, gl_RayFlagsOpaqueEXT, 0xff, 0, 0, 0, prd.rayOrigin, tmin, prd.rayDir, tmax, 0); + hitValue += prd.radiance; + if (prd.done == 1 || length(prd.attenuation) < 0.1) + break; + } + + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(hitValue, 0.0)); +}