#include #include #include #include #include #include #include #include #include #include #include #include "volk.h" #include "spdlog/spdlog.h" void ShadowMapPipeline::init(GfxDevice& gfxDevice, RenderResources& resources) { try { const auto vertexShader = AssetFS::GetInstance().GetCookedPathForFile("engine://shaders/shadow.vert"); const auto bufferRange = VkPushConstantRange{ .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .offset = 0, .size = sizeof(PushConstants), }; const auto pushConstantRanges = std::array{bufferRange}; const auto layouts = std::array{ resources.getBindlessDescSetLayout() }; VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = static_cast(layouts.size()); pipelineLayoutInfo.pSetLayouts = layouts.data(); pipelineLayoutInfo.pushConstantRangeCount = static_cast(pushConstantRanges.size()); pipelineLayoutInfo.pPushConstantRanges = pushConstantRanges.data(); if (vkCreatePipelineLayout( gfxDevice.getDevice(), &pipelineLayoutInfo, nullptr, &m_pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("Could not create shadow map pipeline layout"); } PipelineConfigInfo pipelineConfig{}; Pipeline::DefaultPipelineConfigInfo(pipelineConfig); pipelineConfig.name = "Shadow Map Pipeline"; pipelineConfig.pipelineLayout = m_pipelineLayout; pipelineConfig.vertexAttributeDescriptions = {}; pipelineConfig.vertexBindingDescriptions = {}; pipelineConfig.colorAttachments = {}; pipelineConfig.colorBlendInfo.attachmentCount = 0; pipelineConfig.depthAttachment = shadowMapFormat; // the light projection isn't Y flipped like the camera's, so the winding is mirrored pipelineConfig.rasterizationInfo.cullMode = VK_CULL_MODE_NONE; pipelineConfig.rasterizationInfo.depthBiasEnable = VK_TRUE; pipelineConfig.rasterizationInfo.depthBiasConstantFactor = 0.0f; pipelineConfig.rasterizationInfo.depthBiasSlopeFactor = 2.0f; m_pipeline = std::make_unique( gfxDevice, vertexShader.string(), "", pipelineConfig ); const auto createInfo = vkutil::CreateImageInfo{ .format = shadowMapFormat, .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, .extent = VkExtent3D{SHADOW_MAP_SIZE, SHADOW_MAP_SIZE, 1}, }; shadowMapId = resources.createImage(gfxDevice, createInfo, "shadow map", nullptr, shadowMapId); spdlog::info("Created {0}x{0} shadow map with id {1}", SHADOW_MAP_SIZE, shadowMapId); } catch (...) { cleanup(gfxDevice.getDevice()); throw; } } void ShadowMapPipeline::cleanup(VkDevice device) { m_pipeline.reset(); if (m_pipelineLayout != VK_NULL_HANDLE && device != VK_NULL_HANDLE) { vkDestroyPipelineLayout(device, m_pipelineLayout, nullptr); } m_pipelineLayout = VK_NULL_HANDLE; shadowMapId = NULL_IMAGE_ID; } glm::mat4 ShadowMapPipeline::calculateLightViewProj(const glm::vec3& lightDirection, const glm::vec3& center) const { const glm::vec3 direction = glm::normalize(lightDirection); const glm::vec3 up = std::abs(direction.y) > 0.99f ? glm::vec3{0.0f, 0.0f, 1.0f} : glm::vec3{0.0f, 1.0f, 0.0f}; const glm::mat4 lightView = glm::lookAt(glm::vec3{0.0f}, direction, up); const float texelSize = (2.0f * shadowDistance) / static_cast(SHADOW_MAP_SIZE); glm::vec3 lightSpaceCenter = glm::vec3(lightView * glm::vec4(center, 1.0f)); lightSpaceCenter.x = std::floor(lightSpaceCenter.x / texelSize) * texelSize; lightSpaceCenter.y = std::floor(lightSpaceCenter.y / texelSize) * texelSize; const glm::mat4 lightProj = glm::orthoRH_ZO( lightSpaceCenter.x - shadowDistance, lightSpaceCenter.x + shadowDistance, lightSpaceCenter.y - shadowDistance, lightSpaceCenter.y + shadowDistance, -lightSpaceCenter.z - shadowDepthRange, -lightSpaceCenter.z + shadowDepthRange); return lightProj * lightView; } float ShadowMapPipeline::getNormalOffset() const { const float texelSize = (2.0f * shadowDistance) / static_cast(SHADOW_MAP_SIZE); return texelSize * 1.5f; } void ShadowMapPipeline::draw( VkCommandBuffer cmd, const RenderResources& resources, const GPUBuffer& sceneDataBuffer, const std::vector& drawCommands, const std::vector& sortedDrawCommands) { if (!m_pipeline || shadowMapId == NULL_IMAGE_ID) { return; } const auto& shadowMap = resources.getImage(shadowMapId); vkutil::transitionImage( cmd, shadowMap.image, shadowMap.layout, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); shadowMap.setLayout(VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL); auto renderInfo = vkutil::createRenderingInfo({ .renderExtent = shadowMap.getExtent2D(), .depthImageView = shadowMap.imageView, .depthImageClearValue = 1.f, }); renderInfo.renderingInfo.pDepthAttachment = &renderInfo.depthAttachment; vkCmdBeginRendering(cmd, &renderInfo.renderingInfo); m_pipeline->bind(cmd); resources.bindBindlessDescSet(cmd, m_pipelineLayout); const auto viewport = VkViewport{ .x = 0.f, .y = 0.f, .width = static_cast(SHADOW_MAP_SIZE), .height = static_cast(SHADOW_MAP_SIZE), .minDepth = 0.f, .maxDepth = 1.f, }; vkCmdSetViewport(cmd, 0, 1, &viewport); const auto scissor = VkRect2D{ .offset = {}, .extent = shadowMap.getExtent2D(), }; vkCmdSetScissor(cmd, 0, 1, &scissor); vkCmdSetPolygonModeEXT(cmd, VK_POLYGON_MODE_FILL); const MeshCache& meshCache = resources.meshes(); MeshID prevMeshId = NULL_MESH_ID; const auto drawOne = [&](const MeshDrawCommand& dc) { if (!dc.castShadow) { return; } const auto& mesh = meshCache.getMesh(dc.meshId); const std::string& meshKey = meshCache.getMeshKey(dc.meshId); vkutil::cmdBeginLabelFormatted( cmd, vkutil::labelColors::draw, "%s [mesh %zu, material %u%s]", meshKey.empty() ? "Mesh" : meshKey.c_str(), dc.meshId, dc.materialId, dc.skinnedMesh != nullptr ? ", skinned" : ""); if (dc.meshId != prevMeshId) { prevMeshId = dc.meshId; vkCmdBindIndexBuffer(cmd, mesh.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); } const auto pushConstants = PushConstants{ .transform = dc.transformMatrix, .sceneDataBuffer = sceneDataBuffer.address, .vertexBuffer = dc.skinnedMesh != nullptr ? dc.skinnedMesh->skinnedVertexBuffer.address : mesh.vertexBuffer.address, .materialId = dc.materialId, .padding = 0, }; vkCmdPushConstants( cmd, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PushConstants), &pushConstants); vkCmdDrawIndexed(cmd, mesh.numIndices, 1, 0, 0, 0); vkutil::cmdEndLabel(cmd); }; if (!sortedDrawCommands.empty()) { for (std::size_t drawIndex : sortedDrawCommands) { assert(drawIndex < drawCommands.size()); drawOne(drawCommands[drawIndex]); } } else { for (const MeshDrawCommand& dc : drawCommands) { drawOne(dc); } } vkCmdEndRendering(cmd); vkutil::transitionImage( cmd, shadowMap.image, shadowMap.layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); shadowMap.setLayout(VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); }