From 0328d4aaebfc2bbc86705780064bfb2c6f32d82a Mon Sep 17 00:00:00 2001 From: atdunbg Date: Wed, 23 Sep 2026 13:48:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20CSM=20=E7=BA=A7=E8=81=94=E9=98=B4?= =?UTF-8?q?=E5=BD=B1=EF=BC=883=20=E7=BA=A7=E8=81=94=20=C3=97=201024=C2=B2?= =?UTF-8?q?=EF=BC=8C=E5=AE=9E=E7=94=A8=E5=88=86=E5=89=B2=20+=20=E7=BA=B9?= =?UTF-8?q?=E7=B4=A0=E5=AF=B9=E9=BD=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SceneRenderer::ComputeCascades:相机视锥实用分割(λ=0.6)→ 反投影 取切片角点 → 光空间 AABB 拟合 → 纹素对齐(消除相机移动时阴影边缘 闪烁)→ [0,1] 深度重映射 - Shadow pass 单 pass 渲 3 个 region,每级联 push 各自光空间 VP - Forward:UBO lightSpace[3]+cascadeSplits(视图深度选级联),Set2 三张级联图按 binding 0/1/2 采样;vertex 输出 fragViewDepth - 光源视锥剔除用最远级联(覆盖全阴影范围);双视锥独立剔除铁律保留 - 旧单张 2048 全场景图退役;近处级联分辨率 ~128px/unit(约 2× 提升) - 编译零错误、validation 零警告、8 秒冒烟通过(10 pass 全活跃) --- .../Engine/Renderer/Passes/ForwardPass.h | 7 +- .../include/Engine/Renderer/Passes/SceneGpu.h | 7 +- .../Engine/Renderer/Passes/ShadowPass.h | 13 +- Engine/include/Engine/Renderer/Renderer.h | 3 +- .../include/Engine/Renderer/SceneRenderer.h | 7 +- Engine/include/Engine/Renderer/SceneTypes.h | 10 +- Engine/src/Renderer/Passes/ForwardPass.cpp | 6 +- Engine/src/Renderer/Passes/SceneGpu.cpp | 22 ++- Engine/src/Renderer/Passes/ShadowPass.cpp | 140 +++++++++--------- Engine/src/Renderer/Renderer.cpp | 15 +- Engine/src/Renderer/SceneRenderer.cpp | 103 ++++++++++--- shaders/simple_shader.frag | 41 +++-- shaders/simple_shader.vert | 17 ++- 13 files changed, 250 insertions(+), 141 deletions(-) diff --git a/Engine/include/Engine/Renderer/Passes/ForwardPass.h b/Engine/include/Engine/Renderer/Passes/ForwardPass.h index 36ea424..d733b1d 100644 --- a/Engine/include/Engine/Renderer/Passes/ForwardPass.h +++ b/Engine/include/Engine/Renderer/Passes/ForwardPass.h @@ -19,10 +19,11 @@ namespace Engine::Passes { public: ForwardPass(SceneGpu& gpu, FrameSceneData& frame, RG::TextureHandle hdrColor, RG::TextureHandle velocity, - RG::TextureHandle depth, RG::TextureHandle shadowMap) + RG::TextureHandle depth, + const std::array& shadowMaps) : m_Gpu(gpu), m_Frame(frame), m_HDRColor(hdrColor), m_Velocity(velocity), - m_Depth(depth), m_ShadowMap(shadowMap) {} + m_Depth(depth), m_ShadowMaps(shadowMaps) {} void Register(RG::RenderGraph& rg) override; @@ -32,7 +33,7 @@ namespace Engine::Passes { RG::TextureHandle m_HDRColor; RG::TextureHandle m_Velocity; // TAA 运动向量(第二颜色附件) RG::TextureHandle m_Depth; - RG::TextureHandle m_ShadowMap; + std::array m_ShadowMaps; }; } // namespace Engine::Passes diff --git a/Engine/include/Engine/Renderer/Passes/SceneGpu.h b/Engine/include/Engine/Renderer/Passes/SceneGpu.h index 48f24d4..8e1cab2 100644 --- a/Engine/include/Engine/Renderer/Passes/SceneGpu.h +++ b/Engine/include/Engine/Renderer/Passes/SceneGpu.h @@ -12,8 +12,10 @@ #define HELLOVULKAN_SCENEGPU_H #include +#include #include #include +#include #include namespace Engine { @@ -39,7 +41,7 @@ namespace Engine { // ---- set 布局(shader 契约,改绑定必须同步 shaders/)---- vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer} vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo} - vk::DescriptorSetLayout shadowSetLayout; // Set2 = {shadow map} + vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图} vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO) vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B @@ -55,7 +57,8 @@ namespace Engine { vk::ShaderEXT shadowVert{}, shadowFrag{}; // RG 重编译后重绑阴影图(物理镜像按槽位各一份) - void BindShadowMap(RG::RenderGraph& rg, RG::TextureHandle shadowMap); + void BindShadowMaps(RG::RenderGraph& rg, + const std::array& maps); private: Device& m_Device; diff --git a/Engine/include/Engine/Renderer/Passes/ShadowPass.h b/Engine/include/Engine/Renderer/Passes/ShadowPass.h index 3bdf60f..57aa0fb 100644 --- a/Engine/include/Engine/Renderer/Passes/ShadowPass.h +++ b/Engine/include/Engine/Renderer/Passes/ShadowPass.h @@ -1,8 +1,9 @@ // // Created by atdunbg on 2026/9/23. // -// ShadowPass:光源视角写深度。GPUDriven batch 消费光源视锥剔除结果 -// (FrameSceneData::shadowView——相机外实体仍投影子)。 +// ShadowPass:CSM 级联阴影。单 pass 渲 kMaxCascades 个 region, +// 每级联独立光源 VP(FrameSceneData::lightSpace[c],SceneRenderer 拟合), +// 消费光源视锥剔除结果(FrameSceneData::shadowView——相机外实体仍投影子)。 // #ifndef HELLOVULKAN_SHADOWPASS_H @@ -11,13 +12,15 @@ #include #include #include +#include namespace Engine::Passes { class ShadowPass final : public RenderPass { public: - ShadowPass(SceneGpu& gpu, FrameSceneData& frame, RG::TextureHandle shadowMap) - : m_Gpu(gpu), m_Frame(frame), m_ShadowMap(shadowMap) {} + ShadowPass(SceneGpu& gpu, FrameSceneData& frame, + const std::array& shadowMaps) + : m_Gpu(gpu), m_Frame(frame), m_ShadowMaps(shadowMaps) {} void Register(RG::RenderGraph& rg) override; void OnTargetsRebuilt(RG::RenderGraph& rg) override; @@ -25,7 +28,7 @@ namespace Engine::Passes { private: SceneGpu& m_Gpu; FrameSceneData& m_Frame; - RG::TextureHandle m_ShadowMap; + std::array m_ShadowMaps; }; } // namespace Engine::Passes diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index ecbcc9c..ed12c3f 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace Engine { @@ -125,7 +126,7 @@ namespace Engine { // ---- RenderGraph 资源句柄 ---- RG::TextureHandle m_Backbuffer{}; RG::TextureHandle m_Depth{}; - RG::TextureHandle m_ShadowMap{}; + std::array m_ShadowMaps{}; // CSM 级联 RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一 RG::TextureHandle m_Velocity{}; // Forward 输出(clip 空间运动向量) RG::TextureHandle m_TAAColor{}; // TAA 累积输出(自引用历史),后处理源 diff --git a/Engine/include/Engine/Renderer/SceneRenderer.h b/Engine/include/Engine/Renderer/SceneRenderer.h index d67775c..6965ff3 100644 --- a/Engine/include/Engine/Renderer/SceneRenderer.h +++ b/Engine/include/Engine/Renderer/SceneRenderer.h @@ -45,9 +45,10 @@ namespace Engine { f32 GetCollectMs() const { return m_Frame.collectMs; } private: - // 阴影光空间矩阵:光源正交投影(覆盖场景,[0,1] 深度重映射)。 - // 写入(Shadow pass push)与采样(Forward UBO)共用同一矩阵。 - void ComputeLightSpace(const glm::vec3& shadowDir); + // CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影 + //(含纹素对齐防阴影边缘闪烁)。写入(Shadow pass push)与采样 + //(Forward UBO)共用同一组矩阵。 + void ComputeCascades(const glm::vec3& shadowDir); FrameSceneData m_Frame; diff --git a/Engine/include/Engine/Renderer/SceneTypes.h b/Engine/include/Engine/Renderer/SceneTypes.h index fb98c37..0dbee91 100644 --- a/Engine/include/Engine/Renderer/SceneTypes.h +++ b/Engine/include/Engine/Renderer/SceneTypes.h @@ -34,7 +34,8 @@ namespace Engine { constexpr u32 kMaxLights = 4; constexpr u32 kMaxInstances = 16384; // 实例 SSBO 容量(矩阵数) - constexpr u32 kShadowMapSize = 2048; // 阴影图边长 + constexpr u32 kMaxCascades = 3; // CSM 级联数 + constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长 // 镜像 shader 侧 UBO 布局的灯光项 struct LightData { @@ -43,15 +44,16 @@ namespace Engine { }; // 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。 - // 字段只能追加在尾部(shadow.vert 共用本 set 前缀,前插会错位)。 + // 字段顺序改动必须同步 simple_shader.vert/frag(shadow.vert 只用 SSBO)。 struct UniformBufferObject { glm::mat4 view; glm::mat4 proj; // 抖动后的投影(光栅化用) - glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵) + glm::mat4 lightSpace[kMaxCascades]; // CSM 各级联光空间 VP([0,1] 重映射) + glm::vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度,正值),w 未用 glm::vec4 ambientColor; LightData lights[kMaxLights]; - // ---- TAA(尾部追加;velocity 在 vertex 里算)---- + // ---- TAA(velocity 在 vertex 里算)---- glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用) glm::mat4 prevView; // 上一帧视图(velocity) glm::mat4 prevProj; // 上一帧未抖动投影(velocity) diff --git a/Engine/src/Renderer/Passes/ForwardPass.cpp b/Engine/src/Renderer/Passes/ForwardPass.cpp index 7550a8b..f82a6e1 100644 --- a/Engine/src/Renderer/Passes/ForwardPass.cpp +++ b/Engine/src/Renderer/Passes/ForwardPass.cpp @@ -146,7 +146,11 @@ namespace Engine::Passes .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) .Write(m_Velocity, RG::ResourceUse::ColorAttachment) .Write(m_Depth, RG::ResourceUse::DepthAttachment) - .Read(m_ShadowMap, RG::ResourceUse::ShaderRead, + .Read(m_ShadowMaps[0], RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[1], RG::ResourceUse::ShaderRead, + vk::ShaderStageFlagBits::eFragment) + .Read(m_ShadowMaps[2], RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment); } diff --git a/Engine/src/Renderer/Passes/SceneGpu.cpp b/Engine/src/Renderer/Passes/SceneGpu.cpp index 20015c2..9c8873e 100644 --- a/Engine/src/Renderer/Passes/SceneGpu.cpp +++ b/Engine/src/Renderer/Passes/SceneGpu.cpp @@ -37,10 +37,13 @@ namespace Engine::Passes }; textureSetLayout = descriptors.CreateLayout(texBindings); - // Set 2: shadow map(fragment 采样) - std::vector shadowBindings = { - {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, - }; + // Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号) + std::vector shadowBindings; + for (u32 c = 0; c < kMaxCascades; ++c) { + shadowBindings.push_back( + {c, vk::DescriptorType::eCombinedImageSampler, + vk::ShaderStageFlagBits::eFragment}); + } shadowSetLayout = descriptors.CreateLayout(shadowBindings); // Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO) @@ -133,13 +136,16 @@ namespace Engine::Passes vkDevice.destroyDescriptorSetLayout(shadowSetLayout); } - void SceneGpu::BindShadowMap(RG::RenderGraph& rg, RG::TextureHandle shadowMap) + void SceneGpu::BindShadowMaps(RG::RenderGraph& rg, + const std::array& maps) { // 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { - m_Descriptors.UpdateTexture( - shadowSets[slot], 0, shadowSampler, - rg.GetPhysicalView(shadowMap, slot)); + for (u32 c = 0; c < kMaxCascades; ++c) { + m_Descriptors.UpdateTexture( + shadowSets[slot], c, shadowSampler, + rg.GetPhysicalView(maps[c], slot)); + } } } diff --git a/Engine/src/Renderer/Passes/ShadowPass.cpp b/Engine/src/Renderer/Passes/ShadowPass.cpp index 7c73f17..e480a13 100644 --- a/Engine/src/Renderer/Passes/ShadowPass.cpp +++ b/Engine/src/Renderer/Passes/ShadowPass.cpp @@ -12,93 +12,101 @@ namespace Engine::Passes { void ShadowPass::Register(RG::RenderGraph& rg) { - // 所有渲染体都写入,不做实体级开关——castShadow 应由材质/实体 - // 数据驱动剔除,而非 pass 逻辑特判 + // 所有渲染体都写入每个级联,不做实体级开关——castShadow 应由 + // 材质/实体数据驱动剔除,而非 pass 逻辑特判。 + // (每级联独立剔除是后续优化点:shadowView 细分为 per-cascade 列表) rg.AddPass("Shadow", [this](RG::RenderGraphContext& ctx) { const vk::CommandBuffer cmd = ctx.cmd; - vk::RenderingAttachmentInfo depthAttachment; - depthAttachment.setImageView(ctx.View(m_ShadowMap)) - .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) - .setLoadOp(vk::AttachmentLoadOp::eClear) - .setStoreOp(vk::AttachmentStoreOp::eStore) - .setClearValue(vk::ClearValue( - vk::ClearDepthStencilValue(1.0f, 0))); + for (u32 c = 0; c < kMaxCascades; ++c) { + vk::RenderingAttachmentInfo depthAttachment; + depthAttachment.setImageView(ctx.View(m_ShadowMaps[c])) + .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(vk::ClearValue( + vk::ClearDepthStencilValue(1.0f, 0))); - vk::RenderingInfo renderingInfo; - renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}}) - .setLayerCount(1) - .setPDepthAttachment(&depthAttachment); + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, {kCascadeRes, kCascadeRes}}) + .setLayerCount(1) + .setPDepthAttachment(&depthAttachment); - cmd.beginRendering(renderingInfo); + cmd.beginRendering(renderingInfo); - std::array stages = { - vk::ShaderStageFlagBits::eVertex, - vk::ShaderStageFlagBits::eFragment - }; - std::array shaders = {m_Gpu.shadowVert, m_Gpu.shadowFrag}; - cmd.bindShadersEXT(stages, shaders); + std::array stages = { + vk::ShaderStageFlagBits::eVertex, + vk::ShaderStageFlagBits::eFragment + }; + std::array shaders = {m_Gpu.shadowVert, m_Gpu.shadowFrag}; + cmd.bindShadersEXT(stages, shaders); - // ---- 动态状态(depth-only)---- - cmd.setRasterizerDiscardEnable(VK_FALSE); + // ---- 动态状态(depth-only)---- + cmd.setRasterizerDiscardEnable(VK_FALSE); - cmd.setDepthTestEnable(VK_TRUE); - cmd.setDepthWriteEnable(VK_TRUE); - cmd.setDepthCompareOp(vk::CompareOp::eLess); - cmd.setDepthBoundsTestEnable(VK_FALSE); + cmd.setDepthTestEnable(VK_TRUE); + cmd.setDepthWriteEnable(VK_TRUE); + cmd.setDepthCompareOp(vk::CompareOp::eLess); + cmd.setDepthBoundsTestEnable(VK_FALSE); - cmd.setStencilTestEnable(VK_FALSE); - // 深度 bias 压平 shadow acne(NDC 量级) - cmd.setDepthBiasEnable(VK_TRUE); - cmd.setDepthBias(0.0025f, 0.0f, 0.003f); - cmd.setPolygonModeEXT(vk::PolygonMode::eFill); - cmd.setCullMode(vk::CullModeFlagBits::eNone); - cmd.setFrontFace(vk::FrontFace::eCounterClockwise); - cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); - cmd.setPrimitiveRestartEnable(VK_FALSE); + cmd.setStencilTestEnable(VK_FALSE); + // 深度 bias 压平 shadow acne(NDC 量级) + cmd.setDepthBiasEnable(VK_TRUE); + cmd.setDepthBias(0.0025f, 0.0f, 0.003f); + cmd.setPolygonModeEXT(vk::PolygonMode::eFill); + cmd.setCullMode(vk::CullModeFlagBits::eNone); + cmd.setFrontFace(vk::FrontFace::eCounterClockwise); + cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); + cmd.setPrimitiveRestartEnable(VK_FALSE); - auto binding = Vertex::GetBindingDescription(); - auto attributes = Vertex::GetAttributeDescriptions(); - cmd.setVertexInputEXT(binding, attributes); + auto binding = Vertex::GetBindingDescription(); + auto attributes = Vertex::GetAttributeDescriptions(); + cmd.setVertexInputEXT(binding, attributes); - cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); - vk::SampleMask sampleMask = 0xFFFFFFFF; - cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); - cmd.setAlphaToCoverageEnableEXT(VK_FALSE); - cmd.setAlphaToOneEnableEXT(VK_FALSE); + cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); + vk::SampleMask sampleMask = 0xFFFFFFFF; + cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); + cmd.setAlphaToCoverageEnableEXT(VK_FALSE); + cmd.setAlphaToOneEnableEXT(VK_FALSE); - vk::Viewport viewport(0.0f, 0.0f, - static_cast(kShadowMapSize), - static_cast(kShadowMapSize), - 0.0f, 1.0f); - cmd.setViewportWithCount(viewport); + vk::Viewport viewport(0.0f, 0.0f, + static_cast(kCascadeRes), + static_cast(kCascadeRes), + 0.0f, 1.0f); + cmd.setViewportWithCount(viewport); - vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize}); - cmd.setScissorWithCount(scissor); + vk::Rect2D scissor({0, 0}, {kCascadeRes, kCascadeRes}); + cmd.setScissorWithCount(scissor); - // ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用 - // 光源视锥剔除后的 batch(相机外实体仍投影子)---- - cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, - m_Gpu.shadowLayout, 0, - m_Gpu.uboSets[ctx.frame.frameSlot], {}); + // ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用 + // 光源视锥剔除后的 batch(相机外实体仍投影子)---- + cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, + m_Gpu.shadowLayout, 0, + m_Gpu.uboSets[ctx.frame.frameSlot], {}); - // stage 必须覆盖 layout range 声明的全部 stage(VUID-01796) - cmd.pushConstants(m_Gpu.shadowLayout, - vk::ShaderStageFlagBits::eVertex | - vk::ShaderStageFlagBits::eFragment, - 0, sizeof(glm::mat4), &m_Frame.ubo.lightSpace); + // stage 必须覆盖 layout range 声明的全部 stage(VUID-01796) + // 每级联 push 各自的光空间 VP + cmd.pushConstants(m_Gpu.shadowLayout, + vk::ShaderStageFlagBits::eVertex | + vk::ShaderStageFlagBits::eFragment, + 0, sizeof(glm::mat4), + &m_Frame.ubo.lightSpace[c]); - for (const auto& batch : m_Frame.shadowView.batches) { - batch.mesh->Draw(cmd, batch.count, batch.first); + for (const auto& batch : m_Frame.shadowView.batches) { + batch.mesh->Draw(cmd, batch.count, batch.first); + } + + cmd.endRendering(); } - - cmd.endRendering(); - }).Write(m_ShadowMap, RG::ResourceUse::DepthAttachment); + }) + .Write(m_ShadowMaps[0], RG::ResourceUse::DepthAttachment) + .Write(m_ShadowMaps[1], RG::ResourceUse::DepthAttachment) + .Write(m_ShadowMaps[2], RG::ResourceUse::DepthAttachment); } void ShadowPass::OnTargetsRebuilt(RG::RenderGraph& rg) { - m_Gpu.BindShadowMap(rg, m_ShadowMap); + m_Gpu.BindShadowMaps(rg, m_ShadowMaps); } } // namespace Engine::Passes diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index a60bf43..40c7f6e 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -183,10 +183,13 @@ namespace Engine m_Depth = m_RenderGraph->CreateTexture( "MainDepth", RG::TextureDesc{depthFormat}); - // 阴影图:固定尺寸正交深度图(D32 采样) - m_ShadowMap = m_RenderGraph->CreateTexture( - "ShadowMap", - RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize}); + // CSM 级联阴影图:每级联独立 1024² 正交深度图(D32 采样), + // 范围由 SceneRenderer::ComputeCascades 按相机视锥切片拟合 + for (u32 c = 0; c < kMaxCascades; ++c) { + m_ShadowMaps[c] = m_RenderGraph->CreateTexture( + "ShadowMap" + std::to_string(c), + RG::TextureDesc{vk::Format::eD32Sfloat, kCascadeRes, kCascadeRes}); + } // HDR 颜色图:Forward 的输出(线性光照值 + 抖动),TAA 显式完成 // 时域累积后写入 TAAColor(对齐 UE/Unity 管线) @@ -222,9 +225,9 @@ namespace Engine // 要加新 pass:new 一个 RenderPass 子类,在这里 push 一行。 FrameSceneData& frame = m_SceneRenderer->Frame(); m_Passes.push_back(CreateScope( - *m_SceneGpu, frame, m_ShadowMap)); + *m_SceneGpu, frame, m_ShadowMaps)); m_Passes.push_back(CreateScope( - *m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_ShadowMap)); + *m_SceneGpu, frame, m_HDRColor, m_Velocity, m_Depth, m_ShadowMaps)); m_Passes.push_back(CreateScope( *m_FullscreenGpu, /*historyAlpha*/ 0.9f, m_HDRColor, m_Velocity, m_TAAColor)); diff --git a/Engine/src/Renderer/SceneRenderer.cpp b/Engine/src/Renderer/SceneRenderer.cpp index 8b3ea57..5c75b70 100644 --- a/Engine/src/Renderer/SceneRenderer.cpp +++ b/Engine/src/Renderer/SceneRenderer.cpp @@ -16,6 +16,7 @@ #include #include +#include namespace Engine { @@ -23,11 +24,11 @@ namespace Engine namespace { - // 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应) - constexpr f32 kLightOrthoExtent = 15.0f; - constexpr f32 kLightDistance = 30.0f; - constexpr f32 kLightNear = 1.0f; - constexpr f32 kLightFar = 80.0f; + // 相机裁剪范围(与 BuildFrame 中 GetProjection 的参数一致) + constexpr f32 kCameraNear = 0.1f; + constexpr f32 kCameraFar = 100.0f; + // 实用分割插值因子(0=均匀,1=对数;0.6 是业界常用值) + constexpr f32 kCascadeLambda = 0.6f; // Halton 低差异序列(TAA 子像素抖动) f32 Halton(u32 index, u32 base) @@ -42,26 +43,80 @@ namespace Engine } } - void SceneRenderer::ComputeLightSpace(const glm::vec3& shadowDir) + void SceneRenderer::ComputeCascades(const glm::vec3& shadowDir) { - // 光源位置在光方向的反方向远处,看向场景中心 - const glm::vec3 lightPos = -shadowDir * kLightDistance; - // up 与视线平行时叉积退化(NaN),切换 up + constexpr u32 N = kMaxCascades; + + // ---- 1. 实用分割:均匀与对数分割按 λ 插值(视图深度,正值)---- + f32 splits[N]; + for (u32 i = 1; i <= N; ++i) { + const f32 t = static_cast(i) / N; + const f32 uniform = kCameraNear + (kCameraFar - kCameraNear) * t; + const f32 log = kCameraNear * glm::pow(kCameraFar / kCameraNear, t); + splits[i - 1] = glm::mix(uniform, log, kCascadeLambda); + } + for (u32 c = 0; c < N; ++c) { + m_Frame.ubo.cascadeSplits[c] = splits[c]; + } + + // ---- 2. 全视锥 8 角反投影到世界(NDC z: 0=近 1=远,Vulkan [0,1])---- + const glm::mat4 invVP = glm::inverse(m_Frame.ubo.projNoJitter * m_Frame.ubo.view); + glm::vec3 nearC[4], farC[4]; + const glm::vec2 ndcXY[4] = {{-1,-1},{1,-1},{-1,1},{1,1}}; + for (u32 i = 0; i < 4; ++i) { + glm::vec4 p = invVP * glm::vec4(ndcXY[i], 0.0f, 1.0f); + nearC[i] = glm::vec3(p / p.w); + p = invVP * glm::vec4(ndcXY[i], 1.0f, 1.0f); + farC[i] = glm::vec3(p / p.w); + } + + // ---- 3. 每级联:切片角点 → 光空间 AABB 拟合 → 纹素对齐 ---- const glm::vec3 up = glm::abs(shadowDir.y) > 0.999f ? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f); - const glm::mat4 lightView = glm::lookAt( - lightPos, glm::vec3(0.0f), up); - glm::mat4 lightProj = glm::ortho( - -kLightOrthoExtent, kLightOrthoExtent, - -kLightOrthoExtent, kLightOrthoExtent, - kLightNear, kLightFar); - // Vulkan clip 深度 [0,1]:z' = 0.5*z + 0.5*w。 - // ortho 的 w_clip = 1,偏移折算进 z 的平移分量(与 perspective 公式不同!) - lightProj[2][2] *= 0.5f; - lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f; + for (u32 c = 0; c < N; ++c) { + const f32 d0 = (c == 0) ? kCameraNear : splits[c - 1]; + const f32 d1 = splits[c]; + const f32 t0 = (d0 - kCameraNear) / (kCameraFar - kCameraNear); + const f32 t1 = (d1 - kCameraNear) / (kCameraFar - kCameraNear); - m_Frame.ubo.lightSpace = lightProj * lightView; + glm::vec3 slice[8]; + for (u32 i = 0; i < 4; ++i) { + slice[i] = glm::mix(nearC[i], farC[i], t0); + slice[4 + i] = glm::mix(nearC[i], farC[i], t1); + } + + // 光视图:位于切片中心,沿光方向看 + glm::vec3 center(0.0f); + for (const auto& p : slice) center += p; + center *= (1.0f / 8.0f); + const glm::mat4 lightView = glm::lookAt(center, center + shadowDir, up); + + glm::vec3 mn( std::numeric_limits::max()); + glm::vec3 mx( std::numeric_limits::lowest()); + for (const auto& p : slice) { + const glm::vec3 lv = glm::vec3(lightView * glm::vec4(p, 1.0f)); + mn = glm::min(mn, lv); + mx = glm::max(mx, lv); + } + + // 纹素对齐:X/Y 边界量化到纹素网格(消除相机移动时的阴影边缘闪烁) + const f32 texelX = (mx.x - mn.x) / static_cast(kCascadeRes); + const f32 texelY = (mx.y - mn.y) / static_cast(kCascadeRes); + mn.x = glm::floor(mn.x / texelX) * texelX; + mn.y = glm::floor(mn.y / texelY) * texelY; + mx.x = mn.x + glm::ceil((mx.x - mn.x) / texelX) * texelX; + mx.y = mn.y + glm::ceil((mx.y - mn.y) / texelY) * texelY; + + // 光视图空间里 z 为负(看向 -Z);近远距离取正 + glm::mat4 lightProj = glm::ortho(mn.x, mx.x, mn.y, mx.y, + -mx.z, -mn.z); + // Vulkan clip 深度 [0,1](ortho 的 w_clip=1,偏移折进 z 平移分量) + lightProj[2][2] *= 0.5f; + lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f; + + m_Frame.ubo.lightSpace[c] = lightProj * lightView; + } } bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets, @@ -121,7 +176,7 @@ namespace Engine ubo.lights[i].direction = glm::vec4(0.0f); ubo.lights[i].color = glm::vec4(0.0f); } - ComputeLightSpace(shadowDir); + ComputeCascades(shadowDir); // ---- 3. GPUDriven:单趟收集 + 双视图剔除 ---- // 每个实例对相机视锥、光源视锥分别做包围球测试: @@ -140,9 +195,11 @@ namespace Engine bool instanceTruncated = false; // 剔除平面提取按 [0,1] 深度约定——相机 proj 与 lightSpace 都已 - // 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用 + // 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用。 + // 光源视锥用最远级联(覆盖全阴影范围) const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view); - const Frustum shadowFrustum = Frustum::FromMatrix(ubo.lightSpace); + const Frustum shadowFrustum = + Frustum::FromMatrix(ubo.lightSpace[kMaxCascades - 1]); scene.Each( [&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) { diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 762f2c8..9d4208c 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -10,7 +10,7 @@ struct Light { layout(location = 0) in vec2 fragUV; layout(location = 1) in vec3 fragNormal; layout(location = 2) in vec3 fragWorldPos; -layout(location = 3) in vec3 fragLightSpacePos; +layout(location = 3) in float fragViewDepth; layout(location = 4) in vec2 fragVelocity; layout(location = 0) out vec4 outColor; @@ -19,26 +19,45 @@ layout(location = 1) out vec2 outVelocity; // TAA velocity 附件 layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 view; mat4 proj; - mat4 lightSpace; + mat4 lightSpace[3]; // CSM 各级联光空间 VP + vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度) vec4 ambientColor; Light lights[MAX_LIGHTS]; + mat4 projNoJitter; + mat4 prevView; + mat4 prevProj; } ubo; layout(set = 1, binding = 0) uniform sampler2D texSampler; -layout(set = 2, binding = 0) uniform sampler2D shadowMap; +layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近) +layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1 +layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远) -// 第 0 盏灯(KeyLight)的阴影因子:0.3 影中 / 1.0 受光。 -// 采样器为 clamp-to-border 白色:光视锥外自动 1.0(无阴影)。 -float ShadowFactor(vec3 N) { - // NDC [-1,1] → UV [0,1](ortho 的 w=1,无需透视除法) - vec2 uv = fragLightSpacePos.xy * 0.5 + 0.5; - float closest = texture(shadowMap, uv).r; +// 第 0 盏灯(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。 +// 级联按视图深度选择;采样器均 clamp-to-border 白色: +// 光视锥外自动 1.0(无阴影),省出界分支。 +float ShadowFactor(vec3 N, float viewDepth) { + // 级联选择:第一个 viewDepth < split 的级联 + int cascade = 2; + for (int i = 0; i < 3; ++i) { + if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; } + } + + vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0); + vec3 proj = ls.xyz / ls.w; + // NDC [-1,1] → UV [0,1](ortho 的 w=1) + vec2 uv = proj.xy * 0.5 + 0.5; + + float closest; + if (cascade == 0) closest = texture(shadowMap0, uv).r; + else if (cascade == 1) closest = texture(shadowMap1, uv).r; + else closest = texture(shadowMap2, uv).r; // 斜率缩放 bias:掠射面用更大的 bias 压 acne vec3 L = normalize(-ubo.lights[0].direction.xyz); float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012); - return (fragLightSpacePos.z - bias > closest) ? 0.3 : 1.0; + return (proj.z - bias > closest) ? 0.3 : 1.0; } void main() { @@ -50,7 +69,7 @@ void main() { for (int i = 0; i < MAX_LIGHTS; ++i) { vec3 L = normalize(-ubo.lights[i].direction.xyz); float diff = max(dot(N, L), 0.0); - float shade = (i == 0) ? ShadowFactor(N) : 1.0; + float shade = (i == 0) ? ShadowFactor(N, fragViewDepth) : 1.0; result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo * shade; } diff --git a/shaders/simple_shader.vert b/shaders/simple_shader.vert index 315697e..ea1dea2 100644 --- a/shaders/simple_shader.vert +++ b/shaders/simple_shader.vert @@ -14,8 +14,8 @@ layout(location = 2) in vec2 inUV; layout(location = 0) out vec2 fragUV; layout(location = 1) out vec3 fragNormal; layout(location = 2) out vec3 fragWorldPos; -layout(location = 3) out vec3 fragLightSpacePos; -layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量 +layout(location = 3) out float fragViewDepth; // CSM:视图深度(级联选择) +layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量 // GPUDriven:模型矩阵从实例缓冲按实例索引取。 // 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N), @@ -31,12 +31,13 @@ layout(std430, set = 0, binding = 2) readonly buffer PrevObjectBuffer { layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 view; - mat4 proj; // 抖动后(光栅化) - mat4 lightSpace; + mat4 proj; + mat4 lightSpace[3]; // CSM 各级联光空间 VP + vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度) vec4 ambientColor; Light lights[MAX_LIGHTS]; - mat4 projNoJitter; // 未抖动(velocity 当前帧) - mat4 prevView; // velocity 上一帧 + mat4 projNoJitter; + mat4 prevView; mat4 prevProj; } ubo; @@ -54,8 +55,8 @@ void main() { fragVelocity = (currClip.xy / currClip.w - prevClip.xy / prevClip.w) * 0.5; fragWorldPos = worldPos.xyz; - // 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵) - fragLightSpacePos = (ubo.lightSpace * worldPos).xyz; + // CSM:视图深度(正值,与 cascadeSplits 同尺度) + fragViewDepth = -(ubo.view * worldPos).z; mat3 normalMatrix = mat3(transpose(inverse(model))); fragNormal = normalize(normalMatrix * inNormal);