diff --git a/examples/computeraytracing/computeraytracing.cpp b/examples/computeraytracing/computeraytracing.cpp index c0f6badc..f6953f09 100644 --- a/examples/computeraytracing/computeraytracing.cpp +++ b/examples/computeraytracing/computeraytracing.cpp @@ -1,5 +1,11 @@ /* -* Vulkan Example - Compute shader ray tracing +* Vulkan Example - Compute shader based ray tracing +* +* This samples implements a basic ray tracer with materials and reflections using a compute shader +* Shader storage buffers are used to pass geometry information for spheres and planes to the computer shader +* The compute shader then uses these as the scene geometry for ray tracing and outputs the results to a storage image +* The graphics part of the sample then displays that image full screen +* Not to be confused with actual hardware accelerated ray tracing * * Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * @@ -8,44 +14,37 @@ #include "vulkanexamplebase.h" -#if defined(__ANDROID__) -#define TEX_DIM 1024 -#else -#define TEX_DIM 2048 -#endif - class VulkanExample : public VulkanExampleBase { public: - vks::Texture textureComputeTarget; + // The compute shader will store the ray traced output to a storage image + vks::Texture storageImage{}; - // Resources for the graphics part of the example - struct { - VkDescriptorSetLayout descriptorSetLayout; // Raytraced image display shader binding layout - VkDescriptorSet descriptorSetPreCompute; // Raytraced image display shader bindings before compute shader image manipulation - VkDescriptorSet descriptorSet; // Raytraced image display shader bindings after compute shader image manipulation - VkPipeline pipeline; // Raytraced image display pipeline - VkPipelineLayout pipelineLayout; // Layout of the graphics pipeline + // Resources for the graphics part of the example. The graphics pipeline simply displays the compute shader output + struct Graphics { + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; + VkPipeline pipeline{ VK_NULL_HANDLE }; + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; } graphics; // Resources for the compute part of the example - struct { - struct { - vks::Buffer spheres; // (Shader) storage buffer object with scene spheres - vks::Buffer planes; // (Shader) storage buffer object with scene planes - } storageBuffers; - vks::Buffer uniformBuffer; // Uniform buffer object containing scene data - VkQueue queue; // Separate queue for compute commands (queue family may differ from the one used for graphics) - VkCommandPool commandPool; // Use a separate command pool (queue family may differ from the one used for graphics) - VkCommandBuffer commandBuffer; // Command buffer storing the dispatch commands and barriers - VkFence fence; // Synchronization fence to avoid rewriting compute CB if still in use - VkDescriptorSetLayout descriptorSetLayout; // Compute shader binding layout - VkDescriptorSet descriptorSet; // Compute shader bindings - VkPipelineLayout pipelineLayout; // Layout of the compute pipeline - VkPipeline pipeline; // Compute raytracing pipeline - struct UBOCompute { // Compute shader uniform block object + struct Compute { + // Object properties for planes and spheres are passed via a shade storage buffer + // There is no vertex data, the compute shader calculates the primitives on the fly + vks::Buffer objectStorageBuffer; + vks::Buffer uniformBuffer; // Uniform buffer object containing scene parameters + VkQueue queue{ VK_NULL_HANDLE }; // Separate queue for compute commands (queue family may differ from the one used for graphics) + VkCommandPool commandPool{ VK_NULL_HANDLE }; // Use a separate command pool (queue family may differ from the one used for graphics) + VkCommandBuffer commandBuffer{ VK_NULL_HANDLE }; // Command buffer storing the dispatch commands and barriers + VkFence fence{ VK_NULL_HANDLE }; // Synchronization fence to avoid rewriting compute CB if still in use + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; // Compute shader binding layout + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; // Compute shader bindings + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; // Layout of the compute pipeline + VkPipeline pipeline{ VK_NULL_HANDLE }; // Compute raytracing pipeline + struct UniformDataCompute { // Compute shader uniform block object glm::vec3 lightPos; - float aspectRatio; // Aspect ratio of the viewport + float aspectRatio{ 1.0f }; glm::vec4 fogColor = glm::vec4(0.0f); struct { glm::vec3 pos = glm::vec3(0.0f, 0.0f, 4.0f); @@ -53,33 +52,31 @@ public: float fov = 10.0f; } camera; glm::mat4 _pad; - } ubo; + } uniformData; } compute; - // SSBO sphere declaration - struct Sphere { // Shader uses std140 layout (so we only use vec4 instead of vec3) - glm::vec3 pos; - float radius; - glm::vec3 diffuse; - float specular; - uint32_t id; // Id used to identify sphere for raytracing - glm::ivec3 _pad; + // Definitions for scene objects + // The sample uses spheres and planes that are passed to the compute shader via a shader storage buffer + // The computer shader uses the object type to select different calculations + enum class SceneObjectType { Sphere = 0, Plane = 1 }; + // Spheres and planes are described by different properties, we use a union for this + union SceneObjectProperty { + glm::vec4 positionAndRadius; + glm::vec4 normalAndDistance; }; - - // SSBO plane declaration - struct Plane { - glm::vec3 normal; - float distance; + struct SceneObject { + SceneObjectProperty objectProperties; glm::vec3 diffuse; - float specular; - uint32_t id; - glm::ivec3 _pad; + float specular{ 1.0f }; + uint32_t id{ 0 }; + uint32_t objectType{ 0 }; + // Due to alignment rules we need to pad to make the element align at 16-bytes + glm::ivec2 _pad; }; VulkanExample() : VulkanExampleBase() { title = "Compute shader ray tracing"; - compute.ubo.aspectRatio = (float)width / (float)height; timerSpeed *= 0.25f; camera.type = Camera::CameraType::lookat; @@ -97,41 +94,51 @@ public: ~VulkanExample() { - // Graphics - vkDestroyPipeline(device, graphics.pipeline, nullptr); - vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); + if (device) { + // Graphics + vkDestroyPipeline(device, graphics.pipeline, nullptr); + vkDestroyPipelineLayout(device, graphics.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, graphics.descriptorSetLayout, nullptr); - // Compute - vkDestroyPipeline(device, compute.pipeline, nullptr); - vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); - vkDestroyFence(device, compute.fence, nullptr); - vkDestroyCommandPool(device, compute.commandPool, nullptr); - compute.uniformBuffer.destroy(); - compute.storageBuffers.spheres.destroy(); - compute.storageBuffers.planes.destroy(); + // Compute + vkDestroyPipeline(device, compute.pipeline, nullptr); + vkDestroyPipelineLayout(device, compute.pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, compute.descriptorSetLayout, nullptr); + vkDestroyFence(device, compute.fence, nullptr); + vkDestroyCommandPool(device, compute.commandPool, nullptr); + compute.uniformBuffer.destroy(); + compute.objectStorageBuffer.destroy(); - textureComputeTarget.destroy(); + storageImage.destroy(); + } } - // Prepare a texture target that is used to store compute shader calculations - void prepareTextureTarget(vks::Texture *tex, uint32_t width, uint32_t height, VkFormat format) - { + // Prepare a storage image that is used to store the compute shader ray tracing output + void prepareStorageImage() + { +#if defined(__ANDROID__) + // Use a smaller image on Android for performance reasons + const uint32_t textureSize = 1024; +#else + const uint32_t textureSize = 2048; +#endif + + const VkFormat format = VK_FORMAT_R8G8B8A8_UNORM; + // Get device properties for the requested texture format VkFormatProperties formatProperties; vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); - // Check if requested image format supports image storage operations + // Check if requested image format supports image storage operations required for storing pixesl fromn the compute shader assert(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT); // Prepare blit target texture - tex->width = width; - tex->height = height; + storageImage.width = textureSize; + storageImage.height = textureSize; VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo(); imageCreateInfo.imageType = VK_IMAGE_TYPE_2D; imageCreateInfo.format = format; - imageCreateInfo.extent = { width, height, 1 }; + imageCreateInfo.extent = { textureSize, textureSize, 1 }; imageCreateInfo.mipLevels = 1; imageCreateInfo.arrayLayers = 1; imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT; @@ -144,23 +151,40 @@ public: VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; - VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &tex->image)); - vkGetImageMemoryRequirements(device, tex->image, &memReqs); + VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &storageImage.image)); + vkGetImageMemoryRequirements(device, storageImage.image, &memReqs); memAllocInfo.allocationSize = memReqs.size; memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &tex->deviceMemory)); - VK_CHECK_RESULT(vkBindImageMemory(device, tex->image, tex->deviceMemory, 0)); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &storageImage.deviceMemory)); + VK_CHECK_RESULT(vkBindImageMemory(device, storageImage.image, storageImage.deviceMemory, 0)); VkCommandBuffer layoutCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - - tex->imageLayout = VK_IMAGE_LAYOUT_GENERAL; - vks::tools::setImageLayout( - layoutCmd, - tex->image, - VK_IMAGE_ASPECT_COLOR_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - tex->imageLayout); - + storageImage.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + vks::tools::setImageLayout(layoutCmd, storageImage.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, storageImage.imageLayout); + // Add an initial release barrier to the graphics queue, + // so that when the compute command buffer executes for the first time + // it doesn't complain about a lack of a corresponding "release" to its "acquire" + if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) + { + VkImageMemoryBarrier imageMemoryBarrier = {}; + imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; + imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; + imageMemoryBarrier.image = storageImage.image; + imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + imageMemoryBarrier.dstAccessMask = 0; + imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; + imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; + vkCmdPipelineBarrier( + layoutCmd, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_FLAGS_NONE, + 0, nullptr, + 0, nullptr, + 1, &imageMemoryBarrier); + } vulkanDevice->flushCommandBuffer(layoutCmd, queue, true); // Create sampler @@ -177,21 +201,21 @@ public: sampler.minLod = 0.0f; sampler.maxLod = 0.0f; sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &tex->sampler)); + VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &storageImage.sampler)); // Create image view VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo(); view.viewType = VK_IMAGE_VIEW_TYPE_2D; view.format = format; view.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - view.image = tex->image; - VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &tex->view)); + view.image = storageImage.image; + VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &storageImage.view)); // Initialize a descriptor for later use - tex->descriptor.imageLayout = tex->imageLayout; - tex->descriptor.imageView = tex->view; - tex->descriptor.sampler = tex->sampler; - tex->device = vulkanDevice; + storageImage.descriptor.imageLayout = storageImage.imageLayout; + storageImage.descriptor.imageView = storageImage.view; + storageImage.descriptor.sampler = storageImage.sampler; + storageImage.device = vulkanDevice; } void buildCommandBuffers() @@ -223,7 +247,7 @@ public: imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = textureComputeTarget.image; + imageMemoryBarrier.image = storageImage.image; imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) { @@ -308,7 +332,7 @@ public: imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = textureComputeTarget.image; + imageMemoryBarrier.image = storageImage.image; imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) { @@ -330,7 +354,7 @@ public: vkCmdBindPipeline(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipeline); vkCmdBindDescriptorSets(compute.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, compute.pipelineLayout, 0, 1, &compute.descriptorSet, 0, 0); - vkCmdDispatch(compute.commandBuffer, textureComputeTarget.width / 16, textureComputeTarget.height / 16, 1); + vkCmdDispatch(compute.commandBuffer, storageImage.width / 16, storageImage.height / 16, 1); if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) { @@ -352,248 +376,120 @@ public: vkEndCommandBuffer(compute.commandBuffer); } - uint32_t currentId = 0; // Id used to identify objects by the ray tracing shader - - Sphere newSphere(glm::vec3 pos, float radius, glm::vec3 diffuse, float specular) - { - Sphere sphere; - sphere.id = currentId++; - sphere.pos = pos; - sphere.radius = radius; - sphere.diffuse = diffuse; - sphere.specular = specular; - return sphere; - } - - Plane newPlane(glm::vec3 normal, float distance, glm::vec3 diffuse, float specular) - { - Plane plane; - plane.id = currentId++; - plane.normal = normal; - plane.distance = distance; - plane.diffuse = diffuse; - plane.specular = specular; - return plane; - } - - // Setup and fill the compute shader storage buffers containing primitives for the raytraced scene + // Setup and fill the compute shader storage buffes containing object definitions for the raytraced scene void prepareStorageBuffers() { - // Spheres - std::vector spheres; - spheres.push_back(newSphere(glm::vec3(1.75f, -0.5f, 0.0f), 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f)); - spheres.push_back(newSphere(glm::vec3(0.0f, 1.0f, -0.5f), 1.0f, glm::vec3(0.65f, 0.77f, 0.97f), 32.0f)); - spheres.push_back(newSphere(glm::vec3(-1.75f, -0.75f, -0.5f), 1.25f, glm::vec3(0.9f, 0.76f, 0.46f), 32.0f)); - VkDeviceSize storageBufferSize = spheres.size() * sizeof(Sphere); + // Id used to identify objects by the ray tracing shader + uint32_t currentId = 0; - // Stage - vks::Buffer stagingBuffer; + std::vector sceneObjects{}; - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &stagingBuffer, - storageBufferSize, - spheres.data()); + // Add some spheres to the scene + //std::vector spheres; + // Lambda to simplify object creation + auto addSphere = [&sceneObjects, ¤tId](glm::vec3 pos, float radius, glm::vec3 diffuse, float specular) { + SceneObject sphere{}; + sphere.id = currentId++; + sphere.objectProperties.positionAndRadius = glm::vec4(pos, radius); + sphere.diffuse = diffuse; + sphere.specular = specular; + sphere.objectType = (uint32_t)SceneObjectType::Sphere; + sceneObjects.push_back(sphere); + }; + + auto addPlane = [&sceneObjects, ¤tId](glm::vec3 normal, float distance, glm::vec3 diffuse, float specular) { + SceneObject plane{}; + plane.id = currentId++; + plane.objectProperties.normalAndDistance = glm::vec4(normal, distance); + plane.diffuse = diffuse; + plane.specular = specular; + plane.objectType = (uint32_t)SceneObjectType::Plane; + sceneObjects.push_back(plane); + }; + + addSphere(glm::vec3(1.75f, -0.5f, 0.0f), 1.0f, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f); + addSphere(glm::vec3(0.0f, 1.0f, -0.5f), 1.0f, glm::vec3(0.65f, 0.77f, 0.97f), 32.0f); + addSphere(glm::vec3(-1.75f, -0.75f, -0.5f), 1.25f, glm::vec3(0.9f, 0.76f, 0.46f), 32.0f); - vulkanDevice->createBuffer( - // The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &compute.storageBuffers.spheres, - storageBufferSize); - - // Copy to staging buffer - VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - VkBufferCopy copyRegion = {}; - copyRegion.size = storageBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.spheres.buffer, 1, ©Region); - vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); - - // Planes - std::vector planes; const float roomDim = 4.0f; - planes.push_back(newPlane(glm::vec3(0.0f, 1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(0.0f, -1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(0.0f, 0.0f, 1.0f), roomDim, glm::vec3(1.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(0.0f, 0.0f, -1.0f), roomDim, glm::vec3(0.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(-1.0f, 0.0f, 0.0f), roomDim, glm::vec3(1.0f, 0.0f, 0.0f), 32.0f)); - planes.push_back(newPlane(glm::vec3(1.0f, 0.0f, 0.0f), roomDim, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f)); - storageBufferSize = planes.size() * sizeof(Plane); + addPlane(glm::vec3(0.0f, 1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f); + addPlane(glm::vec3(0.0f, -1.0f, 0.0f), roomDim, glm::vec3(1.0f), 32.0f); + addPlane(glm::vec3(0.0f, 0.0f, 1.0f), roomDim, glm::vec3(1.0f), 32.0f); + addPlane(glm::vec3(0.0f, 0.0f, -1.0f), roomDim, glm::vec3(0.0f), 32.0f); + addPlane(glm::vec3(-1.0f, 0.0f, 0.0f), roomDim, glm::vec3(1.0f, 0.0f, 0.0f), 32.0f); + addPlane(glm::vec3(1.0f, 0.0f, 0.0f), roomDim, glm::vec3(0.0f, 1.0f, 0.0f), 32.0f); - // Stage - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_TRANSFER_SRC_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &stagingBuffer, - storageBufferSize, - planes.data()); + VkDeviceSize storageBufferSize = sceneObjects.size() * sizeof(SceneObject); - vulkanDevice->createBuffer( - // The SSBO will be used as a storage buffer for the compute pipeline and as a vertex buffer in the graphics pipeline - VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, - &compute.storageBuffers.planes, - storageBufferSize); - - // Copy to staging buffer - copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); - copyRegion.size = storageBufferSize; - vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.storageBuffers.planes.buffer, 1, ©Region); - // Add an initial release barrier to the graphics queue, - // so that when the compute command buffer executes for the first time - // it doesn't complain about a lack of a corresponding "release" to its "acquire" - if (vulkanDevice->queueFamilyIndices.graphics != vulkanDevice->queueFamilyIndices.compute) - { - VkImageMemoryBarrier imageMemoryBarrier = {}; - imageMemoryBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; - imageMemoryBarrier.image = textureComputeTarget.image; - imageMemoryBarrier.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; - imageMemoryBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - imageMemoryBarrier.dstAccessMask = 0; - imageMemoryBarrier.srcQueueFamilyIndex = vulkanDevice->queueFamilyIndices.graphics; - imageMemoryBarrier.dstQueueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; - vkCmdPipelineBarrier( - copyCmd, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_FLAGS_NONE, - 0, nullptr, - 0, nullptr, - 1, &imageMemoryBarrier); - } + // Copy the data to the device + vks::Buffer stagingBuffer; + vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, storageBufferSize, sceneObjects.data()); + vulkanDevice->createBuffer(VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &compute.objectStorageBuffer, storageBufferSize); + VkCommandBuffer copyCmd = vulkanDevice->createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + VkBufferCopy copyRegion = { 0, 0, storageBufferSize}; + vkCmdCopyBuffer(copyCmd, stagingBuffer.buffer, compute.objectStorageBuffer.buffer, 1, ©Region); vulkanDevice->flushCommandBuffer(copyCmd, queue, true); - - stagingBuffer.destroy(); } + // The descriptor pool will be shared between graphics and compute void setupDescriptorPool() { - std::vector poolSizes = - { - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), // Compute UBO - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4), // Graphics image samplers - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1), // Storage image for ray traced image output - vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2), // Storage buffer for the scene primitives + // @todo: probably wrong + std::vector poolSizes = { + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1), + vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2), }; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); - + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 3); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } - void setupDescriptorSetLayout() + // Prepare the graphics resources used to display the ray traced output of the compute shader + void prepareGraphics() { - std::vector setLayoutBindings = - { - // Binding 0 : Fragment shader image sampler - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 0) - }; + // Setup descriptors + // The graphics pipeline uses one set and one binding + // Binding 0: Storage image with raytraced output as a sampled image for displaying it + + std::vector setLayoutBindings = { + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 0) + }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &graphics.descriptorSetLayout)); + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &graphics.descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); + std::vector writeDescriptorSets = { + vks::initializers::writeDescriptorSet(graphics.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &storageImage.descriptor) + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); + + // Layout VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&graphics.descriptorSetLayout, 1); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &graphics.pipelineLayout)); - } - void setupDescriptorSet() - { - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo( - descriptorPool, - &graphics.descriptorSetLayout, - 1); - - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &graphics.descriptorSet)); - - std::vector writeDescriptorSets = - { - // Binding 0 : Fragment shader texture sampler - vks::initializers::writeDescriptorSet( - graphics.descriptorSet, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 0, - &textureComputeTarget.descriptor) - }; - - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { - VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = - vks::initializers::pipelineInputAssemblyStateCreateInfo( - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, - 0, - VK_FALSE); - - VkPipelineRasterizationStateCreateInfo rasterizationState = - vks::initializers::pipelineRasterizationStateCreateInfo( - VK_POLYGON_MODE_FILL, - VK_CULL_MODE_FRONT_BIT, - VK_FRONT_FACE_COUNTER_CLOCKWISE, - 0); - - VkPipelineColorBlendAttachmentState blendAttachmentState = - vks::initializers::pipelineColorBlendAttachmentState( - 0xf, - VK_FALSE); - - VkPipelineColorBlendStateCreateInfo colorBlendState = - vks::initializers::pipelineColorBlendStateCreateInfo( - 1, - &blendAttachmentState); - - VkPipelineDepthStencilStateCreateInfo depthStencilState = - vks::initializers::pipelineDepthStencilStateCreateInfo( - VK_FALSE, - VK_FALSE, - VK_COMPARE_OP_LESS_OR_EQUAL); - - VkPipelineViewportStateCreateInfo viewportState = - vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); - - VkPipelineMultisampleStateCreateInfo multisampleState = - vks::initializers::pipelineMultisampleStateCreateInfo( - VK_SAMPLE_COUNT_1_BIT, - 0); - - std::vector dynamicStateEnables = { - VK_DYNAMIC_STATE_VIEWPORT, - VK_DYNAMIC_STATE_SCISSOR - }; - VkPipelineDynamicStateCreateInfo dynamicState = - vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); - - // Display pipeline + // Pipeline + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE); + VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_FRONT_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); + VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); + VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); + VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_FALSE, VK_FALSE, VK_COMPARE_OP_LESS_OR_EQUAL); + VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0); + std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; + VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables); std::array shaderStages; shaderStages[0] = loadShader(getShadersPath() + "computeraytracing/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getShadersPath() + "computeraytracing/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); - VkGraphicsPipelineCreateInfo pipelineCreateInfo = - vks::initializers::pipelineCreateInfo( - graphics.pipelineLayout, - renderPass, - 0); - VkPipelineVertexInputStateCreateInfo emptyInputState{}; emptyInputState.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; - emptyInputState.vertexAttributeDescriptionCount = 0; - emptyInputState.pVertexAttributeDescriptions = nullptr; - emptyInputState.vertexBindingDescriptionCount = 0; - emptyInputState.pVertexBindingDescriptions = nullptr; - pipelineCreateInfo.pVertexInputState = &emptyInputState; + VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(graphics.pipelineLayout, renderPass, 0); + pipelineCreateInfo.pVertexInputState = &emptyInputState; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; @@ -604,11 +500,10 @@ public: pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); pipelineCreateInfo.pStages = shaderStages.data(); pipelineCreateInfo.renderPass = renderPass; - VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &graphics.pipeline)); } - // Prepare the compute pipeline that generates the ray traced image + // Prepare the compute resources that generates the ray traced image void prepareCompute() { // Create a compute capable device queue @@ -622,89 +517,39 @@ public: queueCreateInfo.queueCount = 1; vkGetDeviceQueue(device, vulkanDevice->queueFamilyIndices.compute, 0, &compute.queue); + // Setup descriptors + + // The compute pipeline uses one set and four bindings + // Binding 0: Storage image for raytraced output + // Binding 1: Uniform buffer with parameters + // Binding 2: Shader storage buffer with scene object definitions + std::vector setLayoutBindings = { - // Binding 0: Storage image (raytraced output) - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - VK_SHADER_STAGE_COMPUTE_BIT, - 0), - // Binding 1: Uniform buffer block - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 1), - // Binding 1: Shader storage buffer for the spheres - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 2), - // Binding 1: Shader storage buffer for the planes - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - VK_SHADER_STAGE_COMPUTE_BIT, - 3) + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, VK_SHADER_STAGE_COMPUTE_BIT, 0), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 1), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, VK_SHADER_STAGE_COMPUTE_BIT, 2), }; - - VkDescriptorSetLayoutCreateInfo descriptorLayout = - vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); - + VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &compute.descriptorSetLayout)); - VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = - vks::initializers::pipelineLayoutCreateInfo( - &compute.descriptorSetLayout, - 1); - - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); - - VkDescriptorSetAllocateInfo allocInfo = - vks::initializers::descriptorSetAllocateInfo( - descriptorPool, - &compute.descriptorSetLayout, - 1); - + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &compute.descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &compute.descriptorSet)); - - std::vector computeWriteDescriptorSets = - { - // Binding 0: Output storage image - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - 0, - &textureComputeTarget.descriptor), - // Binding 1: Uniform buffer block - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 1, - &compute.uniformBuffer.descriptor), - // Binding 2: Shader storage buffer for the spheres - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - 2, - &compute.storageBuffers.spheres.descriptor), - // Binding 2: Shader storage buffer for the planes - vks::initializers::writeDescriptorSet( - compute.descriptorSet, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, - 3, - &compute.storageBuffers.planes.descriptor) + std::vector computeWriteDescriptorSets = { + vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 0, &storageImage.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &compute.uniformBuffer.descriptor), + vks::initializers::writeDescriptorSet(compute.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, &compute.objectStorageBuffer.descriptor), }; - vkUpdateDescriptorSets(device, static_cast(computeWriteDescriptorSets.size()), computeWriteDescriptorSets.data(), 0, nullptr); - // Create compute shader pipelines - VkComputePipelineCreateInfo computePipelineCreateInfo = - vks::initializers::computePipelineCreateInfo( - compute.pipelineLayout, - 0); + // Create the compute shader pipeline + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&compute.descriptorSetLayout, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &compute.pipelineLayout)); + VkComputePipelineCreateInfo computePipelineCreateInfo = vks::initializers::computePipelineCreateInfo(compute.pipelineLayout, 0); computePipelineCreateInfo.stage = loadShader(getShadersPath() + "computeraytracing/raytracing.comp.spv", VK_SHADER_STAGE_COMPUTE_BIT); VK_CHECK_RESULT(vkCreateComputePipelines(device, pipelineCache, 1, &computePipelineCreateInfo, nullptr, &compute.pipeline)); - // Separate command pool as queue family for compute may be different than graphics + // Separate command pool as queue family for compute may be different from the graphics one VkCommandPoolCreateInfo cmdPoolInfo = {}; cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; cmdPoolInfo.queueFamilyIndex = vulkanDevice->queueFamilyIndices.compute; @@ -712,12 +557,7 @@ public: VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &compute.commandPool)); // Create a command buffer for compute operations - VkCommandBufferAllocateInfo cmdBufAllocateInfo = - vks::initializers::commandBufferAllocateInfo( - compute.commandPool, - VK_COMMAND_BUFFER_LEVEL_PRIMARY, - 1); - + VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(compute.commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1); VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &compute.commandBuffer)); // Fence for compute CB sync @@ -728,30 +568,37 @@ public: buildComputeCommandBuffer(); } - // Prepare and initialize uniform buffer containing shader uniforms void prepareUniformBuffers() { // Compute shader parameter uniform buffer block - vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &compute.uniformBuffer, - sizeof(compute.ubo)); - - updateUniformBuffers(); + vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &compute.uniformBuffer, sizeof(Compute::UniformDataCompute)); } void updateUniformBuffers() { - compute.ubo.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f; - compute.ubo.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f; - compute.ubo.lightPos.z = 0.0f + cos(glm::radians(timer * 360.0f)) * 2.0f; - compute.ubo.camera.pos = camera.position * -1.0f; + compute.uniformData.aspectRatio = (float)width / (float)height; + compute.uniformData.lightPos.x = 0.0f + sin(glm::radians(timer * 360.0f)) * cos(glm::radians(timer * 360.0f)) * 2.0f; + compute.uniformData.lightPos.y = 0.0f + sin(glm::radians(timer * 360.0f)) * 2.0f; + compute.uniformData.lightPos.z = 0.0f + cos(glm::radians(timer * 360.0f)) * 2.0f; + compute.uniformData.camera.pos = camera.position * -1.0f; VK_CHECK_RESULT(compute.uniformBuffer.map()); - memcpy(compute.uniformBuffer.mapped, &compute.ubo, sizeof(compute.ubo)); + memcpy(compute.uniformBuffer.mapped, &compute.uniformData, sizeof(Compute::UniformDataCompute)); compute.uniformBuffer.unmap(); } + void prepare() + { + VulkanExampleBase::prepare(); + prepareStorageImage(); + prepareStorageBuffers(); + prepareUniformBuffers(); + setupDescriptorPool(); + prepareGraphics(); + prepareCompute(); + buildCommandBuffers(); + prepared = true; + } + void draw() { // Submit compute commands @@ -775,36 +622,12 @@ public: VulkanExampleBase::submitFrame(); } - void prepare() - { - VulkanExampleBase::prepare(); - prepareTextureTarget(&textureComputeTarget, TEX_DIM, TEX_DIM, VK_FORMAT_R8G8B8A8_UNORM); - prepareStorageBuffers(); - prepareUniformBuffers(); - setupDescriptorSetLayout(); - preparePipelines(); - setupDescriptorPool(); - setupDescriptorSet(); - prepareCompute(); - buildCommandBuffers(); - prepared = true; - } - virtual void render() { if (!prepared) return; - draw(); - if (!paused) - { - updateUniformBuffers(); - } - } - - virtual void viewChanged() - { - compute.ubo.aspectRatio = (float)width / (float)height; updateUniformBuffers(); + draw(); } }; diff --git a/shaders/glsl/computeraytracing/raytracing.comp b/shaders/glsl/computeraytracing/raytracing.comp index 051b80bd..256560d4 100644 --- a/shaders/glsl/computeraytracing/raytracing.comp +++ b/shaders/glsl/computeraytracing/raytracing.comp @@ -1,3 +1,5 @@ +// Copyright 2023 Sascha Willems + // Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org) #version 450 @@ -13,6 +15,9 @@ layout (binding = 0, rgba8) uniform writeonly image2D resultImage; #define REFLECTIONSTRENGTH 0.4 #define REFLECTIONFALLOFF 0.5 +#define SceneObjectTypeSphere 0 +#define SceneObjectTypePlane 1 + struct Camera { vec3 pos; @@ -29,32 +34,18 @@ layout (binding = 1) uniform UBO mat4 rotMat; } ubo; -struct Sphere +struct SceneObject { - vec3 pos; - float radius; + vec4 objectProperties; vec3 diffuse; float specular; int id; + int objectType; }; -struct Plane +layout (std140, binding = 2) buffer SceneObjects { - vec3 normal; - float distance; - vec3 diffuse; - float specular; - int id; -}; - -layout (std140, binding = 2) buffer Spheres -{ - Sphere spheres[ ]; -}; - -layout (std140, binding = 3) buffer Planes -{ - Plane planes[ ]; + SceneObject sceneObjects[ ]; }; void reflectRay(inout vec3 rayD, in vec3 mormal) @@ -78,11 +69,11 @@ float lightSpecular(vec3 normal, vec3 lightDir, float specularFactor) // Sphere =========================================================== -float sphereIntersect(in vec3 rayO, in vec3 rayD, in Sphere sphere) +float sphereIntersect(in vec3 rayO, in vec3 rayD, in SceneObject sphere) { - vec3 oc = rayO - sphere.pos; + vec3 oc = rayO - sphere.objectProperties.xyz; float b = 2.0 * dot(oc, rayD); - float c = dot(oc, oc) - sphere.radius*sphere.radius; + float c = dot(oc, oc) - sphere.objectProperties.w * sphere.objectProperties.w; float h = b*b - 4.0*c; if (h < 0.0) { @@ -93,21 +84,21 @@ float sphereIntersect(in vec3 rayO, in vec3 rayD, in Sphere sphere) return t; } -vec3 sphereNormal(in vec3 pos, in Sphere sphere) +vec3 sphereNormal(in vec3 pos, in SceneObject sphere) { - return (pos - sphere.pos) / sphere.radius; + return (pos - sphere.objectProperties.xyz) / sphere.objectProperties.w; } // Plane =========================================================== -float planeIntersect(vec3 rayO, vec3 rayD, Plane plane) +float planeIntersect(vec3 rayO, vec3 rayD, SceneObject plane) { - float d = dot(rayD, plane.normal); + float d = dot(rayD, plane.objectProperties.xyz); if (d == 0.0) return 0.0; - float t = -(plane.distance + dot(rayO, plane.normal)) / d; + float t = -(plane.objectProperties.w + dot(rayO, plane.objectProperties.xyz)) / d; if (t < 0.0) return 0.0; @@ -119,40 +110,48 @@ float planeIntersect(vec3 rayO, vec3 rayD, Plane plane) int intersect(in vec3 rayO, in vec3 rayD, inout float resT) { int id = -1; + float t = -1000.0f; - for (int i = 0; i < spheres.length(); i++) + for (int i = 0; i < sceneObjects.length(); i++) { - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < resT)) + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + t = sphereIntersect(rayO, rayD, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + t = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((t > EPSILON) && (t < resT)) { - id = spheres[i].id; - resT = tSphere; + id = sceneObjects[i].id; + resT = t; } } - for (int i = 0; i < planes.length(); i++) - { - float tplane = planeIntersect(rayO, rayD, planes[i]); - if ((tplane > EPSILON) && (tplane < resT)) - { - id = planes[i].id; - resT = tplane; - } - } - return id; } float calcShadow(in vec3 rayO, in vec3 rayD, in int objectId, inout float t) { - for (int i = 0; i < spheres.length(); i++) + for (int i = 0; i < sceneObjects.length(); i++) { - if (spheres[i].id == objectId) + if (sceneObjects[i].id == objectId) continue; - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < t)) + + float tLoc = MAXLEN; + + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + tLoc = sphereIntersect(rayO, rayD, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + tLoc = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((tLoc > EPSILON) && (tLoc < t)) { - t = tSphere; + t = tLoc; return SHADOW; } } @@ -180,30 +179,22 @@ vec3 renderScene(inout vec3 rayO, inout vec3 rayD, inout int id) vec3 pos = rayO + t * rayD; vec3 lightVec = normalize(ubo.lightPos - pos); vec3 normal; - - // Planes - - // Spheres - - for (int i = 0; i < planes.length(); i++) + + for (int i = 0; i < sceneObjects.length(); i++) { - if (objectID == planes[i].id) - { - normal = planes[i].normal; + if (objectID == sceneObjects[i].id) { + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + normal = sphereNormal(pos, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + normal = sceneObjects[i].objectProperties.xyz; + } + // Lighting float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, planes[i].specular); - color = diffuse * planes[i].diffuse + specular; - } - } - - for (int i = 0; i < spheres.length(); i++) - { - if (objectID == spheres[i].id) - { - normal = sphereNormal(pos, spheres[i]); - float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, spheres[i].specular); - color = diffuse * spheres[i].diffuse + specular; + float specular = lightSpecular(normal, lightVec, sceneObjects[i].specular); + color = diffuse * sceneObjects[i].diffuse + specular; } } diff --git a/shaders/glsl/computeraytracing/raytracing.comp.spv b/shaders/glsl/computeraytracing/raytracing.comp.spv index ee022372..4b706dfe 100644 Binary files a/shaders/glsl/computeraytracing/raytracing.comp.spv and b/shaders/glsl/computeraytracing/raytracing.comp.spv differ diff --git a/shaders/hlsl/computeraytracing/raytracing.comp b/shaders/hlsl/computeraytracing/raytracing.comp index 9f53c7dc..d1842398 100644 --- a/shaders/hlsl/computeraytracing/raytracing.comp +++ b/shaders/hlsl/computeraytracing/raytracing.comp @@ -1,4 +1,5 @@ // Copyright 2020 Google LLC +// Copyright 2023 Sascha Willems // Shader is looseley based on the ray tracing coding session by Inigo Quilez (www.iquilezles.org) @@ -12,6 +13,9 @@ RWTexture2D resultImage : register(u0); #define REFLECTIONSTRENGTH 0.4 #define REFLECTIONFALLOFF 0.5 +#define SceneObjectTypeSphere 0 +#define SceneObjectTypePlane 1 + struct Camera { float3 pos; @@ -30,26 +34,16 @@ struct UBO cbuffer ubo : register(b1) { UBO ubo; } -struct Sphere +struct SceneObject { - float3 pos; - float radius; + float4 objectProperties; float3 diffuse; float specular; int id; + int objectType; }; -struct Plane -{ - float3 normal; - float distance; - float3 diffuse; - float specular; - int id; -}; - -StructuredBuffer spheres : register(t2); -StructuredBuffer planes : register(t3); +StructuredBuffer sceneObjects : register(t2); void reflectRay(inout float3 rayD, in float3 mormal) { @@ -72,11 +66,11 @@ float lightSpecular(float3 normal, float3 lightDir, float specularFactor) // Sphere =========================================================== -float sphereIntersect(in float3 rayO, in float3 rayD, in Sphere sphere) +float sphereIntersect(in float3 rayO, in float3 rayD, in SceneObject sphere) { - float3 oc = rayO - sphere.pos; + float3 oc = rayO - sphere.objectProperties.xyz; float b = 2.0 * dot(oc, rayD); - float c = dot(oc, oc) - sphere.radius*sphere.radius; + float c = dot(oc, oc) - sphere.objectProperties.w * sphere.objectProperties.w; float h = b*b - 4.0*c; if (h < 0.0) { @@ -87,21 +81,21 @@ float sphereIntersect(in float3 rayO, in float3 rayD, in Sphere sphere) return t; } -float3 sphereNormal(in float3 pos, in Sphere sphere) +float3 sphereNormal(in float3 pos, in SceneObject sphere) { - return (pos - sphere.pos) / sphere.radius; + return (pos - sphere.objectProperties.xyz) / sphere.objectProperties.w; } // Plane =========================================================== -float planeIntersect(float3 rayO, float3 rayD, Plane plane) +float planeIntersect(float3 rayO, float3 rayD, SceneObject plane) { - float d = dot(rayD, plane.normal); + float d = dot(rayD, plane.objectProperties.xyz); if (d == 0.0) return 0.0; - float t = -(plane.distance + dot(rayO, plane.normal)) / d; + float t = -(plane.objectProperties.w + dot(rayO, plane.objectProperties.xyz)) / d; if (t < 0.0) return 0.0; @@ -113,33 +107,25 @@ float planeIntersect(float3 rayO, float3 rayD, Plane plane) int intersect(in float3 rayO, in float3 rayD, inout float resT) { int id = -1; + float t = MAXLEN; - uint spheresLength; - uint spheresStride; - spheres.GetDimensions(spheresLength, spheresStride); + uint sceneObjectsLength; + uint sceneObjectsStride; + sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride); - int i; - for (i = 0; i < spheresLength; i++) - { - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < resT)) - { - id = spheres[i].id; - resT = tSphere; + for (int i = 0; i < sceneObjectsLength; i++) { + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + t = sphereIntersect(rayO, rayD, sceneObjects[i]); } - } - - uint planesLength; - uint planesStride; - planes.GetDimensions(planesLength, planesStride); - - for (i = 0; i < planesLength; i++) - { - float tplane = planeIntersect(rayO, rayD, planes[i]); - if ((tplane > EPSILON) && (tplane < resT)) + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + t = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((t > EPSILON) && (t < resT)) { - id = planes[i].id; - resT = tplane; + id = sceneObjects[i].id; + resT = t; } } @@ -148,18 +134,29 @@ int intersect(in float3 rayO, in float3 rayD, inout float resT) float calcShadow(in float3 rayO, in float3 rayD, in int objectId, inout float t) { - uint spheresLength; - uint spheresStride; - spheres.GetDimensions(spheresLength, spheresStride); + uint sceneObjectsLength; + uint sceneObjectsStride; + sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride); - for (int i = 0; i < spheresLength; i++) - { - if (spheres[i].id == objectId) + for (int i = 0; i < sceneObjectsLength; i++) { + if (sceneObjects[i].id == objectId) continue; - float tSphere = sphereIntersect(rayO, rayD, spheres[i]); - if ((tSphere > EPSILON) && (tSphere < t)) + + float tLoc = MAXLEN; + + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { - t = tSphere; + tLoc = sphereIntersect(rayO, rayD, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) + { + tLoc = planeIntersect(rayO, rayD, sceneObjects[i]); + } + if ((tLoc > EPSILON) && (tLoc < t)) + { + t = tLoc; return SHADOW; } } @@ -188,38 +185,25 @@ float3 renderScene(inout float3 rayO, inout float3 rayD, inout int id) float3 lightVec = normalize(ubo.lightPos - pos); float3 normal; - // Planes + uint sceneObjectsLength; + uint sceneObjectsStride; + sceneObjects.GetDimensions(sceneObjectsLength, sceneObjectsStride); - // Spheres - - uint planesLength; - uint planesStride; - planes.GetDimensions(planesLength, planesStride); - - int i; - for (i = 0; i < planesLength; i++) - { - if (objectID == planes[i].id) + for (int i = 0; i < sceneObjectsLength; i++) { + if (objectID == sceneObjects[i].id) { - normal = planes[i].normal; + // Sphere + if (sceneObjects[i].objectType == SceneObjectTypeSphere) { + normal = sphereNormal(pos, sceneObjects[i]); + } + // Plane + if (sceneObjects[i].objectType == SceneObjectTypePlane) { + normal = sceneObjects[i].objectProperties.xyz; + } + // Lighting float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, planes[i].specular); - color = diffuse * planes[i].diffuse + specular; - } - } - - uint spheresLength; - uint spheresStride; - spheres.GetDimensions(spheresLength, spheresStride); - - for (i = 0; i < spheresLength; i++) - { - if (objectID == spheres[i].id) - { - normal = sphereNormal(pos, spheres[i]); - float diffuse = lightDiffuse(normal, lightVec); - float specular = lightSpecular(normal, lightVec, spheres[i].specular); - color = diffuse * spheres[i].diffuse + specular; + float specular = lightSpecular(normal, lightVec, sceneObjects[i].specular); + color = diffuse * sceneObjects[i].diffuse + specular; } } diff --git a/shaders/hlsl/computeraytracing/raytracing.comp.spv b/shaders/hlsl/computeraytracing/raytracing.comp.spv index d5a9950e..ed2571b6 100644 Binary files a/shaders/hlsl/computeraytracing/raytracing.comp.spv and b/shaders/hlsl/computeraytracing/raytracing.comp.spv differ