ADD: CSM base code
This commit is contained in:
@@ -6,3 +6,14 @@ inline constexpr bool kUseValidationLayers = false;
|
|||||||
#else
|
#else
|
||||||
inline constexpr bool kUseValidationLayers = true;
|
inline constexpr bool kUseValidationLayers = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// Shadow mapping configuration
|
||||||
|
inline constexpr int kShadowCascadeCount = 4;
|
||||||
|
// Maximum shadow distance for CSM in view-space units
|
||||||
|
inline constexpr float kShadowCSMFar = 50.0f;
|
||||||
|
// Shadow map resolution used for stabilization (texel snapping). Must match actual image size.
|
||||||
|
inline constexpr float kShadowMapResolution = 2048.0f;
|
||||||
|
// Extra XY expansion for cascade footprint (safety against FOV/aspect changes)
|
||||||
|
inline constexpr float kShadowCascadeRadiusScale = 1.15f;
|
||||||
|
// Additive XY margin in world units (light-space) beyond scaled radius
|
||||||
|
inline constexpr float kShadowCascadeRadiusMargin = 10.0f;
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#include <core/vk_descriptors.h>
|
#include <core/vk_descriptors.h>
|
||||||
|
|
||||||
void DescriptorLayoutBuilder::add_binding(uint32_t binding, VkDescriptorType type)
|
void DescriptorLayoutBuilder::add_binding(uint32_t binding, VkDescriptorType type, uint32_t count)
|
||||||
{
|
{
|
||||||
VkDescriptorSetLayoutBinding newbind{};
|
VkDescriptorSetLayoutBinding newbind{};
|
||||||
newbind.binding = binding;
|
newbind.binding = binding;
|
||||||
newbind.descriptorCount = 1;
|
newbind.descriptorCount = count;
|
||||||
newbind.descriptorType = type;
|
newbind.descriptorType = type;
|
||||||
|
|
||||||
bindings.push_back(newbind);
|
bindings.push_back(newbind);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ struct DescriptorLayoutBuilder
|
|||||||
{
|
{
|
||||||
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
||||||
|
|
||||||
void add_binding(uint32_t binding, VkDescriptorType type);
|
void add_binding(uint32_t binding, VkDescriptorType type, uint32_t count = 1);
|
||||||
|
|
||||||
void clear();
|
void clear();
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
#include "render/vk_pipelines.h"
|
#include "render/vk_pipelines.h"
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
#include "render/primitives.h"
|
#include "render/primitives.h"
|
||||||
|
|
||||||
#include "vk_mem_alloc.h"
|
#include "vk_mem_alloc.h"
|
||||||
@@ -315,8 +317,14 @@ void VulkanEngine::draw()
|
|||||||
RGImageHandle hSwapchain = _renderGraph->import_swapchain_image(swapchainImageIndex);
|
RGImageHandle hSwapchain = _renderGraph->import_swapchain_image(swapchainImageIndex);
|
||||||
|
|
||||||
// Create a transient shadow depth target (fixed resolution for now)
|
// Create a transient shadow depth target (fixed resolution for now)
|
||||||
|
// Create transient depth targets for cascaded shadow maps
|
||||||
const VkExtent2D shadowExtent{2048, 2048};
|
const VkExtent2D shadowExtent{2048, 2048};
|
||||||
RGImageHandle hShadow = _renderGraph->create_depth_image("shadow.depth", shadowExtent, VK_FORMAT_D32_SFLOAT);
|
std::array<RGImageHandle, kShadowCascadeCount> hShadowCascades{};
|
||||||
|
for (int i = 0; i < kShadowCascadeCount; ++i)
|
||||||
|
{
|
||||||
|
std::string name = std::string("shadow.cascade.") + std::to_string(i);
|
||||||
|
hShadowCascades[i] = _renderGraph->create_depth_image(name.c_str(), shadowExtent, VK_FORMAT_D32_SFLOAT);
|
||||||
|
}
|
||||||
|
|
||||||
_resourceManager->register_upload_pass(*_renderGraph, get_current_frame());
|
_resourceManager->register_upload_pass(*_renderGraph, get_current_frame());
|
||||||
|
|
||||||
@@ -331,7 +339,7 @@ void VulkanEngine::draw()
|
|||||||
}
|
}
|
||||||
if (auto *shadow = _renderPassManager->getPass<ShadowPass>())
|
if (auto *shadow = _renderPassManager->getPass<ShadowPass>())
|
||||||
{
|
{
|
||||||
shadow->register_graph(_renderGraph.get(), hShadow, shadowExtent);
|
shadow->register_graph(_renderGraph.get(), std::span<RGImageHandle>(hShadowCascades.data(), hShadowCascades.size()), shadowExtent);
|
||||||
}
|
}
|
||||||
if (auto *geometry = _renderPassManager->getPass<GeometryPass>())
|
if (auto *geometry = _renderPassManager->getPass<GeometryPass>())
|
||||||
{
|
{
|
||||||
@@ -339,7 +347,8 @@ void VulkanEngine::draw()
|
|||||||
}
|
}
|
||||||
if (auto *lighting = _renderPassManager->getPass<LightingPass>())
|
if (auto *lighting = _renderPassManager->getPass<LightingPass>())
|
||||||
{
|
{
|
||||||
lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo, hShadow);
|
lighting->register_graph(_renderGraph.get(), hDraw, hGBufferPosition, hGBufferNormal, hGBufferAlbedo,
|
||||||
|
std::span<RGImageHandle>(hShadowCascades.data(), hShadowCascades.size()));
|
||||||
}
|
}
|
||||||
if (auto *transparent = _renderPassManager->getPass<TransparentPass>())
|
if (auto *transparent = _renderPassManager->getPass<TransparentPass>())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,16 @@ void SamplerManager::init(DeviceManager *deviceManager)
|
|||||||
sampl.magFilter = VK_FILTER_LINEAR;
|
sampl.magFilter = VK_FILTER_LINEAR;
|
||||||
sampl.minFilter = VK_FILTER_LINEAR;
|
sampl.minFilter = VK_FILTER_LINEAR;
|
||||||
vkCreateSampler(_deviceManager->device(), &sampl, nullptr, &_defaultSamplerLinear);
|
vkCreateSampler(_deviceManager->device(), &sampl, nullptr, &_defaultSamplerLinear);
|
||||||
|
|
||||||
|
// Shadow linear clamp sampler (border=white)
|
||||||
|
VkSamplerCreateInfo sh = sampl;
|
||||||
|
sh.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||||
|
sh.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||||
|
sh.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER;
|
||||||
|
sh.compareEnable = VK_FALSE; // manual PCF
|
||||||
|
sh.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
|
||||||
|
vkCreateSampler(_deviceManager->device(), &sh, nullptr, &_shadowLinearClamp);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SamplerManager::cleanup()
|
void SamplerManager::cleanup()
|
||||||
@@ -44,4 +54,10 @@ void SamplerManager::cleanup()
|
|||||||
vkDestroySampler(_deviceManager->device(), _defaultSamplerLinear, nullptr);
|
vkDestroySampler(_deviceManager->device(), _defaultSamplerLinear, nullptr);
|
||||||
_defaultSamplerLinear = VK_NULL_HANDLE;
|
_defaultSamplerLinear = VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_shadowLinearClamp)
|
||||||
|
{
|
||||||
|
vkDestroySampler(_deviceManager->device(), _shadowLinearClamp, nullptr);
|
||||||
|
_shadowLinearClamp = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,13 @@ public:
|
|||||||
|
|
||||||
VkSampler defaultLinear() const { return _defaultSamplerLinear; }
|
VkSampler defaultLinear() const { return _defaultSamplerLinear; }
|
||||||
VkSampler defaultNearest() const { return _defaultSamplerNearest; }
|
VkSampler defaultNearest() const { return _defaultSamplerNearest; }
|
||||||
|
VkSampler shadowLinearClamp() const { return _shadowLinearClamp; }
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
DeviceManager *_deviceManager = nullptr;
|
DeviceManager *_deviceManager = nullptr;
|
||||||
VkSampler _defaultSamplerLinear = VK_NULL_HANDLE;
|
VkSampler _defaultSamplerLinear = VK_NULL_HANDLE;
|
||||||
VkSampler _defaultSamplerNearest = VK_NULL_HANDLE;
|
VkSampler _defaultSamplerNearest = VK_NULL_HANDLE;
|
||||||
|
VkSampler _shadowLinearClamp = VK_NULL_HANDLE;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -75,6 +75,9 @@ struct GPUSceneData {
|
|||||||
glm::vec4 ambientColor;
|
glm::vec4 ambientColor;
|
||||||
glm::vec4 sunlightDirection; // w for sun power
|
glm::vec4 sunlightDirection; // w for sun power
|
||||||
glm::vec4 sunlightColor;
|
glm::vec4 sunlightColor;
|
||||||
|
|
||||||
|
glm::mat4 lightViewProjCascades[4];
|
||||||
|
glm::vec4 cascadeSplitsView;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class MaterialPass :uint8_t {
|
enum class MaterialPass :uint8_t {
|
||||||
|
|||||||
@@ -676,6 +676,15 @@ void RenderGraph::execute(VkCommandBuffer cmd)
|
|||||||
if (rec && rec->imageView != VK_NULL_HANDLE)
|
if (rec && rec->imageView != VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
depthInfo = vkinit::depth_attachment_info(rec->imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
depthInfo = vkinit::depth_attachment_info(rec->imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
|
||||||
|
if (p.depthAttachment.clearOnLoad)
|
||||||
|
{
|
||||||
|
depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
|
depthInfo.clearValue = p.depthAttachment.clear;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
|
}
|
||||||
if (p.depthAttachment.clearOnLoad) depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
if (p.depthAttachment.clearOnLoad) depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||||||
if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
if (!p.depthAttachment.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||||
|
|||||||
@@ -10,11 +10,13 @@
|
|||||||
#include "core/vk_pipeline_manager.h"
|
#include "core/vk_pipeline_manager.h"
|
||||||
#include "core/asset_manager.h"
|
#include "core/asset_manager.h"
|
||||||
#include "core/vk_descriptors.h"
|
#include "core/vk_descriptors.h"
|
||||||
|
#include "core/config.h"
|
||||||
|
|
||||||
#include "vk_mem_alloc.h"
|
#include "vk_mem_alloc.h"
|
||||||
#include "vk_sampler_manager.h"
|
#include "vk_sampler_manager.h"
|
||||||
#include "vk_swapchain.h"
|
#include "vk_swapchain.h"
|
||||||
#include "render/rg_graph.h"
|
#include "render/rg_graph.h"
|
||||||
|
#include <array>
|
||||||
|
|
||||||
void LightingPass::init(EngineContext *context)
|
void LightingPass::init(EngineContext *context)
|
||||||
{
|
{
|
||||||
@@ -43,10 +45,10 @@ void LightingPass::init(EngineContext *context)
|
|||||||
writer.update_set(_context->getDevice()->device(), _gBufferInputDescriptorSet);
|
writer.update_set(_context->getDevice()->device(), _gBufferInputDescriptorSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shadow map descriptor layout (set = 2, updated per-frame)
|
// Shadow map descriptor layout (set = 2, updated per-frame). Use array of cascades
|
||||||
{
|
{
|
||||||
DescriptorLayoutBuilder builder;
|
DescriptorLayoutBuilder builder;
|
||||||
builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
builder.add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, kShadowCascadeCount);
|
||||||
_shadowDescriptorLayout = builder.build(_context->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT);
|
_shadowDescriptorLayout = builder.build(_context->getDevice()->device(), VK_SHADER_STAGE_FRAGMENT_BIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +97,9 @@ void LightingPass::register_graph(RenderGraph *graph,
|
|||||||
RGImageHandle gbufferPosition,
|
RGImageHandle gbufferPosition,
|
||||||
RGImageHandle gbufferNormal,
|
RGImageHandle gbufferNormal,
|
||||||
RGImageHandle gbufferAlbedo,
|
RGImageHandle gbufferAlbedo,
|
||||||
RGImageHandle shadowDepth)
|
std::span<RGImageHandle> shadowCascades)
|
||||||
{
|
{
|
||||||
if (!graph || !drawHandle.valid() || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid() || !shadowDepth.valid())
|
if (!graph || !drawHandle.valid() || !gbufferPosition.valid() || !gbufferNormal.valid() || !gbufferAlbedo.valid())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -105,18 +107,21 @@ void LightingPass::register_graph(RenderGraph *graph,
|
|||||||
graph->add_pass(
|
graph->add_pass(
|
||||||
"Lighting",
|
"Lighting",
|
||||||
RGPassType::Graphics,
|
RGPassType::Graphics,
|
||||||
[drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, shadowDepth](RGPassBuilder &builder, EngineContext *)
|
[drawHandle, gbufferPosition, gbufferNormal, gbufferAlbedo, shadowCascades](RGPassBuilder &builder, EngineContext *)
|
||||||
{
|
{
|
||||||
builder.read(gbufferPosition, RGImageUsage::SampledFragment);
|
builder.read(gbufferPosition, RGImageUsage::SampledFragment);
|
||||||
builder.read(gbufferNormal, RGImageUsage::SampledFragment);
|
builder.read(gbufferNormal, RGImageUsage::SampledFragment);
|
||||||
builder.read(gbufferAlbedo, RGImageUsage::SampledFragment);
|
builder.read(gbufferAlbedo, RGImageUsage::SampledFragment);
|
||||||
builder.read(shadowDepth, RGImageUsage::SampledFragment);
|
for (size_t i = 0; i < shadowCascades.size(); ++i)
|
||||||
|
{
|
||||||
|
if (shadowCascades[i].valid()) builder.read(shadowCascades[i], RGImageUsage::SampledFragment);
|
||||||
|
}
|
||||||
|
|
||||||
builder.write_color(drawHandle);
|
builder.write_color(drawHandle);
|
||||||
},
|
},
|
||||||
[this, drawHandle, shadowDepth](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
[this, drawHandle, shadowCascades](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
||||||
{
|
{
|
||||||
draw_lighting(cmd, ctx, res, drawHandle, shadowDepth);
|
draw_lighting(cmd, ctx, res, drawHandle, shadowCascades);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +129,7 @@ void LightingPass::draw_lighting(VkCommandBuffer cmd,
|
|||||||
EngineContext *context,
|
EngineContext *context,
|
||||||
const RGPassResources &resources,
|
const RGPassResources &resources,
|
||||||
RGImageHandle drawHandle,
|
RGImageHandle drawHandle,
|
||||||
RGImageHandle shadowDepth)
|
std::span<RGImageHandle> shadowCascades)
|
||||||
{
|
{
|
||||||
EngineContext *ctxLocal = context ? context : _context;
|
EngineContext *ctxLocal = context ? context : _context;
|
||||||
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
||||||
@@ -173,12 +178,21 @@ void LightingPass::draw_lighting(VkCommandBuffer cmd,
|
|||||||
VkDescriptorSet shadowSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
VkDescriptorSet shadowSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
|
||||||
deviceManager->device(), _shadowDescriptorLayout);
|
deviceManager->device(), _shadowDescriptorLayout);
|
||||||
{
|
{
|
||||||
VkImageView shadowView = resources.image_view(shadowDepth);
|
const uint32_t cascadeCount = std::min<uint32_t>(kShadowCascadeCount, static_cast<uint32_t>(shadowCascades.size()));
|
||||||
DescriptorWriter writer2;
|
std::array<VkDescriptorImageInfo, kShadowCascadeCount> infos{};
|
||||||
writer2.write_image(0, shadowView, ctxLocal->getSamplers()->defaultLinear(),
|
for (uint32_t i = 0; i < cascadeCount; ++i)
|
||||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
{
|
||||||
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
|
infos[i].sampler = ctxLocal->getSamplers()->shadowLinearClamp();
|
||||||
writer2.update_set(deviceManager->device(), shadowSet);
|
infos[i].imageView = resources.image_view(shadowCascades[i]);
|
||||||
|
infos[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||||
|
}
|
||||||
|
VkWriteDescriptorSet write{.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||||
|
write.dstSet = shadowSet;
|
||||||
|
write.dstBinding = 0;
|
||||||
|
write.descriptorCount = cascadeCount;
|
||||||
|
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||||
|
write.pImageInfo = infos.data();
|
||||||
|
vkUpdateDescriptorSets(deviceManager->device(), 1, &write, 0, nullptr);
|
||||||
}
|
}
|
||||||
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 2, 1, &shadowSet, 0, nullptr);
|
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, _pipelineLayout, 2, 1, &shadowSet, 0, nullptr);
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ public:
|
|||||||
RGImageHandle drawHandle,
|
RGImageHandle drawHandle,
|
||||||
RGImageHandle gbufferPosition,
|
RGImageHandle gbufferPosition,
|
||||||
RGImageHandle gbufferNormal,
|
RGImageHandle gbufferNormal,
|
||||||
RGImageHandle gbufferAlbedo,
|
RGImageHandle gbufferAlbedo, std::span<RGImageHandle> shadowCascades);
|
||||||
RGImageHandle shadowDepth);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EngineContext *_context = nullptr;
|
EngineContext *_context = nullptr;
|
||||||
@@ -34,7 +33,7 @@ private:
|
|||||||
EngineContext *context,
|
EngineContext *context,
|
||||||
const class RGPassResources &resources,
|
const class RGPassResources &resources,
|
||||||
RGImageHandle drawHandle,
|
RGImageHandle drawHandle,
|
||||||
RGImageHandle shadowDepth);
|
std::span<RGImageHandle> shadowCascades);
|
||||||
|
|
||||||
DeletionQueue _deletionQueue;
|
DeletionQueue _deletionQueue;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "vk_renderpass_shadow.h"
|
#include "vk_renderpass_shadow.h"
|
||||||
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
#include "core/engine_context.h"
|
#include "core/engine_context.h"
|
||||||
#include "render/rg_graph.h"
|
#include "render/rg_graph.h"
|
||||||
@@ -24,9 +25,13 @@ void ShadowPass::init(EngineContext *context)
|
|||||||
if (!_context || !_context->pipelines) return;
|
if (!_context || !_context->pipelines) return;
|
||||||
|
|
||||||
// Build a depth-only graphics pipeline for shadow map rendering
|
// Build a depth-only graphics pipeline for shadow map rendering
|
||||||
|
// Keep push constants matching current shader layout for now
|
||||||
VkPushConstantRange pc{};
|
VkPushConstantRange pc{};
|
||||||
pc.offset = 0;
|
pc.offset = 0;
|
||||||
pc.size = sizeof(GPUDrawPushConstants);
|
// Push constants layout in shadow.vert is mat4 + device address + uint, rounded to 16 bytes
|
||||||
|
const uint32_t pcRaw = static_cast<uint32_t>(sizeof(GPUDrawPushConstants) + sizeof(uint32_t));
|
||||||
|
const uint32_t pcAligned = (pcRaw + 15u) & ~15u; // 16-byte alignment to match std430 expectations
|
||||||
|
pc.size = pcAligned;
|
||||||
pc.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
pc.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
|
||||||
|
|
||||||
GraphicsPipelineCreateInfo info{};
|
GraphicsPipelineCreateInfo info{};
|
||||||
@@ -40,11 +45,11 @@ void ShadowPass::init(EngineContext *context)
|
|||||||
b.set_cull_mode(VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
b.set_cull_mode(VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
|
||||||
b.set_multisampling_none();
|
b.set_multisampling_none();
|
||||||
b.disable_blending();
|
b.disable_blending();
|
||||||
// Reverse-Z depth test & depth-only pipeline
|
// Reverse-Z depth test for shadow maps (clear=0.0, GREATER_OR_EQUAL)
|
||||||
b.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
b.enable_depthtest(true, VK_COMPARE_OP_GREATER_OR_EQUAL);
|
||||||
b.set_depth_format(VK_FORMAT_D32_SFLOAT);
|
b.set_depth_format(VK_FORMAT_D32_SFLOAT);
|
||||||
|
|
||||||
// Static depth bias to help with surface acne (will tune later)
|
// Static depth bias to help with surface acne (tune later)
|
||||||
b._rasterizer.depthBiasEnable = VK_TRUE;
|
b._rasterizer.depthBiasEnable = VK_TRUE;
|
||||||
b._rasterizer.depthBiasConstantFactor = 2.0f;
|
b._rasterizer.depthBiasConstantFactor = 2.0f;
|
||||||
b._rasterizer.depthBiasSlopeFactor = 2.0f;
|
b._rasterizer.depthBiasSlopeFactor = 2.0f;
|
||||||
@@ -65,12 +70,18 @@ void ShadowPass::execute(VkCommandBuffer)
|
|||||||
// Shadow rendering is done via the RenderGraph registration.
|
// Shadow rendering is done via the RenderGraph registration.
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowPass::register_graph(RenderGraph *graph, RGImageHandle shadowDepth, VkExtent2D extent)
|
void ShadowPass::register_graph(RenderGraph *graph, std::span<RGImageHandle> cascades, VkExtent2D extent)
|
||||||
{
|
{
|
||||||
if (!graph || !shadowDepth.valid()) return;
|
if (!graph || cascades.empty()) return;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < cascades.size(); ++i)
|
||||||
|
{
|
||||||
|
RGImageHandle shadowDepth = cascades[i];
|
||||||
|
if (!shadowDepth.valid()) continue;
|
||||||
|
|
||||||
|
std::string passName = std::string("ShadowMap[") + std::to_string(i) + "]";
|
||||||
graph->add_pass(
|
graph->add_pass(
|
||||||
"ShadowMap",
|
passName.c_str(),
|
||||||
RGPassType::Graphics,
|
RGPassType::Graphics,
|
||||||
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
|
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
|
||||||
{
|
{
|
||||||
@@ -93,7 +104,7 @@ void ShadowPass::register_graph(RenderGraph *graph, RGImageHandle shadowDepth, V
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
collect(dc.OpaqueSurfaces);
|
collect(dc.OpaqueSurfaces);
|
||||||
// Transparent surfaces are ignored for shadow map in this simple pass
|
// Ignore transparent for shadow map
|
||||||
|
|
||||||
for (VkBuffer b : indexSet)
|
for (VkBuffer b : indexSet)
|
||||||
builder.read_buffer(b, RGBufferUsage::IndexRead, 0, "shadow.index");
|
builder.read_buffer(b, RGBufferUsage::IndexRead, 0, "shadow.index");
|
||||||
@@ -101,17 +112,19 @@ void ShadowPass::register_graph(RenderGraph *graph, RGImageHandle shadowDepth, V
|
|||||||
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "shadow.vertex");
|
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "shadow.vertex");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[this, shadowDepth, extent](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
[this, shadowDepth, extent, i](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
|
||||||
{
|
{
|
||||||
draw_shadow(cmd, ctx, res, shadowDepth, extent);
|
draw_shadow(cmd, ctx, res, shadowDepth, extent, i);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShadowPass::draw_shadow(VkCommandBuffer cmd,
|
void ShadowPass::draw_shadow(VkCommandBuffer cmd,
|
||||||
EngineContext *context,
|
EngineContext *context,
|
||||||
const RGPassResources &/*resources*/,
|
const RGPassResources &/*resources*/,
|
||||||
RGImageHandle /*shadowDepth*/,
|
RGImageHandle /*shadowDepth*/,
|
||||||
VkExtent2D extent) const
|
VkExtent2D extent,
|
||||||
|
uint32_t cascadeIndex) const
|
||||||
{
|
{
|
||||||
EngineContext *ctxLocal = context ? context : _context;
|
EngineContext *ctxLocal = context ? context : _context;
|
||||||
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
if (!ctxLocal || !ctxLocal->currentFrame) return;
|
||||||
@@ -166,6 +179,12 @@ void ShadowPass::draw_shadow(VkCommandBuffer cmd,
|
|||||||
const DrawContext &dc = ctxLocal->getMainDrawContext();
|
const DrawContext &dc = ctxLocal->getMainDrawContext();
|
||||||
|
|
||||||
VkBuffer lastIndexBuffer = VK_NULL_HANDLE;
|
VkBuffer lastIndexBuffer = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
struct ShadowPC
|
||||||
|
{
|
||||||
|
GPUDrawPushConstants draw;
|
||||||
|
uint32_t cascadeIndex;
|
||||||
|
};
|
||||||
for (const auto &r : dc.OpaqueSurfaces)
|
for (const auto &r : dc.OpaqueSurfaces)
|
||||||
{
|
{
|
||||||
if (r.indexBuffer != lastIndexBuffer)
|
if (r.indexBuffer != lastIndexBuffer)
|
||||||
@@ -174,11 +193,11 @@ void ShadowPass::draw_shadow(VkCommandBuffer cmd,
|
|||||||
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
|
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
|
||||||
}
|
}
|
||||||
|
|
||||||
GPUDrawPushConstants pc{};
|
ShadowPC spc{};
|
||||||
pc.worldMatrix = r.transform;
|
spc.draw.worldMatrix = r.transform;
|
||||||
pc.vertexBuffer = r.vertexBufferAddress;
|
spc.draw.vertexBuffer = r.vertexBufferAddress;
|
||||||
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(GPUDrawPushConstants), &pc);
|
spc.cascadeIndex = cascadeIndex;
|
||||||
|
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ShadowPC), &spc);
|
||||||
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@
|
|||||||
|
|
||||||
#include "vk_renderpass.h"
|
#include "vk_renderpass.h"
|
||||||
#include <render/rg_types.h>
|
#include <render/rg_types.h>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
class RenderGraph;
|
class RenderGraph;
|
||||||
class EngineContext;
|
class EngineContext;
|
||||||
class RGPassResources;
|
class RGPassResources;
|
||||||
|
|
||||||
// Depth-only directional shadow map pass (skeleton)
|
// Depth-only directional shadow map pass (CSM-ready API)
|
||||||
// - Writes a depth image using reversed-Z (clear=0)
|
|
||||||
// - Draw function will be filled in a later step
|
|
||||||
class ShadowPass : public IRenderPass
|
class ShadowPass : public IRenderPass
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -19,8 +18,8 @@ public:
|
|||||||
|
|
||||||
const char *getName() const override { return "ShadowMap"; }
|
const char *getName() const override { return "ShadowMap"; }
|
||||||
|
|
||||||
// Register the depth-only pass into the render graph
|
// Register N cascades; one graphics pass per cascade.
|
||||||
void register_graph(RenderGraph *graph, RGImageHandle shadowDepth, VkExtent2D extent);
|
void register_graph(RenderGraph *graph, std::span<RGImageHandle> cascades, VkExtent2D extent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EngineContext *_context = nullptr;
|
EngineContext *_context = nullptr;
|
||||||
@@ -29,5 +28,6 @@ private:
|
|||||||
EngineContext *context,
|
EngineContext *context,
|
||||||
const RGPassResources &resources,
|
const RGPassResources &resources,
|
||||||
RGImageHandle shadowDepth,
|
RGImageHandle shadowDepth,
|
||||||
VkExtent2D extent) const;
|
VkExtent2D extent,
|
||||||
|
uint32_t cascadeIndex) const;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "vk_swapchain.h"
|
#include "vk_swapchain.h"
|
||||||
#include "core/engine_context.h"
|
#include "core/engine_context.h"
|
||||||
|
#include "core/config.h"
|
||||||
#include "glm/gtx/transform.hpp"
|
#include "glm/gtx/transform.hpp"
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
@@ -104,7 +105,9 @@ void SceneManager::update_scene()
|
|||||||
sceneData.viewproj = projection * view;
|
sceneData.viewproj = projection * view;
|
||||||
|
|
||||||
// Build a simple directional light view-projection (reversed-Z orthographic)
|
// Build a simple directional light view-projection (reversed-Z orthographic)
|
||||||
// Centered around the camera for now (non-cascaded, non-stabilized)
|
// Centered around the camera for now. For the initial CSM-plumbing test,
|
||||||
|
// duplicate this single shadow matrix across all cascades so we render
|
||||||
|
// four identical shadow maps. This verifies the pass/descriptor wiring.
|
||||||
{
|
{
|
||||||
const glm::vec3 camPos = glm::vec3(glm::inverse(view)[3]);
|
const glm::vec3 camPos = glm::vec3(glm::inverse(view)[3]);
|
||||||
glm::vec3 L = glm::normalize(-glm::vec3(sceneData.sunlightDirection));
|
glm::vec3 L = glm::normalize(-glm::vec3(sceneData.sunlightDirection));
|
||||||
@@ -124,11 +127,27 @@ void SceneManager::update_scene()
|
|||||||
const float farDist = 200.0f;
|
const float farDist = 200.0f;
|
||||||
const glm::vec3 lightPos = camPos - L * 100.0f;
|
const glm::vec3 lightPos = camPos - L * 100.0f;
|
||||||
glm::mat4 viewLight = glm::lookAtRH(lightPos, camPos, up);
|
glm::mat4 viewLight = glm::lookAtRH(lightPos, camPos, up);
|
||||||
// Standard RH ZO ortho with near<far, then explicitly flip Z to reversed-Z
|
// Standard RH ZO ortho with near<far (works with our reversed-Z depth setup
|
||||||
|
// as we clamp and bias in shader). We'll stabilize/flip later when CSM lands.
|
||||||
glm::mat4 projLight = glm::orthoRH_ZO(-orthoRange, orthoRange, -orthoRange, orthoRange,
|
glm::mat4 projLight = glm::orthoRH_ZO(-orthoRange, orthoRange, -orthoRange, orthoRange,
|
||||||
nearDist, farDist);
|
nearDist, farDist);
|
||||||
|
|
||||||
sceneData.lightViewProj = projLight * viewLight;
|
sceneData.lightViewProj = projLight * viewLight;
|
||||||
|
|
||||||
|
// Fill cascade arrays with the same matrix for now so the shadow
|
||||||
|
// pass can run four times using identical transforms.
|
||||||
|
for (int c = 0; c < kShadowCascadeCount; ++c)
|
||||||
|
{
|
||||||
|
sceneData.lightViewProjCascades[c] = sceneData.lightViewProj;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provide a simple increasing split hint (view-space distances).
|
||||||
|
// Not used yet by shaders, but helps when we switch to CSM.
|
||||||
|
const float farView = kShadowCSMFar;
|
||||||
|
sceneData.cascadeSplitsView = glm::vec4(0.1f * farView,
|
||||||
|
0.3f * farView,
|
||||||
|
0.6f * farView,
|
||||||
|
1.0f * farView);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto end = std::chrono::system_clock::now();
|
auto end = std::chrono::system_clock::now();
|
||||||
|
|||||||
Reference in New Issue
Block a user