ADD: CSM half-working

This commit is contained in:
2025-10-12 00:20:12 +09:00
parent b2fdcf5310
commit 26b7db9030
16 changed files with 297 additions and 105 deletions

View File

@@ -676,8 +676,15 @@ void RenderGraph::execute(VkCommandBuffer cmd)
if (rec && rec->imageView != VK_NULL_HANDLE)
{
depthInfo = vkinit::depth_attachment_info(rec->imageView, VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL);
if (p.depthAttachment.clearOnLoad) depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
else depthInfo.loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
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.store) depthInfo.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
hasDepth = true;
if (rec->extent.width && rec->extent.height) chosenExtent = clamp_min(chosenExtent, rec->extent);

View File

@@ -10,11 +10,13 @@
#include "core/vk_pipeline_manager.h"
#include "core/asset_manager.h"
#include "core/vk_descriptors.h"
#include "core/config.h"
#include "vk_mem_alloc.h"
#include "vk_sampler_manager.h"
#include "vk_swapchain.h"
#include "render/rg_graph.h"
#include <array>
void LightingPass::init(EngineContext *context)
{
@@ -43,10 +45,10 @@ void LightingPass::init(EngineContext *context)
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;
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);
}
@@ -95,9 +97,9 @@ void LightingPass::register_graph(RenderGraph *graph,
RGImageHandle gbufferPosition,
RGImageHandle gbufferNormal,
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;
}
@@ -105,18 +107,21 @@ void LightingPass::register_graph(RenderGraph *graph,
graph->add_pass(
"Lighting",
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(gbufferNormal, 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);
},
[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,
const RGPassResources &resources,
RGImageHandle drawHandle,
RGImageHandle shadowDepth)
std::span<RGImageHandle> shadowCascades)
{
EngineContext *ctxLocal = context ? context : _context;
if (!ctxLocal || !ctxLocal->currentFrame) return;
@@ -173,12 +178,21 @@ void LightingPass::draw_lighting(VkCommandBuffer cmd,
VkDescriptorSet shadowSet = ctxLocal->currentFrame->_frameDescriptors.allocate(
deviceManager->device(), _shadowDescriptorLayout);
{
VkImageView shadowView = resources.image_view(shadowDepth);
DescriptorWriter writer2;
writer2.write_image(0, shadowView, ctxLocal->getSamplers()->defaultLinear(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER);
writer2.update_set(deviceManager->device(), shadowSet);
const uint32_t cascadeCount = std::min<uint32_t>(kShadowCascadeCount, static_cast<uint32_t>(shadowCascades.size()));
std::array<VkDescriptorImageInfo, kShadowCascadeCount> infos{};
for (uint32_t i = 0; i < cascadeCount; ++i)
{
infos[i].sampler = ctxLocal->getSamplers()->shadowLinearClamp();
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);

View File

@@ -1,6 +1,7 @@
#pragma once
#include "vk_renderpass.h"
#include <render/rg_types.h>
#include <span>
class LightingPass : public IRenderPass
{
@@ -13,19 +14,20 @@ public:
const char *getName() const override { return "Lighting"; }
// Register lighting; consumes GBuffer + CSM cascades.
void register_graph(class RenderGraph *graph,
RGImageHandle drawHandle,
RGImageHandle gbufferPosition,
RGImageHandle gbufferNormal,
RGImageHandle gbufferAlbedo,
RGImageHandle shadowDepth);
std::span<RGImageHandle> shadowCascades);
private:
EngineContext *_context = nullptr;
VkDescriptorSetLayout _gBufferInputDescriptorLayout = VK_NULL_HANDLE;
VkDescriptorSet _gBufferInputDescriptorSet = VK_NULL_HANDLE;
VkDescriptorSetLayout _shadowDescriptorLayout = VK_NULL_HANDLE; // set=2
VkDescriptorSetLayout _shadowDescriptorLayout = VK_NULL_HANDLE; // set=2 (array)
VkPipelineLayout _pipelineLayout = VK_NULL_HANDLE;
VkPipeline _pipeline = VK_NULL_HANDLE;
@@ -34,7 +36,7 @@ private:
EngineContext *context,
const class RGPassResources &resources,
RGImageHandle drawHandle,
RGImageHandle shadowDepth);
std::span<RGImageHandle> shadowCascades);
DeletionQueue _deletionQueue;
};

View File

@@ -1,6 +1,7 @@
#include "vk_renderpass_shadow.h"
#include <unordered_set>
#include <string>
#include "core/engine_context.h"
#include "render/rg_graph.h"
@@ -24,9 +25,13 @@ void ShadowPass::init(EngineContext *context)
if (!_context || !_context->pipelines) return;
// Build a depth-only graphics pipeline for shadow map rendering
// Keep push constants matching current shader layout for now
VkPushConstantRange pc{};
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;
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_multisampling_none();
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.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.depthBiasConstantFactor = 2.0f;
b._rasterizer.depthBiasSlopeFactor = 2.0f;
@@ -65,53 +70,61 @@ void ShadowPass::execute(VkCommandBuffer)
// 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;
graph->add_pass(
"ShadowMap",
RGPassType::Graphics,
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
{
// Reverse-Z depth clear to 0.0
VkClearValue clear{}; clear.depthStencil = {0.f, 0};
builder.write_depth(shadowDepth, true, clear);
for (uint32_t i = 0; i < cascades.size(); ++i)
{
RGImageHandle shadowDepth = cascades[i];
if (!shadowDepth.valid()) continue;
// Ensure index/vertex buffers are tracked as reads (like Geometry)
if (ctx)
std::string passName = std::string("ShadowMap[") + std::to_string(i) + "]";
graph->add_pass(
passName.c_str(),
RGPassType::Graphics,
[shadowDepth](RGPassBuilder &builder, EngineContext *ctx)
{
const DrawContext &dc = ctx->getMainDrawContext();
std::unordered_set<VkBuffer> indexSet;
std::unordered_set<VkBuffer> vertexSet;
auto collect = [&](const std::vector<RenderObject> &v)
{
for (const auto &r : v)
{
if (r.indexBuffer) indexSet.insert(r.indexBuffer);
if (r.vertexBuffer) vertexSet.insert(r.vertexBuffer);
}
};
collect(dc.OpaqueSurfaces);
// Transparent surfaces are ignored for shadow map in this simple pass
// Reverse-Z depth clear to 0.0
VkClearValue clear{}; clear.depthStencil = {0.f, 0};
builder.write_depth(shadowDepth, true, clear);
for (VkBuffer b : indexSet)
builder.read_buffer(b, RGBufferUsage::IndexRead, 0, "shadow.index");
for (VkBuffer b : vertexSet)
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "shadow.vertex");
}
},
[this, shadowDepth, extent](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
{
draw_shadow(cmd, ctx, res, shadowDepth, extent);
});
// Ensure index/vertex buffers are tracked as reads (like Geometry)
if (ctx)
{
const DrawContext &dc = ctx->getMainDrawContext();
std::unordered_set<VkBuffer> indexSet;
std::unordered_set<VkBuffer> vertexSet;
auto collect = [&](const std::vector<RenderObject> &v)
{
for (const auto &r : v)
{
if (r.indexBuffer) indexSet.insert(r.indexBuffer);
if (r.vertexBuffer) vertexSet.insert(r.vertexBuffer);
}
};
collect(dc.OpaqueSurfaces);
// Ignore transparent for shadow map
for (VkBuffer b : indexSet)
builder.read_buffer(b, RGBufferUsage::IndexRead, 0, "shadow.index");
for (VkBuffer b : vertexSet)
builder.read_buffer(b, RGBufferUsage::StorageRead, 0, "shadow.vertex");
}
},
[this, shadowDepth, extent, i](VkCommandBuffer cmd, const RGPassResources &res, EngineContext *ctx)
{
draw_shadow(cmd, ctx, res, shadowDepth, extent, i);
});
}
}
void ShadowPass::draw_shadow(VkCommandBuffer cmd,
EngineContext *context,
const RGPassResources &/*resources*/,
RGImageHandle /*shadowDepth*/,
VkExtent2D extent) const
VkExtent2D extent,
uint32_t cascadeIndex) const
{
EngineContext *ctxLocal = context ? context : _context;
if (!ctxLocal || !ctxLocal->currentFrame) return;
@@ -166,6 +179,12 @@ void ShadowPass::draw_shadow(VkCommandBuffer cmd,
const DrawContext &dc = ctxLocal->getMainDrawContext();
VkBuffer lastIndexBuffer = VK_NULL_HANDLE;
struct ShadowPC
{
GPUDrawPushConstants draw;
uint32_t cascadeIndex;
};
for (const auto &r : dc.OpaqueSurfaces)
{
if (r.indexBuffer != lastIndexBuffer)
@@ -174,11 +193,11 @@ void ShadowPass::draw_shadow(VkCommandBuffer cmd,
vkCmdBindIndexBuffer(cmd, r.indexBuffer, 0, VK_INDEX_TYPE_UINT32);
}
GPUDrawPushConstants pc{};
pc.worldMatrix = r.transform;
pc.vertexBuffer = r.vertexBufferAddress;
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(GPUDrawPushConstants), &pc);
ShadowPC spc{};
spc.draw.worldMatrix = r.transform;
spc.draw.vertexBuffer = r.vertexBufferAddress;
spc.cascadeIndex = cascadeIndex;
vkCmdPushConstants(cmd, layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(ShadowPC), &spc);
vkCmdDrawIndexed(cmd, r.indexCount, 1, r.firstIndex, 0, 0);
}
}

View File

@@ -2,14 +2,13 @@
#include "vk_renderpass.h"
#include <render/rg_types.h>
#include <span>
class RenderGraph;
class EngineContext;
class RGPassResources;
// Depth-only directional shadow map pass (skeleton)
// - Writes a depth image using reversed-Z (clear=0)
// - Draw function will be filled in a later step
// Depth-only directional shadow map pass (CSM-ready API)
class ShadowPass : public IRenderPass
{
public:
@@ -19,8 +18,8 @@ public:
const char *getName() const override { return "ShadowMap"; }
// Register the depth-only pass into the render graph
void register_graph(RenderGraph *graph, RGImageHandle shadowDepth, VkExtent2D extent);
// Register N cascades; one graphics pass per cascade.
void register_graph(RenderGraph *graph, std::span<RGImageHandle> cascades, VkExtent2D extent);
private:
EngineContext *_context = nullptr;
@@ -29,5 +28,6 @@ private:
EngineContext *context,
const RGPassResources &resources,
RGImageHandle shadowDepth,
VkExtent2D extent) const;
VkExtent2D extent,
uint32_t cascadeIndex) const;
};