/* * Vulkan Example - Shadow mapping for directional light sources * * Copyright (C) 2016 by Sascha Willems - www.saschawillems.de * * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) */ #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 VERTEX_BUFFER_BIND_ID 0 #define ENABLE_VALIDATION false // 16 bits of depth is enough for such a small scene #define DEPTH_FORMAT VK_FORMAT_D16_UNORM // Shadowmap properties #if defined(__ANDROID__) #define SHADOWMAP_DIM 1024 #else #define SHADOWMAP_DIM 2048 #endif #define SHADOWMAP_FILTER VK_FILTER_LINEAR // Offscreen frame buffer properties #define FB_COLOR_FORMAT VK_FORMAT_R8G8B8A8_UNORM class VulkanExample : public VulkanExampleBase { public: bool displayShadowMap = false; bool lightPOV = false; bool filterPCF = true; // Keep depth range as small as possible // for better shadow map precision float zNear = 1.0f; float zFar = 96.0f; // Depth bias (and slope) are used to avoid shadowing artefacts // Constant depth bias factor (always applied) float depthBiasConstant = 1.25f; // Slope depth bias factor, applied depending on polygon's slope float depthBiasSlope = 1.75f; glm::vec3 lightPos = glm::vec3(); float lightFOV = 45.0f; // 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, }); struct { vks::Model scene; vks::Model quad; } models; struct { VkPipelineVertexInputStateCreateInfo inputState; std::vector bindingDescriptions; std::vector attributeDescriptions; } vertices; struct { vks::Buffer scene; vks::Buffer offscreen; vks::Buffer debug; } uniformBuffers; struct { glm::mat4 projection; glm::mat4 model; } uboVSquad; struct { glm::mat4 projection; glm::mat4 view; glm::mat4 model; glm::mat4 depthBiasMVP; glm::vec3 lightPos; } uboVSscene; struct { glm::mat4 depthMVP; } uboOffscreenVS; struct { VkPipeline quad; VkPipeline offscreen; VkPipeline sceneShadow; VkPipeline sceneShadowPCF; } pipelines; struct { VkPipelineLayout quad; VkPipelineLayout offscreen; } pipelineLayouts; struct { VkDescriptorSet offscreen; VkDescriptorSet scene; } descriptorSets; VkDescriptorSet descriptorSet; VkDescriptorSetLayout descriptorSetLayout; // Framebuffer for offscreen rendering struct FrameBufferAttachment { VkImage image; VkDeviceMemory mem; VkImageView view; }; struct OffscreenPass { int32_t width, height; VkFramebuffer frameBuffer; FrameBufferAttachment depth; VkRenderPass renderPass; VkSampler depthSampler; VkDescriptorImageInfo descriptor; VkCommandBuffer commandBuffer = VK_NULL_HANDLE; // Semaphore used to synchronize between offscreen and final scene render pass VkSemaphore semaphore = VK_NULL_HANDLE; } offscreenPass; VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) { zoom = -20.0f; rotation = { -15.0f, -390.0f, 0.0f }; enableTextOverlay = true; title = "Vulkan Example - Projected shadow mapping"; timerSpeed *= 0.5f; } ~VulkanExample() { // Clean up used Vulkan resources // Note : Inherited destructor cleans up resources stored in base class // Frame buffer vkDestroySampler(device, offscreenPass.depthSampler, nullptr); // Depth attachment vkDestroyImageView(device, offscreenPass.depth.view, nullptr); vkDestroyImage(device, offscreenPass.depth.image, nullptr); vkFreeMemory(device, offscreenPass.depth.mem, nullptr); vkDestroyFramebuffer(device, offscreenPass.frameBuffer, nullptr); vkDestroyRenderPass(device, offscreenPass.renderPass, nullptr); vkDestroyPipeline(device, pipelines.quad, nullptr); vkDestroyPipeline(device, pipelines.offscreen, nullptr); vkDestroyPipeline(device, pipelines.sceneShadow, nullptr); vkDestroyPipeline(device, pipelines.sceneShadowPCF, nullptr); vkDestroyPipelineLayout(device, pipelineLayouts.quad, nullptr); vkDestroyPipelineLayout(device, pipelineLayouts.offscreen, nullptr); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); // Meshes models.scene.destroy(); models.quad.destroy(); // Uniform buffers uniformBuffers.offscreen.destroy(); uniformBuffers.scene.destroy(); uniformBuffers.debug.destroy(); vkFreeCommandBuffers(device, cmdPool, 1, &offscreenPass.commandBuffer); vkDestroySemaphore(device, offscreenPass.semaphore, nullptr); } // Set up a separate render pass for the offscreen frame buffer // This is necessary as the offscreen frame buffer attachments use formats different to those from the example render pass void prepareOffscreenRenderpass() { VkAttachmentDescription attachmentDescription{}; attachmentDescription.format = DEPTH_FORMAT; attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT; attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; // Clear depth at beginning of the render pass attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // We will read from depth, so it's important to store the depth attachment results attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; // We don't care about initial layout of the attachment attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL;// Attachment will be transitioned to shader read at render pass end VkAttachmentReference depthReference = {}; depthReference.attachment = 0; depthReference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; // Attachment will be used as depth/stencil during render pass VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 0; // No color attachments subpass.pDepthStencilAttachment = &depthReference; // Reference to our depth attachment // 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, &offscreenPass.renderPass)); } // Setup the offscreen framebuffer for rendering the scene from light's point-of-view to // The depth attachment of this framebuffer will then be used to sample from in the fragment shader of the shadowing pass void prepareOffscreenFramebuffer() { offscreenPass.width = SHADOWMAP_DIM; offscreenPass.height = SHADOWMAP_DIM; VkFormat fbColorFormat = FB_COLOR_FORMAT; // For shadow mapping we only need a depth attachment VkImageCreateInfo image = vks::initializers::imageCreateInfo(); image.imageType = VK_IMAGE_TYPE_2D; image.extent.width = offscreenPass.width; image.extent.height = offscreenPass.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.format = DEPTH_FORMAT; // Depth stencil attachment image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; // We will sample directly from the depth attachment for the shadow mapping VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &offscreenPass.depth.image)); VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo(); VkMemoryRequirements memReqs; vkGetImageMemoryRequirements(device, offscreenPass.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, &offscreenPass.depth.mem)); VK_CHECK_RESULT(vkBindImageMemory(device, offscreenPass.depth.image, offscreenPass.depth.mem, 0)); VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo(); depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D; depthStencilView.format = DEPTH_FORMAT; depthStencilView.subresourceRange = {}; depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; depthStencilView.subresourceRange.baseMipLevel = 0; depthStencilView.subresourceRange.levelCount = 1; depthStencilView.subresourceRange.baseArrayLayer = 0; depthStencilView.subresourceRange.layerCount = 1; depthStencilView.image = offscreenPass.depth.image; VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &offscreenPass.depth.view)); // Create sampler to sample from to depth attachment // Used to sample in the fragment shader for shadowed rendering VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo(); sampler.magFilter = SHADOWMAP_FILTER; sampler.minFilter = SHADOWMAP_FILTER; 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, &offscreenPass.depthSampler)); prepareOffscreenRenderpass(); // Create frame buffer VkFramebufferCreateInfo fbufCreateInfo = vks::initializers::framebufferCreateInfo(); fbufCreateInfo.renderPass = offscreenPass.renderPass; fbufCreateInfo.attachmentCount = 1; fbufCreateInfo.pAttachments = &offscreenPass.depth.view; fbufCreateInfo.width = offscreenPass.width; fbufCreateInfo.height = offscreenPass.height; fbufCreateInfo.layers = 1; VK_CHECK_RESULT(vkCreateFramebuffer(device, &fbufCreateInfo, nullptr, &offscreenPass.frameBuffer)); } void buildOffscreenCommandBuffer() { if (offscreenPass.commandBuffer == VK_NULL_HANDLE) { offscreenPass.commandBuffer = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, false); } if (offscreenPass.semaphore == VK_NULL_HANDLE) { // Create a semaphore used to synchronize offscreen rendering and usage VkSemaphoreCreateInfo semaphoreCreateInfo = vks::initializers::semaphoreCreateInfo(); VK_CHECK_RESULT(vkCreateSemaphore(device, &semaphoreCreateInfo, nullptr, &offscreenPass.semaphore)); } VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[1]; clearValues[0].depthStencil = { 1.0f, 0 }; VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo(); renderPassBeginInfo.renderPass = offscreenPass.renderPass; renderPassBeginInfo.framebuffer = offscreenPass.frameBuffer; renderPassBeginInfo.renderArea.offset.x = 0; renderPassBeginInfo.renderArea.offset.y = 0; renderPassBeginInfo.renderArea.extent.width = offscreenPass.width; renderPassBeginInfo.renderArea.extent.height = offscreenPass.height; renderPassBeginInfo.clearValueCount = 2; renderPassBeginInfo.pClearValues = clearValues; VK_CHECK_RESULT(vkBeginCommandBuffer(offscreenPass.commandBuffer, &cmdBufInfo)); VkViewport viewport = vks::initializers::viewport((float)offscreenPass.width, (float)offscreenPass.height, 0.0f, 1.0f); vkCmdSetViewport(offscreenPass.commandBuffer, 0, 1, &viewport); VkRect2D scissor = vks::initializers::rect2D(offscreenPass.width, offscreenPass.height, 0, 0); vkCmdSetScissor(offscreenPass.commandBuffer, 0, 1, &scissor); // Set depth bias (aka "Polygon offset") // Required to avoid shadow mapping artefacts vkCmdSetDepthBias( offscreenPass.commandBuffer, depthBiasConstant, 0.0f, depthBiasSlope); vkCmdBeginRenderPass(offscreenPass.commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(offscreenPass.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.offscreen); vkCmdBindDescriptorSets(offscreenPass.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.offscreen, 0, 1, &descriptorSets.offscreen, 0, NULL); VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(offscreenPass.commandBuffer, VERTEX_BUFFER_BIND_ID, 1, &models.scene.vertices.buffer, offsets); vkCmdBindIndexBuffer(offscreenPass.commandBuffer, models.scene.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(offscreenPass.commandBuffer, models.scene.indexCount, 1, 0, 0, 0); vkCmdEndRenderPass(offscreenPass.commandBuffer); VK_CHECK_RESULT(vkEndCommandBuffer(offscreenPass.commandBuffer)); } void buildCommandBuffers() { VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo(); VkClearValue clearValues[2]; clearValues[0].color = defaultClearColor; 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) { // Set target frame buffer 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 }; vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.quad, 0, 1, &descriptorSet, 0, NULL); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.quad); // Visualize shadow map if (displayShadowMap) { vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.quad.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.quad.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], models.quad.indexCount, 1, 0, 0, 0); } // 3D scene vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayouts.quad, 0, 1, &descriptorSets.scene, 0, NULL); vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, (filterPCF) ? pipelines.sceneShadowPCF : pipelines.sceneShadow); vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &models.scene.vertices.buffer, offsets); vkCmdBindIndexBuffer(drawCmdBuffers[i], models.scene.indices.buffer, 0, VK_INDEX_TYPE_UINT32); vkCmdDrawIndexed(drawCmdBuffers[i], models.scene.indexCount, 1, 0, 0, 0); vkCmdEndRenderPass(drawCmdBuffers[i]); VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); } } void loadAssets() { models.scene.loadFromFile(getAssetPath() + "models/vulkanscene_shadow.dae", vertexLayout, 4.0f, vulkanDevice, queue); } void generateQuad() { // Setup vertices for a single uv-mapped quad struct Vertex { float pos[3]; float uv[2]; float col[3]; float normal[3]; }; #define QUAD_COLOR_NORMAL { 1.0f, 1.0f, 1.0f }, { 0.0f, 0.0f, 1.0f } std::vector vertexBuffer = { { { 1.0f, 1.0f, 0.0f },{ 1.0f, 1.0f }, QUAD_COLOR_NORMAL }, { { 0.0f, 1.0f, 0.0f },{ 0.0f, 1.0f }, QUAD_COLOR_NORMAL }, { { 0.0f, 0.0f, 0.0f },{ 0.0f, 0.0f }, QUAD_COLOR_NORMAL }, { { 1.0f, 0.0f, 0.0f },{ 1.0f, 0.0f }, QUAD_COLOR_NORMAL } }; #undef QUAD_COLOR_NORMAL VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer.size() * sizeof(Vertex), &models.quad.vertices.buffer, &models.quad.vertices.memory, vertexBuffer.data())); // Setup indices std::vector indexBuffer = { 0,1,2, 2,3,0 }; models.quad.indexCount = indexBuffer.size(); VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, indexBuffer.size() * sizeof(uint32_t), &models.quad.indices.buffer, &models.quad.indices.memory, indexBuffer.data())); models.quad.device = device; } void setupVertexDescriptions() { // Binding description vertices.bindingDescriptions.resize(1); vertices.bindingDescriptions[0] = vks::initializers::vertexInputBindingDescription( VERTEX_BUFFER_BIND_ID, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX); // Attribute descriptions vertices.attributeDescriptions.resize(4); // Location 0 : Position vertices.attributeDescriptions[0] = vks::initializers::vertexInputAttributeDescription( VERTEX_BUFFER_BIND_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, 0); // Location 1 : Texture coordinates vertices.attributeDescriptions[1] = vks::initializers::vertexInputAttributeDescription( VERTEX_BUFFER_BIND_ID, 1, VK_FORMAT_R32G32_SFLOAT, sizeof(float) * 3); // Location 2 : Color vertices.attributeDescriptions[2] = vks::initializers::vertexInputAttributeDescription( VERTEX_BUFFER_BIND_ID, 2, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 5); // Location 3 : Normal vertices.attributeDescriptions[3] = vks::initializers::vertexInputAttributeDescription( VERTEX_BUFFER_BIND_ID, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 8); vertices.inputState = vks::initializers::pipelineVertexInputStateCreateInfo(); vertices.inputState.vertexBindingDescriptionCount = vertices.bindingDescriptions.size(); vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data(); vertices.inputState.vertexAttributeDescriptionCount = vertices.attributeDescriptions.size(); vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data(); } void setupDescriptorPool() { // Example uses three ubos and two image samplers std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 6), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 4) }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo( poolSizes.size(), poolSizes.data(), 3); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); } void setupDescriptorSetLayout() { // Textured quad pipeline layout std::vector setLayoutBindings = { // Binding 0 : Vertex shader uniform buffer vks::initializers::descriptorSetLayoutBinding( VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), // Binding 1 : Fragment shader image sampler vks::initializers::descriptorSetLayoutBinding( VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1) }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo( setLayoutBindings.data(), setLayoutBindings.size()); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo( &descriptorSetLayout, 1); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.quad)); // Offscreen pipeline layout VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayouts.offscreen)); } void setupDescriptorSets() { std::vector writeDescriptorSets; // Textured quad descriptor set VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo( descriptorPool, &descriptorSetLayout, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); // Image descriptor for the shadow map attachment VkDescriptorImageInfo texDescriptor = vks::initializers::descriptorImageInfo( offscreenPass.depthSampler, offscreenPass.depth.view, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL); writeDescriptorSets = { // Binding 0 : Vertex shader uniform buffer vks::initializers::writeDescriptorSet( descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.debug.descriptor), // Binding 1 : Fragment shader texture sampler vks::initializers::writeDescriptorSet( descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptor) }; vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); // Offscreen VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.offscreen)); writeDescriptorSets = { // Binding 0 : Vertex shader uniform buffer vks::initializers::writeDescriptorSet( descriptorSets.offscreen, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.offscreen.descriptor), }; vkUpdateDescriptorSets(device, writeDescriptorSets.size(), writeDescriptorSets.data(), 0, NULL); // 3D scene VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.scene)); // Image descriptor for the shadow map attachment texDescriptor.sampler = offscreenPass.depthSampler; texDescriptor.imageView = offscreenPass.depth.view; writeDescriptorSets = { // Binding 0 : Vertex shader uniform buffer vks::initializers::writeDescriptorSet( descriptorSets.scene, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.scene.descriptor), // Binding 1 : Fragment shader shadow sampler vks::initializers::writeDescriptorSet( descriptorSets.scene, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptor) }; vkUpdateDescriptorSets(device, 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.data(), dynamicStateEnables.size(), 0); // Solid rendering pipeline // Load shaders std::array shaderStages; shaderStages[0] = loadShader(getAssetPath() + "shaders/shadowmapping/quad.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getAssetPath() + "shaders/shadowmapping/quad.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo( pipelineLayouts.quad, renderPass, 0); pipelineCreateInfo.pVertexInputState = &vertices.inputState; pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState; pipelineCreateInfo.pRasterizationState = &rasterizationState; pipelineCreateInfo.pColorBlendState = &colorBlendState; pipelineCreateInfo.pMultisampleState = &multisampleState; pipelineCreateInfo.pViewportState = &viewportState; pipelineCreateInfo.pDepthStencilState = &depthStencilState; pipelineCreateInfo.pDynamicState = &dynamicState; pipelineCreateInfo.stageCount = shaderStages.size(); pipelineCreateInfo.pStages = shaderStages.data(); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.quad)); // Scene rendering with shadows applied shaderStages[0] = loadShader(getAssetPath() + "shaders/shadowmapping/scene.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getAssetPath() + "shaders/shadowmapping/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)); // Offscreen pipeline shaderStages[0] = loadShader(getAssetPath() + "shaders/shadowmapping/offscreen.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); shaderStages[1] = loadShader(getAssetPath() + "shaders/shadowmapping/offscreen.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); // No blend attachment states (no color attachments used) colorBlendState.attachmentCount = 0; // Cull front faces depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; // Enable depth bias rasterizationState.depthBiasEnable = VK_TRUE; // Add depth bias to dynamic state, so we can change it at runtime dynamicStateEnables.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS); dynamicState = vks::initializers::pipelineDynamicStateCreateInfo( dynamicStateEnables.data(), dynamicStateEnables.size(), 0); pipelineCreateInfo.layout = pipelineLayouts.offscreen; pipelineCreateInfo.renderPass = offscreenPass.renderPass; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.offscreen)); } // Prepare and initialize uniform buffer containing shader uniforms void prepareUniformBuffers() { // Debug quad vertex shader uniform buffer block VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.debug, sizeof(uboVSscene))); // Offscreen vertex shader uniform buffer block VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.offscreen, sizeof(uboOffscreenVS))); // Scene vertex shader uniform buffer block VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.scene, sizeof(uboVSscene))); // Map persistent VK_CHECK_RESULT(uniformBuffers.debug.map()); VK_CHECK_RESULT(uniformBuffers.offscreen.map()); VK_CHECK_RESULT(uniformBuffers.scene.map()); updateLight(); updateUniformBufferOffscreen(); updateUniformBuffers(); } void updateLight() { // Animate the light source lightPos.x = cos(glm::radians(timer * 360.0f)) * 40.0f; lightPos.y = -50.0f + sin(glm::radians(timer * 360.0f)) * 20.0f; lightPos.z = 25.0f + sin(glm::radians(timer * 360.0f)) * 5.0f; } void updateUniformBuffers() { // Shadow map debug quad float AR = (float)height / (float)width; uboVSquad.projection = glm::ortho(2.5f / AR, 0.0f, 0.0f, 2.5f, -1.0f, 1.0f); uboVSquad.model = glm::mat4(1.0f); memcpy(uniformBuffers.debug.mapped, &uboVSquad, sizeof(uboVSquad)); // 3D scene uboVSscene.projection = glm::perspective(glm::radians(45.0f), (float)width / (float)height, zNear, zFar); uboVSscene.view = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, zoom)); uboVSscene.view = glm::rotate(uboVSscene.view, glm::radians(rotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); uboVSscene.view = glm::rotate(uboVSscene.view, glm::radians(rotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); uboVSscene.view = glm::rotate(uboVSscene.view, glm::radians(rotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); uboVSscene.model = glm::mat4(1.0f); uboVSscene.lightPos = lightPos; // Render scene from light's point of view if (lightPOV) { uboVSscene.projection = glm::perspective(glm::radians(lightFOV), (float)width / (float)height, zNear, zFar); uboVSscene.view = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); } uboVSscene.depthBiasMVP = uboOffscreenVS.depthMVP; memcpy(uniformBuffers.scene.mapped, &uboVSscene, sizeof(uboVSscene)); } void updateUniformBufferOffscreen() { // Matrix from light's point of view glm::mat4 depthProjectionMatrix = glm::perspective(glm::radians(lightFOV), 1.0f, zNear, zFar); glm::mat4 depthViewMatrix = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0, 1, 0)); glm::mat4 depthModelMatrix = glm::mat4(1.0f); uboOffscreenVS.depthMVP = depthProjectionMatrix * depthViewMatrix * depthModelMatrix; memcpy(uniformBuffers.offscreen.mapped, &uboOffscreenVS, sizeof(uboOffscreenVS)); } void draw() { VulkanExampleBase::prepareFrame(); // The scene render command buffer has to wait for the offscreen rendering (and transfer) to be finished before using the shadow map // Therefore we synchronize using an additional semaphore // Offscreen rendering // Wait for swap chain presentation to finish submitInfo.pWaitSemaphores = &semaphores.presentComplete; // Signal ready with offscreen semaphore submitInfo.pSignalSemaphores = &offscreenPass.semaphore; // Submit work submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &offscreenPass.commandBuffer; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); // Scene rendering // Wait for offscreen semaphore submitInfo.pWaitSemaphores = &offscreenPass.semaphore;; // Signal ready with render complete semaphpre submitInfo.pSignalSemaphores = &semaphores.renderComplete; // Submit work submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); VulkanExampleBase::submitFrame(); } void prepare() { VulkanExampleBase::prepare(); loadAssets(); generateQuad(); prepareOffscreenFramebuffer(); setupVertexDescriptions(); prepareUniformBuffers(); setupDescriptorSetLayout(); preparePipelines(); setupDescriptorPool(); setupDescriptorSets(); buildCommandBuffers(); buildOffscreenCommandBuffer(); prepared = true; } virtual void render() { if (!prepared) return; draw(); if (!paused) { updateLight(); updateUniformBufferOffscreen(); updateUniformBuffers(); } } virtual void viewChanged() { updateUniformBufferOffscreen(); updateUniformBuffers(); } void toggleShadowMapDisplay() { displayShadowMap = !displayShadowMap; buildCommandBuffers(); } void toogleLightPOV() { lightPOV = !lightPOV; viewChanged(); } void toogleFilterPCF() { filterPCF = !filterPCF; buildCommandBuffers(); } virtual void keyPressed(uint32_t keyCode) { switch (keyCode) { case KEY_S: case GAMEPAD_BUTTON_A: toggleShadowMapDisplay(); break; case KEY_L: case GAMEPAD_BUTTON_X: toogleLightPOV(); break; case KEY_F: case GAMEPAD_BUTTON_Y: toogleFilterPCF(); break; } } virtual void getOverlayText(VulkanTextOverlay *textOverlay) { #if defined(__ANDROID__) textOverlay->addText("\"Button A\" to toggle shadow map", 5.0f, 85.0f, VulkanTextOverlay::alignLeft); textOverlay->addText("\"Button X\" to toggle light's pov", 5.0f, 100.0f, VulkanTextOverlay::alignLeft); textOverlay->addText("\"Button Y\" to toggle PCF filtering", 5.0f, 115.0f, VulkanTextOverlay::alignLeft); #else textOverlay->addText("\"s\" to toggle shadow map", 5.0f, 85.0f, VulkanTextOverlay::alignLeft); textOverlay->addText("\"l\" to toggle light's pov", 5.0f, 100.0f, VulkanTextOverlay::alignLeft); textOverlay->addText("\"f\" to toggle PCF filtering", 5.0f, 115.0f, VulkanTextOverlay::alignLeft); #endif } }; VULKAN_EXAMPLE_MAIN()