diff --git a/data/shaders/shadowmappingcascade/debugshadowmap.frag b/data/shaders/shadowmappingcascade/debugshadowmap.frag new file mode 100644 index 00000000..70aaf94a --- /dev/null +++ b/data/shaders/shadowmappingcascade/debugshadowmap.frag @@ -0,0 +1,17 @@ +#version 450 + +layout (binding = 1) uniform sampler2DArray shadowMap; + +layout (location = 0) in vec2 inUV; + +layout (location = 0) out vec4 outFragColor; + +layout(push_constant) uniform PushConsts { + uint cascadeIndex; +} pushConsts; + +void main() +{ + float depth = texture(shadowMap, vec3(inUV, float(pushConsts.cascadeIndex))).r; + outFragColor = vec4(vec3((depth)), 1.0); +} \ No newline at end of file diff --git a/data/shaders/shadowmappingcascade/debugshadowmap.frag.spv b/data/shaders/shadowmappingcascade/debugshadowmap.frag.spv new file mode 100644 index 00000000..04713235 Binary files /dev/null and b/data/shaders/shadowmappingcascade/debugshadowmap.frag.spv differ diff --git a/data/shaders/shadowmappingcascade/debugshadowmap.vert b/data/shaders/shadowmappingcascade/debugshadowmap.vert new file mode 100644 index 00000000..780bdf00 --- /dev/null +++ b/data/shaders/shadowmappingcascade/debugshadowmap.vert @@ -0,0 +1,14 @@ +#version 450 + +layout (location = 0) out vec2 outUV; + +out gl_PerVertex { + vec4 gl_Position; +}; + + +void main() +{ + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 0.0f, 1.0f); +} diff --git a/data/shaders/shadowmappingcascade/debugshadowmap.vert.spv b/data/shaders/shadowmappingcascade/debugshadowmap.vert.spv new file mode 100644 index 00000000..f9cc1345 Binary files /dev/null and b/data/shaders/shadowmappingcascade/debugshadowmap.vert.spv differ diff --git a/data/shaders/shadowmappingcascade/depthpass.frag b/data/shaders/shadowmappingcascade/depthpass.frag new file mode 100644 index 00000000..9941acf4 --- /dev/null +++ b/data/shaders/shadowmappingcascade/depthpass.frag @@ -0,0 +1,5 @@ +#version 450 + +void main() +{ +} \ No newline at end of file diff --git a/data/shaders/shadowmappingcascade/depthpass.frag.spv b/data/shaders/shadowmappingcascade/depthpass.frag.spv new file mode 100644 index 00000000..b128ff56 Binary files /dev/null and b/data/shaders/shadowmappingcascade/depthpass.frag.spv differ diff --git a/data/shaders/shadowmappingcascade/depthpass.vert b/data/shaders/shadowmappingcascade/depthpass.vert new file mode 100644 index 00000000..9bf8daf2 --- /dev/null +++ b/data/shaders/shadowmappingcascade/depthpass.vert @@ -0,0 +1,23 @@ +#version 450 + +layout (location = 0) in vec3 inPos; + +// todo: pass via specialization constant +#define SHADOW_MAP_CASCADE_COUNT 4 + +layout(push_constant) uniform PushConsts { + uint cascadeIndex; +} pushConsts; + +layout (binding = 0) uniform UBO { + mat4[SHADOW_MAP_CASCADE_COUNT] cascadeViewProjMat; +} ubo; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() +{ + gl_Position = ubo.cascadeViewProjMat[pushConsts.cascadeIndex] * vec4(inPos, 1.0); +} \ No newline at end of file diff --git a/data/shaders/shadowmappingcascade/depthpass.vert.spv b/data/shaders/shadowmappingcascade/depthpass.vert.spv new file mode 100644 index 00000000..c9a61d1a Binary files /dev/null and b/data/shaders/shadowmappingcascade/depthpass.vert.spv differ diff --git a/data/shaders/shadowmappingcascade/scene.frag b/data/shaders/shadowmappingcascade/scene.frag new file mode 100644 index 00000000..d6d7fc30 --- /dev/null +++ b/data/shaders/shadowmappingcascade/scene.frag @@ -0,0 +1,116 @@ +#version 450 + +#define SHADOW_MAP_CASCADE_COUNT 4 + +layout (binding = 1) uniform sampler2DArray shadowMap; + +layout (location = 0) in vec3 inNormal; +layout (location = 1) in vec3 inColor; +layout (location = 2) in vec3 inViewPos; +layout (location = 3) in vec3 inPos; + +layout (constant_id = 0) const int enablePCF = 0; + +layout (location = 0) out vec4 outFragColor; + +#define ambient 0.1 + +layout (binding = 2) uniform UBO { + vec4 cascadeSplits; + mat4 cascadeViewProjMat[SHADOW_MAP_CASCADE_COUNT]; + mat4 inverseViewMat; + vec3 lightDir; + float _pad; + int colorCascades; +} ubo; + +const mat4 biasMat = mat4( + 0.5, 0.0, 0.0, 0.0, + 0.0, 0.5, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.5, 0.5, 0.0, 1.0 +); + +float textureProj(vec4 P, vec2 offset, uint cascadeIndex) +{ + float shadow = 1.0; + float bias = 0.005; + + vec4 shadowCoord = P / P.w; + if ( shadowCoord.z > -1.0 && shadowCoord.z < 1.0 ) { + float dist = texture(shadowMap, vec3(shadowCoord.st + offset, cascadeIndex)).r; + if (shadowCoord.w > 0 && dist < shadowCoord.z - bias) { + shadow = ambient; + } + } + return shadow; + +} + +float filterPCF(vec4 sc, uint cascadeIndex) +{ + ivec2 texDim = textureSize(shadowMap, 0).xy; + float scale = 1.5; + float dx = scale * 1.0 / float(texDim.x); + float dy = scale * 1.0 / float(texDim.y); + + float shadowFactor = 0.0; + int count = 0; + int range = 1; + + for (int x = -range; x <= range; x++) { + for (int y = -range; y <= range; y++) { + shadowFactor += textureProj(sc, vec2(dx*x, dy*y), cascadeIndex); + count++; + } + } + return shadowFactor / count; +} + +void main() +{ + // Get cascade index for the current fragment's view position + uint cascadeIndex = 0; + for(uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) { + if(inViewPos.z < ubo.cascadeSplits[i]) { + cascadeIndex = i + 1; + } + } + + // Depth compare for shadowing + vec4 shadowCoord = (biasMat * ubo.cascadeViewProjMat[cascadeIndex]) * vec4(inPos, 1.0); + + float shadow = 0; + if (enablePCF == 1) { + shadow = filterPCF(shadowCoord / shadowCoord.w, cascadeIndex); + } else { + shadow = textureProj(shadowCoord / shadowCoord.w, vec2(0.0), cascadeIndex); + } + + // Directional light + vec3 N = normalize(inNormal); + vec3 L = normalize(-ubo.lightDir); + vec3 H = normalize(L + inViewPos); + float diffuse = max(dot(N, L), 0.0); + vec3 lightColor = vec3(1.0); + outFragColor.rgb = max(lightColor * (diffuse ), vec3(0.0)); + outFragColor.rgb *= shadow; + + // Color cascades (if enabled) + if (ubo.colorCascades == 1) { + switch(cascadeIndex) { + case 0 : + outFragColor.rgb *= vec3(1.0f, 0.0f, 0.0f); + break; + case 1 : + outFragColor.rgb *= vec3(0.0f, 1.0f, 0.0f); + break; + case 2 : + outFragColor.rgb *= vec3(0.0f, 0.0f, 1.0f); + break; + case 3 : + outFragColor.rgb *= vec3(1.0f, 1.0f, 0.0f); + break; + } + } +} \ No newline at end of file diff --git a/data/shaders/shadowmappingcascade/scene.frag.spv b/data/shaders/shadowmappingcascade/scene.frag.spv new file mode 100644 index 00000000..63d9f2b2 Binary files /dev/null and b/data/shaders/shadowmappingcascade/scene.frag.spv differ diff --git a/data/shaders/shadowmappingcascade/scene.vert b/data/shaders/shadowmappingcascade/scene.vert new file mode 100644 index 00000000..a75dfd89 --- /dev/null +++ b/data/shaders/shadowmappingcascade/scene.vert @@ -0,0 +1,31 @@ +#version 450 + +layout (location = 0) in vec3 inPos; +layout (location = 1) in vec2 inUV; +layout (location = 2) in vec3 inColor; +layout (location = 3) in vec3 inNormal; + +layout (binding = 0) uniform UBO { + mat4 projection; + mat4 view; + mat4 model; +} ubo; + +layout (location = 0) out vec3 outNormal; +layout (location = 1) out vec3 outColor; +layout (location = 2) out vec3 outViewPos; +layout (location = 3) out vec3 outPos; + +out gl_PerVertex { + vec4 gl_Position; +}; + +void main() +{ + outColor = inColor; + outNormal = inNormal; + outPos = inPos; + outViewPos = (ubo.view * vec4(inPos.xyz, 1.0)).xyz; + gl_Position = ubo.projection * ubo.view * ubo.model * vec4(inPos.xyz, 1.0); +} + diff --git a/data/shaders/shadowmappingcascade/scene.vert.spv b/data/shaders/shadowmappingcascade/scene.vert.spv new file mode 100644 index 00000000..f7395d5d Binary files /dev/null and b/data/shaders/shadowmappingcascade/scene.vert.spv differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index b7e16b5f..9119db6c 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -78,6 +78,7 @@ set(EXAMPLES screenshot shadowmapping shadowmappingomni + shadowmappingcascade skeletalanimation specializationconstants sphericalenvmapping diff --git a/examples/shadowmappingcascade/shadowmappingcascade.cpp b/examples/shadowmappingcascade/shadowmappingcascade.cpp new file mode 100644 index 00000000..d777d2f5 --- /dev/null +++ b/examples/shadowmappingcascade/shadowmappingcascade.cpp @@ -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 +#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 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 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(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 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 = 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 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 + 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, 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()