Added cascaded shadow mapping example

This commit is contained in:
saschawillems 2017-12-09 21:12:55 +01:00
parent f4281096ea
commit 3c150e18f3
14 changed files with 1081 additions and 0 deletions

View file

@ -78,6 +78,7 @@ set(EXAMPLES
screenshot
shadowmapping
shadowmappingomni
shadowmappingcascade
skeletalanimation
specializationconstants
sphericalenvmapping

View file

@ -0,0 +1,874 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <vector>
#define GLM_FORCE_RADIANS
#define GLM_FORCE_DEPTH_ZERO_TO_ONE
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <vulkan/vulkan.h>
#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 SHADOWMAP_FILTER VK_FILTER_LINEAR
#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<vks::Model> scenes;
std::vector<std::string> 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<glm::mat4, SHADOW_MAP_CASCADE_COUNT> 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<Cascade, SHADOW_MAP_CASCADE_COUNT> 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(8.75f, 0.5f, -8.3f));
camera.setRotation(glm::vec3(-1.0f, 50.0f, 0.0f));
settings.overlay = 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<VkSubpassDependency, 2> 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<uint32_t>(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 = 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, &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/terrain.obj", vertexLayout, 2.0f, vulkanDevice, queue);
scenes[1].loadFromFile(getAssetPath() + "models/shadowtest.obj", vertexLayout, 0.25f, vulkanDevice, queue);
sceneNames = {"Terrain test", "Object test" };
}
void setupDescriptorPool()
{
std::vector<VkDescriptorPoolSize> 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<uint32_t>(poolSizes.size()), poolSizes.data(), 3 + SHADOW_MAP_CASCADE_COUNT);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
}
void setupLayoutsAndDescriptors()
{
/*
Layouts
*/
std::vector<VkDescriptorSetLayoutBinding> 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<VkWriteDescriptorSet> 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<uint32_t>(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<uint32_t>(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<VkDynamicState> dynamicStateEnables = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState =
vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::array<VkPipelineShaderStageCreateInfo, 2> 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<uint32_t>(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<VkVertexInputBindingDescription> vertexInputBindings = {
vks::initializers::vertexInputBindingDescription(0, vertexLayout.stride(), VK_VERTEX_INPUT_RATE_VERTEX)
};
std::vector<VkVertexInputAttributeDescription> 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<uint32_t>(vertexInputBindings.size());
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(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
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<float>(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, radius, 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::vec3 cascadeExtents = maxExtents - minExtents;
glm::mat4 lightOrthoMatrix = glm::ortho(minExtents.x, maxExtents.x, minExtents.y, maxExtents.y, 0.0f, cascadeExtents.z);
// Store split distance and matrix in cascade
const float clipDist = camera.getFarClip() - camera.getNearClip();
cascades[i].splitDepth = (camera.getNearClip() + splitDist * clipDist) * -1.0f;
cascades[i].viewProjMatrix = lightOrthoMatrix * lightViewMatrix;
lastSplitDist = cascadeSplits[i];
}
}
void updateLight()
{
lightPos.x = cos(glm::radians(timer * 360.0f)) * 50.0f;
lightPos.y = -20.0f;
lightPos.z = sin(glm::radians(timer * 360.0f)) * 50.0f;
}
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();
prepareShadowMaps();
prepareUniformBuffers();
setupDescriptorPool();
setupLayoutsAndDescriptors();
preparePipelines();
buildCommandBuffers();
buildOffscreenCommandBuffer();
updateCascades();
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()