feat: CSM 级联阴影(3 级联 × 1024²,实用分割 + 纹素对齐)

- 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 全活跃)
This commit is contained in:
2026-09-23 13:48:43 +08:00
parent 27b7f8785c
commit 0328d4aaeb
13 changed files with 250 additions and 141 deletions
@@ -19,10 +19,11 @@ namespace Engine::Passes {
public: public:
ForwardPass(SceneGpu& gpu, FrameSceneData& frame, ForwardPass(SceneGpu& gpu, FrameSceneData& frame,
RG::TextureHandle hdrColor, RG::TextureHandle velocity, RG::TextureHandle hdrColor, RG::TextureHandle velocity,
RG::TextureHandle depth, RG::TextureHandle shadowMap) RG::TextureHandle depth,
const std::array<RG::TextureHandle, kMaxCascades>& shadowMaps)
: m_Gpu(gpu), m_Frame(frame), : m_Gpu(gpu), m_Frame(frame),
m_HDRColor(hdrColor), m_Velocity(velocity), m_HDRColor(hdrColor), m_Velocity(velocity),
m_Depth(depth), m_ShadowMap(shadowMap) {} m_Depth(depth), m_ShadowMaps(shadowMaps) {}
void Register(RG::RenderGraph& rg) override; void Register(RG::RenderGraph& rg) override;
@@ -32,7 +33,7 @@ namespace Engine::Passes {
RG::TextureHandle m_HDRColor; RG::TextureHandle m_HDRColor;
RG::TextureHandle m_Velocity; // TAA 运动向量(第二颜色附件) RG::TextureHandle m_Velocity; // TAA 运动向量(第二颜色附件)
RG::TextureHandle m_Depth; RG::TextureHandle m_Depth;
RG::TextureHandle m_ShadowMap; std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps;
}; };
} // namespace Engine::Passes } // namespace Engine::Passes
@@ -12,8 +12,10 @@
#define HELLOVULKAN_SCENEGPU_H #define HELLOVULKAN_SCENEGPU_H
#include <Engine/Core/FrameContext.h> #include <Engine/Core/FrameContext.h>
#include <Engine/Renderer/SceneTypes.h>
#include <Engine/RenderGraph/RenderGraph.h> #include <Engine/RenderGraph/RenderGraph.h>
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
#include <array>
#include <vector> #include <vector>
namespace Engine { namespace Engine {
@@ -39,7 +41,7 @@ namespace Engine {
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)---- // ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer} vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo} 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 forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B
@@ -55,7 +57,8 @@ namespace Engine {
vk::ShaderEXT shadowVert{}, shadowFrag{}; vk::ShaderEXT shadowVert{}, shadowFrag{};
// RG 重编译后重绑阴影图(物理镜像按槽位各一份) // RG 重编译后重绑阴影图(物理镜像按槽位各一份)
void BindShadowMap(RG::RenderGraph& rg, RG::TextureHandle shadowMap); void BindShadowMaps(RG::RenderGraph& rg,
const std::array<RG::TextureHandle, kMaxCascades>& maps);
private: private:
Device& m_Device; Device& m_Device;
@@ -1,8 +1,9 @@
// //
// Created by atdunbg on 2026/9/23. // Created by atdunbg on 2026/9/23.
// //
// ShadowPass:光源视角写深度。GPUDriven batch 消费光源视锥剔除结果 // ShadowPass:CSM 级联阴影。单 pass 渲 kMaxCascades 个 region,
// (FrameSceneData::shadowView——相机外实体仍投影子)。 // 每级联独立光源 VP(FrameSceneData::lightSpace[c],SceneRenderer 拟合),
// 消费光源视锥剔除结果(FrameSceneData::shadowView——相机外实体仍投影子)。
// //
#ifndef HELLOVULKAN_SHADOWPASS_H #ifndef HELLOVULKAN_SHADOWPASS_H
@@ -11,13 +12,15 @@
#include <Engine/Renderer/Passes/RenderPass.h> #include <Engine/Renderer/Passes/RenderPass.h>
#include <Engine/Renderer/Passes/SceneGpu.h> #include <Engine/Renderer/Passes/SceneGpu.h>
#include <Engine/Renderer/SceneTypes.h> #include <Engine/Renderer/SceneTypes.h>
#include <array>
namespace Engine::Passes { namespace Engine::Passes {
class ShadowPass final : public RenderPass { class ShadowPass final : public RenderPass {
public: public:
ShadowPass(SceneGpu& gpu, FrameSceneData& frame, RG::TextureHandle shadowMap) ShadowPass(SceneGpu& gpu, FrameSceneData& frame,
: m_Gpu(gpu), m_Frame(frame), m_ShadowMap(shadowMap) {} const std::array<RG::TextureHandle, kMaxCascades>& shadowMaps)
: m_Gpu(gpu), m_Frame(frame), m_ShadowMaps(shadowMaps) {}
void Register(RG::RenderGraph& rg) override; void Register(RG::RenderGraph& rg) override;
void OnTargetsRebuilt(RG::RenderGraph& rg) override; void OnTargetsRebuilt(RG::RenderGraph& rg) override;
@@ -25,7 +28,7 @@ namespace Engine::Passes {
private: private:
SceneGpu& m_Gpu; SceneGpu& m_Gpu;
FrameSceneData& m_Frame; FrameSceneData& m_Frame;
RG::TextureHandle m_ShadowMap; std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps;
}; };
} // namespace Engine::Passes } // namespace Engine::Passes
+2 -1
View File
@@ -25,6 +25,7 @@
#include <Engine/Renderer/SceneTypes.h> #include <Engine/Renderer/SceneTypes.h>
#include <Engine/RenderGraph/RenderGraph.h> #include <Engine/RenderGraph/RenderGraph.h>
#include <vulkan/vulkan.hpp> #include <vulkan/vulkan.hpp>
#include <array>
#include <vector> #include <vector>
namespace Engine { namespace Engine {
@@ -125,7 +126,7 @@ namespace Engine {
// ---- RenderGraph 资源句柄 ---- // ---- RenderGraph 资源句柄 ----
RG::TextureHandle m_Backbuffer{}; RG::TextureHandle m_Backbuffer{};
RG::TextureHandle m_Depth{}; RG::TextureHandle m_Depth{};
RG::TextureHandle m_ShadowMap{}; std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps{}; // CSM 级联
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一 RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一
RG::TextureHandle m_Velocity{}; // Forward 输出(clip 空间运动向量) RG::TextureHandle m_Velocity{}; // Forward 输出(clip 空间运动向量)
RG::TextureHandle m_TAAColor{}; // TAA 累积输出(自引用历史),后处理源 RG::TextureHandle m_TAAColor{}; // TAA 累积输出(自引用历史),后处理源
@@ -45,9 +45,10 @@ namespace Engine {
f32 GetCollectMs() const { return m_Frame.collectMs; } f32 GetCollectMs() const { return m_Frame.collectMs; }
private: private:
// 阴影光空间矩阵:光源正交投影(覆盖场景,[0,1] 深度重映射)。 // CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影
// 写入(Shadow pass push)与采样(Forward UBO)共用同一矩阵。 //(含纹素对齐防阴影边缘闪烁)。写入(Shadow pass push)与采样
void ComputeLightSpace(const glm::vec3& shadowDir); //(Forward UBO)共用同一组矩阵。
void ComputeCascades(const glm::vec3& shadowDir);
FrameSceneData m_Frame; FrameSceneData m_Frame;
+6 -4
View File
@@ -34,7 +34,8 @@ namespace Engine {
constexpr u32 kMaxLights = 4; constexpr u32 kMaxLights = 4;
constexpr u32 kMaxInstances = 16384; // 实例 SSBO 容量(矩阵数) constexpr u32 kMaxInstances = 16384; // 实例 SSBO 容量(矩阵数)
constexpr u32 kShadowMapSize = 2048; // 阴影图边长 constexpr u32 kMaxCascades = 3; // CSM 级联数
constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长
// 镜像 shader 侧 UBO 布局的灯光项 // 镜像 shader 侧 UBO 布局的灯光项
struct LightData { struct LightData {
@@ -43,15 +44,16 @@ namespace Engine {
}; };
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。 // 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。
// 字段只能追加在尾部(shadow.vert 共用本 set 前缀,前插会错位)。 // 字段顺序改动必须同步 simple_shader.vert/frag(shadow.vert 只用 SSBO)。
struct UniformBufferObject { struct UniformBufferObject {
glm::mat4 view; glm::mat4 view;
glm::mat4 proj; // 抖动后的投影(光栅化用) 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; glm::vec4 ambientColor;
LightData lights[kMaxLights]; LightData lights[kMaxLights];
// ---- TAA(尾部追加;velocity 在 vertex 里算)---- // ---- TAA(velocity 在 vertex 里算)----
glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用) glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用)
glm::mat4 prevView; // 上一帧视图(velocity) glm::mat4 prevView; // 上一帧视图(velocity)
glm::mat4 prevProj; // 上一帧未抖动投影(velocity) glm::mat4 prevProj; // 上一帧未抖动投影(velocity)
+5 -1
View File
@@ -146,7 +146,11 @@ namespace Engine::Passes
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment) .Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
.Write(m_Velocity, RG::ResourceUse::ColorAttachment) .Write(m_Velocity, RG::ResourceUse::ColorAttachment)
.Write(m_Depth, RG::ResourceUse::DepthAttachment) .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); vk::ShaderStageFlagBits::eFragment);
} }
+14 -8
View File
@@ -37,10 +37,13 @@ namespace Engine::Passes
}; };
textureSetLayout = descriptors.CreateLayout(texBindings); textureSetLayout = descriptors.CreateLayout(texBindings);
// Set 2: shadow map(fragment 采样) // Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
std::vector<DescriptorBinding> shadowBindings = { std::vector<DescriptorBinding> shadowBindings;
{0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, for (u32 c = 0; c < kMaxCascades; ++c) {
}; shadowBindings.push_back(
{c, vk::DescriptorType::eCombinedImageSampler,
vk::ShaderStageFlagBits::eFragment});
}
shadowSetLayout = descriptors.CreateLayout(shadowBindings); shadowSetLayout = descriptors.CreateLayout(shadowBindings);
// Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO) // Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO)
@@ -133,13 +136,16 @@ namespace Engine::Passes
vkDevice.destroyDescriptorSetLayout(shadowSetLayout); vkDevice.destroyDescriptorSetLayout(shadowSetLayout);
} }
void SceneGpu::BindShadowMap(RG::RenderGraph& rg, RG::TextureHandle shadowMap) void SceneGpu::BindShadowMaps(RG::RenderGraph& rg,
const std::array<RG::TextureHandle, kMaxCascades>& maps)
{ {
// 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view // 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
m_Descriptors.UpdateTexture( for (u32 c = 0; c < kMaxCascades; ++c) {
shadowSets[slot], 0, shadowSampler, m_Descriptors.UpdateTexture(
rg.GetPhysicalView(shadowMap, slot)); shadowSets[slot], c, shadowSampler,
rg.GetPhysicalView(maps[c], slot));
}
} }
} }
+74 -66
View File
@@ -12,93 +12,101 @@ namespace Engine::Passes
{ {
void ShadowPass::Register(RG::RenderGraph& rg) void ShadowPass::Register(RG::RenderGraph& rg)
{ {
// 所有渲染体都写入,不做实体级开关——castShadow 应由材质/实体 // 所有渲染体都写入每个级联,不做实体级开关——castShadow 应由
// 数据驱动剔除,而非 pass 逻辑特判 // 材质/实体数据驱动剔除,而非 pass 逻辑特判。
// (每级联独立剔除是后续优化点:shadowView 细分为 per-cascade 列表)
rg.AddPass("Shadow", [this](RG::RenderGraphContext& ctx) { rg.AddPass("Shadow", [this](RG::RenderGraphContext& ctx) {
const vk::CommandBuffer cmd = ctx.cmd; const vk::CommandBuffer cmd = ctx.cmd;
vk::RenderingAttachmentInfo depthAttachment; for (u32 c = 0; c < kMaxCascades; ++c) {
depthAttachment.setImageView(ctx.View(m_ShadowMap)) vk::RenderingAttachmentInfo depthAttachment;
.setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) depthAttachment.setImageView(ctx.View(m_ShadowMaps[c]))
.setLoadOp(vk::AttachmentLoadOp::eClear) .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal)
.setStoreOp(vk::AttachmentStoreOp::eStore) .setLoadOp(vk::AttachmentLoadOp::eClear)
.setClearValue(vk::ClearValue( .setStoreOp(vk::AttachmentStoreOp::eStore)
vk::ClearDepthStencilValue(1.0f, 0))); .setClearValue(vk::ClearValue(
vk::ClearDepthStencilValue(1.0f, 0)));
vk::RenderingInfo renderingInfo; vk::RenderingInfo renderingInfo;
renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}}) renderingInfo.setRenderArea({{0, 0}, {kCascadeRes, kCascadeRes}})
.setLayerCount(1) .setLayerCount(1)
.setPDepthAttachment(&depthAttachment); .setPDepthAttachment(&depthAttachment);
cmd.beginRendering(renderingInfo); cmd.beginRendering(renderingInfo);
std::array<vk::ShaderStageFlagBits, 2> stages = { std::array<vk::ShaderStageFlagBits, 2> stages = {
vk::ShaderStageFlagBits::eVertex, vk::ShaderStageFlagBits::eVertex,
vk::ShaderStageFlagBits::eFragment vk::ShaderStageFlagBits::eFragment
}; };
std::array<vk::ShaderEXT, 2> shaders = {m_Gpu.shadowVert, m_Gpu.shadowFrag}; std::array<vk::ShaderEXT, 2> shaders = {m_Gpu.shadowVert, m_Gpu.shadowFrag};
cmd.bindShadersEXT(stages, shaders); cmd.bindShadersEXT(stages, shaders);
// ---- 动态状态(depth-only)---- // ---- 动态状态(depth-only)----
cmd.setRasterizerDiscardEnable(VK_FALSE); cmd.setRasterizerDiscardEnable(VK_FALSE);
cmd.setDepthTestEnable(VK_TRUE); cmd.setDepthTestEnable(VK_TRUE);
cmd.setDepthWriteEnable(VK_TRUE); cmd.setDepthWriteEnable(VK_TRUE);
cmd.setDepthCompareOp(vk::CompareOp::eLess); cmd.setDepthCompareOp(vk::CompareOp::eLess);
cmd.setDepthBoundsTestEnable(VK_FALSE); cmd.setDepthBoundsTestEnable(VK_FALSE);
cmd.setStencilTestEnable(VK_FALSE); cmd.setStencilTestEnable(VK_FALSE);
// 深度 bias 压平 shadow acne(NDC 量级) // 深度 bias 压平 shadow acne(NDC 量级)
cmd.setDepthBiasEnable(VK_TRUE); cmd.setDepthBiasEnable(VK_TRUE);
cmd.setDepthBias(0.0025f, 0.0f, 0.003f); cmd.setDepthBias(0.0025f, 0.0f, 0.003f);
cmd.setPolygonModeEXT(vk::PolygonMode::eFill); cmd.setPolygonModeEXT(vk::PolygonMode::eFill);
cmd.setCullMode(vk::CullModeFlagBits::eNone); cmd.setCullMode(vk::CullModeFlagBits::eNone);
cmd.setFrontFace(vk::FrontFace::eCounterClockwise); cmd.setFrontFace(vk::FrontFace::eCounterClockwise);
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
cmd.setPrimitiveRestartEnable(VK_FALSE); cmd.setPrimitiveRestartEnable(VK_FALSE);
auto binding = Vertex::GetBindingDescription(); auto binding = Vertex::GetBindingDescription();
auto attributes = Vertex::GetAttributeDescriptions(); auto attributes = Vertex::GetAttributeDescriptions();
cmd.setVertexInputEXT(binding, attributes); cmd.setVertexInputEXT(binding, attributes);
cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1);
vk::SampleMask sampleMask = 0xFFFFFFFF; vk::SampleMask sampleMask = 0xFFFFFFFF;
cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask);
cmd.setAlphaToCoverageEnableEXT(VK_FALSE); cmd.setAlphaToCoverageEnableEXT(VK_FALSE);
cmd.setAlphaToOneEnableEXT(VK_FALSE); cmd.setAlphaToOneEnableEXT(VK_FALSE);
vk::Viewport viewport(0.0f, 0.0f, vk::Viewport viewport(0.0f, 0.0f,
static_cast<float>(kShadowMapSize), static_cast<float>(kCascadeRes),
static_cast<float>(kShadowMapSize), static_cast<float>(kCascadeRes),
0.0f, 1.0f); 0.0f, 1.0f);
cmd.setViewportWithCount(viewport); cmd.setViewportWithCount(viewport);
vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize}); vk::Rect2D scissor({0, 0}, {kCascadeRes, kCascadeRes});
cmd.setScissorWithCount(scissor); cmd.setScissorWithCount(scissor);
// ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用 // ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用
// 光源视锥剔除后的 batch(相机外实体仍投影子)---- // 光源视锥剔除后的 batch(相机外实体仍投影子)----
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
m_Gpu.shadowLayout, 0, m_Gpu.shadowLayout, 0,
m_Gpu.uboSets[ctx.frame.frameSlot], {}); m_Gpu.uboSets[ctx.frame.frameSlot], {});
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796) // stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
cmd.pushConstants(m_Gpu.shadowLayout, // 每级联 push 各自的光空间 VP
vk::ShaderStageFlagBits::eVertex | cmd.pushConstants(m_Gpu.shadowLayout,
vk::ShaderStageFlagBits::eFragment, vk::ShaderStageFlagBits::eVertex |
0, sizeof(glm::mat4), &m_Frame.ubo.lightSpace); vk::ShaderStageFlagBits::eFragment,
0, sizeof(glm::mat4),
&m_Frame.ubo.lightSpace[c]);
for (const auto& batch : m_Frame.shadowView.batches) { for (const auto& batch : m_Frame.shadowView.batches) {
batch.mesh->Draw(cmd, batch.count, batch.first); batch.mesh->Draw(cmd, batch.count, batch.first);
}
cmd.endRendering();
} }
})
cmd.endRendering(); .Write(m_ShadowMaps[0], RG::ResourceUse::DepthAttachment)
}).Write(m_ShadowMap, RG::ResourceUse::DepthAttachment); .Write(m_ShadowMaps[1], RG::ResourceUse::DepthAttachment)
.Write(m_ShadowMaps[2], RG::ResourceUse::DepthAttachment);
} }
void ShadowPass::OnTargetsRebuilt(RG::RenderGraph& rg) void ShadowPass::OnTargetsRebuilt(RG::RenderGraph& rg)
{ {
m_Gpu.BindShadowMap(rg, m_ShadowMap); m_Gpu.BindShadowMaps(rg, m_ShadowMaps);
} }
} // namespace Engine::Passes } // namespace Engine::Passes
+9 -6
View File
@@ -183,10 +183,13 @@ namespace Engine
m_Depth = m_RenderGraph->CreateTexture( m_Depth = m_RenderGraph->CreateTexture(
"MainDepth", RG::TextureDesc{depthFormat}); "MainDepth", RG::TextureDesc{depthFormat});
// 阴影图:固定尺寸正交深度图(D32 采样) // CSM 级联阴影图:每级联独立 1024² 正交深度图(D32 采样),
m_ShadowMap = m_RenderGraph->CreateTexture( // 范围由 SceneRenderer::ComputeCascades 按相机视锥切片拟合
"ShadowMap", for (u32 c = 0; c < kMaxCascades; ++c) {
RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize}); m_ShadowMaps[c] = m_RenderGraph->CreateTexture(
"ShadowMap" + std::to_string(c),
RG::TextureDesc{vk::Format::eD32Sfloat, kCascadeRes, kCascadeRes});
}
// HDR 颜色图:Forward 的输出(线性光照值 + 抖动),TAA 显式完成 // HDR 颜色图:Forward 的输出(线性光照值 + 抖动),TAA 显式完成
// 时域累积后写入 TAAColor(对齐 UE/Unity 管线) // 时域累积后写入 TAAColor(对齐 UE/Unity 管线)
@@ -222,9 +225,9 @@ namespace Engine
// 要加新 pass:new 一个 RenderPass 子类,在这里 push 一行。 // 要加新 pass:new 一个 RenderPass 子类,在这里 push 一行。
FrameSceneData& frame = m_SceneRenderer->Frame(); FrameSceneData& frame = m_SceneRenderer->Frame();
m_Passes.push_back(CreateScope<Passes::ShadowPass>( m_Passes.push_back(CreateScope<Passes::ShadowPass>(
*m_SceneGpu, frame, m_ShadowMap)); *m_SceneGpu, frame, m_ShadowMaps));
m_Passes.push_back(CreateScope<Passes::ForwardPass>( m_Passes.push_back(CreateScope<Passes::ForwardPass>(
*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<Passes::TAAPass>( m_Passes.push_back(CreateScope<Passes::TAAPass>(
*m_FullscreenGpu, /*historyAlpha*/ 0.9f, m_HDRColor, m_Velocity, *m_FullscreenGpu, /*historyAlpha*/ 0.9f, m_HDRColor, m_Velocity,
m_TAAColor)); m_TAAColor));
+80 -23
View File
@@ -16,6 +16,7 @@
#include <glm/gtc/matrix_transform.hpp> #include <glm/gtc/matrix_transform.hpp>
#include <chrono> #include <chrono>
#include <limits>
namespace Engine namespace Engine
{ {
@@ -23,11 +24,11 @@ namespace Engine
namespace namespace
{ {
// 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应) // 相机裁剪范围(与 BuildFrame 中 GetProjection 的参数一致)
constexpr f32 kLightOrthoExtent = 15.0f; constexpr f32 kCameraNear = 0.1f;
constexpr f32 kLightDistance = 30.0f; constexpr f32 kCameraFar = 100.0f;
constexpr f32 kLightNear = 1.0f; // 实用分割插值因子(0=均匀,1=对数;0.6 是业界常用值)
constexpr f32 kLightFar = 80.0f; constexpr f32 kCascadeLambda = 0.6f;
// Halton 低差异序列(TAA 子像素抖动) // Halton 低差异序列(TAA 子像素抖动)
f32 Halton(u32 index, u32 base) 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)
{ {
// 光源位置在光方向的反方向远处,看向场景中心 constexpr u32 N = kMaxCascades;
const glm::vec3 lightPos = -shadowDir * kLightDistance;
// up 与视线平行时叉积退化(NaN),切换 up // ---- 1. 实用分割:均匀与对数分割按 λ 插值(视图深度,正值)----
f32 splits[N];
for (u32 i = 1; i <= N; ++i) {
const f32 t = static_cast<f32>(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 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); ? 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( for (u32 c = 0; c < N; ++c) {
-kLightOrthoExtent, kLightOrthoExtent, const f32 d0 = (c == 0) ? kCameraNear : splits[c - 1];
-kLightOrthoExtent, kLightOrthoExtent, const f32 d1 = splits[c];
kLightNear, kLightFar); const f32 t0 = (d0 - kCameraNear) / (kCameraFar - kCameraNear);
// Vulkan clip 深度 [0,1]:z' = 0.5*z + 0.5*w。 const f32 t1 = (d1 - kCameraNear) / (kCameraFar - kCameraNear);
// ortho 的 w_clip = 1,偏移折算进 z 的平移分量(与 perspective 公式不同!)
lightProj[2][2] *= 0.5f;
lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f;
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<f32>::max());
glm::vec3 mx( std::numeric_limits<f32>::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<f32>(kCascadeRes);
const f32 texelY = (mx.y - mn.y) / static_cast<f32>(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, 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].direction = glm::vec4(0.0f);
ubo.lights[i].color = glm::vec4(0.0f); ubo.lights[i].color = glm::vec4(0.0f);
} }
ComputeLightSpace(shadowDir); ComputeCascades(shadowDir);
// ---- 3. GPUDriven:单趟收集 + 双视图剔除 ---- // ---- 3. GPUDriven:单趟收集 + 双视图剔除 ----
// 每个实例对相机视锥、光源视锥分别做包围球测试: // 每个实例对相机视锥、光源视锥分别做包围球测试:
@@ -140,9 +195,11 @@ namespace Engine
bool instanceTruncated = false; bool instanceTruncated = false;
// 剔除平面提取按 [0,1] 深度约定——相机 proj 与 lightSpace 都已 // 剔除平面提取按 [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 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<TransformComponent, MeshRendererComponent>( scene.Each<TransformComponent, MeshRendererComponent>(
[&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) { [&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) {
+30 -11
View File
@@ -10,7 +10,7 @@ struct Light {
layout(location = 0) in vec2 fragUV; layout(location = 0) in vec2 fragUV;
layout(location = 1) in vec3 fragNormal; layout(location = 1) in vec3 fragNormal;
layout(location = 2) in vec3 fragWorldPos; 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 = 4) in vec2 fragVelocity;
layout(location = 0) out vec4 outColor; 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 { layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 view; mat4 view;
mat4 proj; mat4 proj;
mat4 lightSpace; mat4 lightSpace[3]; // CSM 各级联光空间 VP
vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度)
vec4 ambientColor; vec4 ambientColor;
Light lights[MAX_LIGHTS]; Light lights[MAX_LIGHTS];
mat4 projNoJitter;
mat4 prevView;
mat4 prevProj;
} ubo; } ubo;
layout(set = 1, binding = 0) uniform sampler2D texSampler; 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 受光。 // 第 0 盏灯(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
// 采样器为 clamp-to-border 白色:光视锥外自动 1.0(无阴影)。 // 级联按视图深度选择;采样器均 clamp-to-border 白色:
float ShadowFactor(vec3 N) { // 光视锥外自动 1.0(无阴影),省出界分支。
// NDC [-1,1] → UV [0,1](ortho 的 w=1,无需透视除法) float ShadowFactor(vec3 N, float viewDepth) {
vec2 uv = fragLightSpacePos.xy * 0.5 + 0.5; // 级联选择:第一个 viewDepth < split 的级联
float closest = texture(shadowMap, uv).r; 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 // 斜率缩放 bias:掠射面用更大的 bias 压 acne
vec3 L = normalize(-ubo.lights[0].direction.xyz); vec3 L = normalize(-ubo.lights[0].direction.xyz);
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012); 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() { void main() {
@@ -50,7 +69,7 @@ void main() {
for (int i = 0; i < MAX_LIGHTS; ++i) { for (int i = 0; i < MAX_LIGHTS; ++i) {
vec3 L = normalize(-ubo.lights[i].direction.xyz); vec3 L = normalize(-ubo.lights[i].direction.xyz);
float diff = max(dot(N, L), 0.0); 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; result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo * shade;
} }
+9 -8
View File
@@ -14,8 +14,8 @@ layout(location = 2) in vec2 inUV;
layout(location = 0) out vec2 fragUV; layout(location = 0) out vec2 fragUV;
layout(location = 1) out vec3 fragNormal; layout(location = 1) out vec3 fragNormal;
layout(location = 2) out vec3 fragWorldPos; layout(location = 2) out vec3 fragWorldPos;
layout(location = 3) out vec3 fragLightSpacePos; layout(location = 3) out float fragViewDepth; // CSM:视图深度(级联选择)
layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量 layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量
// GPUDriven:模型矩阵从实例缓冲按实例索引取。 // GPUDriven:模型矩阵从实例缓冲按实例索引取。
// 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N), // 同一 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 { layout(set = 0, binding = 0) uniform UniformBufferObject {
mat4 view; mat4 view;
mat4 proj; // 抖动后(光栅化) mat4 proj;
mat4 lightSpace; mat4 lightSpace[3]; // CSM 各级联光空间 VP
vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度)
vec4 ambientColor; vec4 ambientColor;
Light lights[MAX_LIGHTS]; Light lights[MAX_LIGHTS];
mat4 projNoJitter; // 未抖动(velocity 当前帧) mat4 projNoJitter;
mat4 prevView; // velocity 上一帧 mat4 prevView;
mat4 prevProj; mat4 prevProj;
} ubo; } ubo;
@@ -54,8 +55,8 @@ void main() {
fragVelocity = (currClip.xy / currClip.w - prevClip.xy / prevClip.w) * 0.5; fragVelocity = (currClip.xy / currClip.w - prevClip.xy / prevClip.w) * 0.5;
fragWorldPos = worldPos.xyz; fragWorldPos = worldPos.xyz;
// 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵) // CSM:视图深度(正值,与 cascadeSplits 同尺度)
fragLightSpacePos = (ubo.lightSpace * worldPos).xyz; fragViewDepth = -(ubo.view * worldPos).z;
mat3 normalMatrix = mat3(transpose(inverse(model))); mat3 normalMatrix = mat3(transpose(inverse(model)));
fragNormal = normalize(normalMatrix * inNormal); fragNormal = normalize(normalMatrix * inNormal);