/* * Vulkan Example - Cascaded shadow mapping for directional light sources * * Copyright (C) 2017 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ // Note: Could be simplified with a layered frame buffer using geometry shaders (not available on all devices) #include #include #include #include #include #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include #include #include "vulkanexamplebase.h" #include "VulkanBuffer.hpp" #include "VulkanModel.hpp" #define ENABLE_VALIDATION false #if defined(__ANDROID__) #define SHADOWMAP_DIM 1024 #else #define SHADOWMAP_DIM 2048 #endif #define SHADOW_MAP_CASCADE_COUNT 4 class VulkanExample : public VulkanExampleBase { public: bool displayDepthMap = false; int32_t displayDepthMapCascadeIndex = 0; bool colorCascades = false; bool filterPCF = false; float cascadeSplitLambda = 1.0f; float zNear = 0.5f; float zFar = 48.0f; glm::vec3 lightPos = glm::vec3(); // Vertex layout for the models vks::VertexLayout vertexLayout = vks::VertexLayout({ vks::VERTEX_COMPONENT_POSITION, vks::VERTEX_COMPONENT_UV, vks::VERTEX_COMPONENT_COLOR, vks::VERTEX_COMPONENT_NORMAL, }); std::vector scenes; std::vector sceneNames; int32_t sceneIndex = 0; struct { vks::Buffer VS; vks::Buffer FS; } uniformBuffers; struct { glm::mat4 projection; glm::mat4 view; glm::mat4 model; glm::vec3 lightDir; } uboVS; struct { float cascadeSplits[4]; glm::mat4 cascadeViewProjMat[4]; glm::mat4 inverseViewMat; glm::vec3 lightDir; float _pad; int32_t colorCascades; } uboFS; struct { VkPipeline debugShadowMap; VkPipeline sceneShadow; VkPipeline sceneShadowPCF; } pipelines; VkPipelineLayout pipelineLayout; VkDescriptorSet descriptorSet; VkDescriptorSetLayout descriptorSetLayout; // Resources of the depth map generation pass struct DepthPass { VkRenderPass renderPass; VkCommandBuffer commandBuffer; VkSemaphore semaphore; VkPipelineLayout pipelineLayout; VkPipeline pipeline; vks::Buffer uniformBuffer; struct UniformBlock { std::array cascadeViewProjMat; } ubo; } depthPass; // Layered depth image containing the shadow cascade depths struct DepthImage { VkImage image; VkDeviceMemory mem; VkImageView view; VkSampler sampler; void destroy(VkDevice device) { vkDestroyImageView(device, view, nullptr); vkDestroyImage(device, image, nullptr); vkFreeMemory(device, mem, nullptr); vkDestroySampler(device, sampler, nullptr); } } depth; // Contains all resources required for a single shadow map cascade struct Cascade { VkFramebuffer frameBuffer; VkDescriptorSet descriptorSet; VkImageView view; float splitDepth; glm::mat4 viewProjMatrix; void destroy(VkDevice device) { vkDestroyImageView(device, view, nullptr); vkDestroyFramebuffer(device, frameBuffer, nullptr); } }; std::array cascades; VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) { title = "Cascaded shadow mapping"; timerSpeed *= 0.05f; camera.type = Camera::CameraType::firstperson; camera.movementSpeed = 2.5f; camera.setPerspective(45.0f, (float)width / (float)height, zNear, zFar); camera.setPosition(glm::vec3(2.0f, 0.375f, -1.25f)); camera.setRotation(glm::vec3(-19.0f, 42.0f, 0.0f)); settings.overlay = true; timer = 0.317028880f; paused = true; } ~VulkanExample() { for (auto cascade : cascades) { cascade.destroy(device); } depth.destroy(device); vkDestroyRenderPass(device, depthPass.renderPass, nullptr); vkDestroyPipeline(device, pipelines.debugShadowMap, nullptr); vkDestroyPipeline(device, depthPass.pipeline, nullptr); vkDestroyPipeline(device, pipelines.sceneShadow, nullptr); vkDestroyPipeline(device, pipelines.sceneShadowPCF, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyPipelineLayout(device, depthPass.pipelineLayout, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); for (auto scene : scenes) { scene.destroy(); } depthPass.uniformBuffer.destroy(); uniformBuffers.VS.destroy(); uniformBuffers.FS.destroy(); vkFreeCommandBuffers(device, cmdPool, 1, &depthPass.commandBuffer); vkDestroySemaphore(device, depthPass.semaphore, nullptr); } virtual void getEnabledFeatures() { // Depth clamp to avoid near plane clipping enabledFeatures.depthClamp = deviceFeatures.depthClamp; } // Setup resources used for the shadow map cascades void prepareShadowMaps() { VkFormat depthFormat; vks::tools::getSupportedDepthFormat(physicalDevice, &depthFormat); depthPass.commandBuffer = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false); // Create a semaphore used to synchronize offscreen rendering and usage VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo(); VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &depthPass.semaphore)); /* Depth map renderpass */ VkAttachmentDescription attachmentDescription{}; attachmentDescription.format = depthFormat; attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT; attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE; attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; VkAttachmentReference depthReference = {}; depthReference.attachment = 0; depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 0; subpass.pDepthStencilAttachment = &depthReference; // Use subpass dependencies for layout transitions std::array dependencies; dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0; dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; dependencies[0].dstStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT; dependencies[0].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; dependencies[1].srcSubpass = 0; dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL; dependencies[1].srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependencies[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; dependencies[1].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependencies[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT; dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; VkRenderPassCreateInfo renderPassCreateInfo = vks::initializers::renderPassCreateInfo(); renderPassCreateInfo.attachmentCount = 1; renderPassCreateInfo.pAttachments = &attachmentDescription; renderPassCreateInfo.subpassCount = 1; renderPassCreateInfo.pSubpasses = &subpass; renderPassCreateInfo.dependencyCount = static_cast(dependencies.size()); renderPassCreateInfo.pDependencies = dependencies.data(); VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCreateInfo, nullptr, &depthPass.renderPass)); /* Layered depth image and views */ // Layered depth image VkImageCreateInfo imageInfo = vks::initializers::imageCreateInfo(); imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.extent.width = SHADOWMAP_DIM; imageInfo.extent.height = SHADOWMAP_DIM; imageInfo.extent.depth = 1; imageInfo.mipLevels = 1; imageInfo.arrayLayers = SHADOW_MAP_CASCADE_COUNT; imageInfo.samples = VK_SAMPLE_COUNT_1_BIT; imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL; imageInfo.format = depthFormat; imageInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; VK_CHECK_RESULT(vkCreateImage(device, &imageInfo, nullptr, &depth.image)); VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(device, depth.image, &memReqs); memAlloc.allocationSize = memReqs.size; memAlloc.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &depth.mem)); VK_CHECK_RESULT(vkBindImageMemory(device, depth.image, depth.mem, 0)); // Full depth map view (all layers) VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo(); viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; viewInfo.format = depthFormat; viewInfo.subresourceRange = {}; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = SHADOW_MAP_CASCADE_COUNT; viewInfo.image = depth.image; VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &depth.view)); // One image and framebuffer per cascade for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { // Image view for this cascade's layer (inside the depth map) VkImageViewCreateInfo viewInfo = vks::initializers::imageViewCreateInfo(); viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; viewInfo.format = depthFormat; viewInfo.subresourceRange = {}; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = 1; viewInfo.subresourceRange.baseArrayLayer = i; viewInfo.subresourceRange.layerCount = 1; viewInfo.image = depth.image; VK_CHECK_RESULT(vkCreateImageView(device, &viewInfo, nullptr, &cascades[i].view)); // Framebuffer VkFramebufferCreateInfo framebufferInfo = vks::initializers::framebufferCreateInfo(); framebufferInfo.renderPass = depthPass.renderPass; framebufferInfo.attachmentCount = 1; framebufferInfo.pAttachments = &cascades[i].view; framebufferInfo.width = SHADOWMAP_DIM; framebufferInfo.height = SHADOWMAP_DIM; framebufferInfo.layers = 1; VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferInfo, nullptr, &cascades[i].frameBuffer)); } // Shared sampler for cascade deoth reads VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo(); sampler.magFilter = VK_FILTER_LINEAR; sampler.minFilter = VK_FILTER_LINEAR; sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; sampler.addressModeV = sampler.addressModeU; sampler.addressModeW = sampler.addressModeU; sampler.mipLodBias = 0.0f; sampler.maxAnisotropy = 1.0f; sampler.minLod = 0.0f; sampler.maxLod = 1.0f; sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &depth.sampler)); } void buildOffscreenCommandBuffer() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[1]; clearValues[0].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); renderPassBeginInfo.renderPass = depthPass.renderPass; renderPassBeginInfo.renderArea.offset.x = 0; renderPassBeginInfo.renderArea.offset.y = 0; renderPassBeginInfo.renderArea.extent.width = SHADOWMAP_DIM; renderPassBeginInfo.renderArea.extent.height = SHADOWMAP_DIM; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; VK_CHECK_RESULT(vkBeginCommandBuffer(depthPass.commandBuffer, &cmdBufInfo)); VkViewport viewport = vks::initializers::viewport((float)SHADOWMAP_DIM, (float)SHADOWMAP_DIM, 0.0f, 1.0f); vkCmdSetViewport(depthPass.commandBuffer, 0, 1, &viewport); VkRect2D scissor = vks::initializers::rect2D(SHADOWMAP_DIM, SHADOWMAP_DIM, 0, 0); vkCmdSetScissor(depthPass.commandBuffer, 0, 1, &scissor); // Multi-pass depht map cascade generation // Could be simplified to one pass using geometry shaders and layered frame buffers for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { renderPassBeginInfo.framebuffer = cascades[i].frameBuffer; vkCmdBeginRenderPass(depthPass.commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(depthPass.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, depthPass.pipeline); vkCmdPushConstants(depthPass.commandBuffer, depthPass.pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(uint32_t), &i); VkDeviceSize offsets[1] = { 0 }; vkCmdBindDescriptorSets(depthPass.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, depthPass.pipelineLayout, 0, 1, &cascades[i].descriptorSet, 0, NULL); vkCmdBindVertexBuffers(depthPass.commandBuffer, 0, 1, &scenes[sceneIndex].vertices.buffer, offsets); vkCmdBindIndexBuffer(depthPass.commandBuffer, scenes[sceneIndex].indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(depthPass.commandBuffer, scenes[sceneIndex].indexCount, 1, 0, 0, 0); vkCmdEndRenderPass(depthPass.commandBuffer); } VK_CHECK_RESULT(vkEndCommandBuffer(depthPass.commandBuffer)); } void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[2]; clearValues[0].color = defaultClearColor; clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } }; clearValues[1].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); renderPassBeginInfo.renderPass = renderPass; renderPassBeginInfo.renderArea.offset.x = 0; renderPassBeginInfo.renderArea.offset.y = 0; renderPassBeginInfo.renderArea.extent.width = width; renderPassBeginInfo.renderArea.extent.height = height; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; for (int32_t i = 0; i < drawCmdBuffers.size(); i++) { renderPassBeginInfo.framebuffer = frameBuffers[i]; VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo)); vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0); vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor); VkDeviceSize offsets[1] = { 0 }; // Visualize shadow map cascade if (displayDepthMap) { vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.debugShadowMap); vkCmdPushConstants(drawCmdBuffers[i], pipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(uint32_t), &displayDepthMapCascadeIndex); vkCmdDraw(drawCmdBuffers[i], 3, 1, 0, 0); } // Render shadowed scene vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, (filterPCF) ? pipelines.sceneShadowPCF : pipelines.sceneShadow); vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &scenes[sceneIndex].vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], scenes[sceneIndex].indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], scenes[sceneIndex].indexCount, 1, 0, 0, 0); vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void loadAssets() { scenes.resize(2); scenes[0].loadFromFile(getAssetPath() + "models/trees.dae", vertexLayout, 1.0f, vulkanDevice, queue); scenes[1].loadFromFile(getAssetPath() + "models/samplescene.dae", vertexLayout, 0.25f, vulkanDevice, queue); sceneNames = { "Trees", "Teapots and pillars" }; } void setupDescriptorPool() { std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 12), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 32) }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(static_cast(poolSizes.size()), poolSizes.data(), 3 + SHADOW_MAP_CASCADE_COUNT); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } void setupLayoutsAndDescriptors() { /* Layouts */ std::vector setLayoutBindings = { vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_FRAGMENT_BIT, 2), vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 3), }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); // Shared pipeline layout { // Pass cascade index as push constant VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(uint32_t), 0); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); } // Offscreen pipeline layout { // Pass cascade matrix as push constant VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0); VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1); pipelineLayoutCreateInfo.pushConstantRangeCount = 1; pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange; VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &depthPass.pipelineLayout)); } /* Dscriptor sets */ std::vector writeDescriptorSets; VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); // Scene rendering / debug display VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); VkDescriptorImageInfo depthMapDescriptor = vks::initializers::descriptorImageInfo(depth.sampler, depth.view, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL); writeDescriptorSets = { vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.VS.descriptor), vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &depthMapDescriptor), vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2, &uniformBuffers.FS.descriptor), }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); // Per-cascade descriptor set // Each descriptor set represents a single layer of the array texture for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &cascades[i].descriptorSet)); VkDescriptorImageInfo cascadeImageInfo = vks::initializers::descriptorImageInfo(depth.sampler, depth.view, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL); writeDescriptorSets = { vks::initializers::writeDescriptorSet(cascades[i].descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &depthPass.uniformBuffer.descriptor), vks::initializers::writeDescriptorSet(cascades[i].descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &cascadeImageInfo) }; vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); } } 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_BACK_BIT, VK_FRONT_FACE_CLOCKWISE, 0); VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, 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; VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0); pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.pMultisampleState = &multisampleState; pipelineCreateInfo.pViewportState = &viewportState; pipelineCreateInfo.pDepthStencilState = &depthStencilState; pipelineCreateInfo.pDynamicState = &dynamicState; pipelineCreateInfo.stageCount = static_cast(shaderStages.size()); pipelineCreateInfo.pStages = shaderStages.data(); // Shadow map cascade debug quad display rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT; shaderStages[0] = loadShader(getAssetPath() + "shaders/shadowmappingcascade/debugshadowmap.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getAssetPath() + "shaders/shadowmappingcascade/debugshadowmap.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); // Empty vertex input state VkPipelineVertexInputStateCreateInfo emptyInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); pipelineCreateInfo.pVertexInputState = &emptyInputState; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.debugShadowMap)); // Vertex bindings and attributes std::vector vertexInputBindings = { vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX) }; std::vector vertexInputAttributes = { vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0), // Location 0: Position vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 3), // Location 1: UV vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 5), // Location 2: Color vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8) // Location 3: Normal }; VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo(); vertexInputState.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data(); vertexInputState.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data(); pipelineCreateInfo.pVertexInputState = &vertexInputState; /* Shadow mapped scene rendering */ rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT; shaderStages[0] = loadShader(getAssetPath() + "shaders/shadowmappingcascade/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getAssetPath() + "shaders/shadowmappingcascade/scene.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); // Use specialization constants to select between horizontal and vertical blur uint32_t enablePCF = 0; VkSpecializationMapEntry specializationMapEntry = vks::initializers::specializationMapEntry(0, 0, sizeof(uint32_t)); VkSpecializationInfo specializationInfo = vks::initializers::specializationInfo(1, &specializationMapEntry, sizeof(uint32_t), &enablePCF); shaderStages[1].pSpecializationInfo = &specializationInfo; // No filtering VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.sceneShadow)); // PCF filtering enablePCF = 1; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.sceneShadowPCF)); /* Depth map generation */ shaderStages[0] = loadShader(getAssetPath() + "shaders/shadowmappingcascade/depthpass.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getAssetPath() + "shaders/shadowmappingcascade/depthpass.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); // No blend attachment states (no color attachments used) colorBlendState.attachmentCount = 0; depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; // Enable depth clamp (if available) rasterizationState.depthClampEnable = deviceFeatures.depthClamp; pipelineCreateInfo.layout = depthPass.pipelineLayout; pipelineCreateInfo.renderPass = depthPass.renderPass; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &depthPass.pipeline)); } void prepareUniformBuffers() { // Shadow map generation buffer blocks VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &depthPass.uniformBuffer, sizeof(depthPass.ubo))); // Scene uniform buffer blocks VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.VS, sizeof(uboVS))); VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.FS, sizeof(uboFS))); // Map persistent VK_CHECK_RESULT(depthPass.uniformBuffer.map()); VK_CHECK_RESULT(uniformBuffers.VS.map()); VK_CHECK_RESULT(uniformBuffers.FS.map()); updateLight(); updateUniformBuffers(); } // Calculate frustum split depths and matrices for the shadow map cascades // Based on https://johanmedestrom.wordpress.com/2016/03/18/opengl-cascaded-shadow-maps/ void updateCascades() { float cascadeSplits[SHADOW_MAP_CASCADE_COUNT]; float nearClip = camera.getNearClip(); float farClip = camera.getFarClip(); float clipRange = farClip - nearClip; float minZ = nearClip; float maxZ = nearClip + clipRange; float range = maxZ - minZ; float ratio = maxZ / minZ; // Calculate split depths based on view camera furstum // Based on method presentd in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { float p = (i + 1) / static_cast(SHADOW_MAP_CASCADE_COUNT); float log = minZ * std::pow(ratio, p); float uniform = minZ + range * p; float d = cascadeSplitLambda * (log - uniform) + uniform; cascadeSplits[i] = (d - nearClip) / clipRange; } // Calculate orthographic projection matrix for each cascade float lastSplitDist = 0.0; for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { float splitDist = cascadeSplits[i]; glm::vec3 frustumCorners[8] = { glm::vec3(-1.0f, 1.0f, -1.0f), glm::vec3( 1.0f, 1.0f, -1.0f), glm::vec3( 1.0f, -1.0f, -1.0f), glm::vec3(-1.0f, -1.0f, -1.0f), glm::vec3(-1.0f, 1.0f, 1.0f), glm::vec3( 1.0f, 1.0f, 1.0f), glm::vec3( 1.0f, -1.0f, 1.0f), glm::vec3(-1.0f, -1.0f, 1.0f), }; // Project frustum corners into world space glm::mat4 invCam = glm::inverse(camera.matrices.perspective * camera.matrices.view); for (uint32_t i = 0; i < 8; i++) { glm::vec4 invCorner = invCam * glm::vec4(frustumCorners[i], 1.0f); frustumCorners[i] = invCorner / invCorner.w; } for (uint32_t i = 0; i < 4; i++) { glm::vec3 dist = frustumCorners[i + 4] - frustumCorners[i]; frustumCorners[i + 4] = frustumCorners[i] + (dist * splitDist); frustumCorners[i] = frustumCorners[i] + (dist * lastSplitDist); } // Get frustum center glm::vec3 frustumCenter = glm::vec3(0.0f); for (uint32_t i = 0; i < 8; i++) { frustumCenter += frustumCorners[i]; } frustumCenter /= 8.0f; float radius = 0.0f; for (uint32_t i = 0; i < 8; i++) { float distance = glm::length(frustumCorners[i] - frustumCenter); radius = glm::max(radius, distance); } radius = std::ceil(radius * 16.0f) / 16.0f; glm::vec3 maxExtents = glm::vec3(radius); glm::vec3 minExtents = -maxExtents; glm::vec3 lightDir = normalize(-lightPos); glm::mat4 lightViewMatrix = glm::lookAt(frustumCenter - lightDir * -minExtents.z, frustumCenter, glm::vec3(0.0f, 1.0f, 0.0f)); glm::mat4 lightOrthoMatrix = glm::ortho(minExtents.x, maxExtents.x, minExtents.y, maxExtents.y, 0.0f, maxExtents.z - minExtents.z); // Store split distance and matrix in cascade cascades[i].splitDepth = (camera.getNearClip() + splitDist * clipRange) * -1.0f; cascades[i].viewProjMatrix = lightOrthoMatrix * lightViewMatrix; lastSplitDist = cascadeSplits[i]; } } void updateLight() { float angle = glm::radians(timer * 360.0f); float radius = 20.0f; lightPos = glm::vec3(cos(angle) * radius, -radius, sin(angle) * radius); } void updateUniformBuffers() { /* Depth rendering */ for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { depthPass.ubo.cascadeViewProjMat[i] = cascades[i].viewProjMatrix; } memcpy(depthPass.uniformBuffer.mapped, &depthPass.ubo, sizeof(depthPass.ubo)); /* Scene rendering */ uboVS.projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, zNear, zFar); uboVS.view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom)); uboVS.view = glm::rotate(uboVS.view, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); uboVS.view = glm::rotate(uboVS.view, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); uboVS.view = glm::rotate(uboVS.view, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); uboVS.model = glm::mat4(1.0f); uboVS.projection = camera.matrices.perspective; uboVS.view = camera.matrices.view; uboVS.model = glm::mat4(1.0f); uboVS.lightDir = normalize(-lightPos); memcpy(uniformBuffers.VS.mapped, &uboVS, sizeof(uboVS)); for (uint32_t i = 0; i < SHADOW_MAP_CASCADE_COUNT; i++) { uboFS.cascadeSplits[i] = cascades[i].splitDepth; uboFS.cascadeViewProjMat[i] = cascades[i].viewProjMatrix; } uboFS.inverseViewMat = glm::inverse(camera.matrices.view); uboFS.lightDir = normalize(-lightPos); uboFS.colorCascades = colorCascades; memcpy(uniformBuffers.FS.mapped, &uboFS, sizeof(uboFS)); } void draw() { VulkanExampleBase::prepareFrame(); // Depth map generation submitInfo.pWaitSemaphores = &semaphores.presentComplete; submitInfo.pSignalSemaphores = &depthPass.semaphore; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &depthPass.commandBuffer; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); // Scene rendering submitInfo.pWaitSemaphores = &depthPass.semaphore;; submitInfo.pSignalSemaphores = &semaphores.renderComplete; submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } void prepare() { VulkanExampleBase::prepare(); loadAssets(); updateLight(); updateCascades(); prepareShadowMaps(); prepareUniformBuffers(); setupDescriptorPool(); setupLayoutsAndDescriptors(); preparePipelines(); buildCommandBuffers(); buildOffscreenCommandBuffer(); prepared = true; } virtual void render() { if (!prepared) return; draw(); if (!paused) { updateLight(); updateCascades(); updateUniformBuffers(); } } virtual void viewChanged() { updateCascades(); updateUniformBuffers(); } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) { if (overlay->header("Settings")) { if (overlay->comboBox("Scenes", &sceneIndex, sceneNames)) { buildCommandBuffers(); buildOffscreenCommandBuffer(); } if (overlay->sliderFloat("Split lambda", &cascadeSplitLambda, 0.1f, 1.0f)) { updateCascades(); updateUniformBuffers(); } if (overlay->checkBox("Color cascades", &colorCascades)) { updateUniformBuffers(); } if (overlay->checkBox("Display depth map", &displayDepthMap)) { buildCommandBuffers(); } if (displayDepthMap) { if (overlay->sliderInt("Cascade", &displayDepthMapCascadeIndex, 0, SHADOW_MAP_CASCADE_COUNT - 1)) { buildCommandBuffers(); } } if (overlay->checkBox("PCF filtering", &filterPCF)) { buildCommandBuffers(); } } } }; VULKAN_EXAMPLE_MAIN()