feat: Shadow pass 接入 RenderGraph,支持 SRT 变换与程序化平面

阴影(本修正的核心根因):
- Shadow pass 光源正交写深度(2048 D32),Forward 采样比较;
  写入与采样共用同一 lightSpace 矩阵,杜绝姿态/镜像不一致
- 修复:push constant range 64B→128B(原 Shadow pass 推 {lightVP,model}
  超出 layout 声明范围 = UB,阴影中模型矩阵读到垃圾导致旋转错乱)
- 修复:ShadowFactor 缺少 NDC→UV 映射(*0.5+0.5),采样区域整体错位
- 修复:RG ShaderRead stage 位值直转(eFragment=0x10 在 PipelineStage2
  是 TESSELLATION_CONTROL),改为逐位显式映射
- Camera perspective 深度重映射到 Vulkan [0,1](公式与 ortho 不同:
  M22=M22*0.5-0.5, M32*=0.5);光源 ortho remap 用 M32=M32*0.5+0.5

引擎配套:
- TransformComponent 重构为 position/rotation/scale + Compose()
- Texture::CreateSolidColor / Model::CreatePlane 程序化资产
  (两段式上传公共路径),AssetManager.CreatePlane 带缓存入口
- RG 新增 GetPhysicalView(h, slot):按帧槽位把 RG 资源绑进外部描述符
  (阴影图 per-slot 物理镜像各自绑定)

验证:validation 0 error 0 warning,阴影随实体旋转正确
This commit is contained in:
2026-09-22 16:40:54 +08:00
parent 394bfd3d0c
commit 07a3b87001
17 changed files with 494 additions and 55 deletions
+225 -16
View File
@@ -40,6 +40,7 @@ namespace Engine
struct UniformBufferObject {
glm::mat4 view;
glm::mat4 proj;
glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵)
glm::vec4 ambientColor;
Light lights[4];
};
@@ -47,7 +48,18 @@ namespace Engine
constexpr u32 kUboBinding = 0;
constexpr u32 kUBOSetIndex = 0;
constexpr u32 kTextureSetIndex = 1;
constexpr u32 kShadowSetIndex = 2;
constexpr u32 kMaxLights = 4;
// Shadow pass:push constant = { lightVP, model }
constexpr u32 kShadowMapSize = 2048;
constexpr u32 kShadowPushSize = static_cast<u32>(2 * sizeof(glm::mat4));
// 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应)
constexpr f32 kLightOrthoExtent = 15.0f;
constexpr f32 kLightDistance = 30.0f;
constexpr f32 kLightNear = 1.0f;
constexpr f32 kLightFar = 80.0f;
}
Renderer::Renderer(Window& window)
@@ -75,9 +87,20 @@ namespace Engine
};
m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings);
// 6. PipelineLayout:两个 set
// 5.5 Set 2: shadow map(fragment 采样)
std::vector<DescriptorBinding> shadowBindings = {
{0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment},
};
m_ShadowSetLayout = m_DescriptorManager->CreateLayout(shadowBindings);
// 6. PipelineLayout:三个 set;push constant 上限 128B
//(Shadow pass 的 {lightVP, model} 用满;forward shader block 只用前 64B)
m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout(
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
// Shadow pass 独立 layout:无 set,128B push constant
m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout(
std::vector<vk::DescriptorSetLayout>{}, kShadowPushSize);
// 7. ShaderManager(VK_EXT_shader_object)
m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
@@ -86,19 +109,43 @@ namespace Engine
auto vert = m_ShaderManager->CreateFromFile(
shaderDir + "simple_shader.vert.spv",
vk::ShaderStageFlagBits::eVertex,
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
auto frag = m_ShaderManager->CreateFromFile(
shaderDir + "simple_shader.frag.spv",
vk::ShaderStageFlagBits::eFragment,
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
{m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, kShadowPushSize);
m_VertShader = vert.Handle;
m_FragShader = frag.Handle;
if (!m_VertShader || !m_FragShader) {
// shadow shader:depth-only,无 set
auto shadowVert = m_ShaderManager->CreateFromFile(
shaderDir + "shadow.vert.spv",
vk::ShaderStageFlagBits::eVertex, {}, kShadowPushSize);
auto shadowFrag = m_ShaderManager->CreateFromFile(
shaderDir + "shadow.frag.spv",
vk::ShaderStageFlagBits::eFragment, {}, kShadowPushSize);
m_ShadowVertShader = shadowVert.Handle;
m_ShadowFragShader = shadowFrag.Handle;
if (!m_VertShader || !m_FragShader || !m_ShadowVertShader || !m_ShadowFragShader) {
ENGINE_FATAL("Failed to create shader objects");
}
// 阴影采样器:clamp-to-border 白色 = 光视锥之外无阴影(省 shader 出界分支)
vk::SamplerCreateInfo shadowSamplerInfo;
shadowSamplerInfo.setMagFilter(vk::Filter::eLinear)
.setMinFilter(vk::Filter::eLinear)
.setMipmapMode(vk::SamplerMipmapMode::eNearest)
.setAddressModeU(vk::SamplerAddressMode::eClampToBorder)
.setAddressModeV(vk::SamplerAddressMode::eClampToBorder)
.setAddressModeW(vk::SamplerAddressMode::eClampToBorder)
.setBorderColor(vk::BorderColor::eFloatOpaqueWhite)
.setUnnormalizedCoordinates(VK_FALSE)
.setCompareEnable(VK_FALSE);
m_ShadowSampler = m_Device->GetDevice().createSampler(shadowSamplerInfo);
// 8. Swapchain(只管呈现;深度图由 RenderGraph 创建)
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, m_Window);
m_Extent = m_Swapchain->GetExtent();
@@ -113,17 +160,24 @@ namespace Engine
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
m_UBODescriptorSets.resize(kMaxFramesInFlight);
m_ShadowDescriptorSets.resize(kMaxFramesInFlight);
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
m_UBODescriptorSets[slot] =
m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout);
m_DescriptorManager->UpdateUniformBuffer(
m_UBODescriptorSets[slot], kUboBinding,
m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize());
m_ShadowDescriptorSets[slot] =
m_DescriptorManager->AllocateDescriptorSet(m_ShadowSetLayout);
}
// 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定)
BuildGraph();
// Compile 已分配物理镜像:把各槽位的阴影图 view 绑进描述符
UpdateShadowDescriptors();
ENGINE_INFO("Renderer initialized");
}
@@ -131,16 +185,27 @@ namespace Engine
{
m_Device->GetDevice().waitIdle();
const auto& vkDevice = m_Device->GetDevice();
if (m_VertShader) {
m_Device->GetDevice().destroyShaderEXT(m_VertShader);
vkDevice.destroyShaderEXT(m_VertShader);
}
if (m_FragShader) {
m_Device->GetDevice().destroyShaderEXT(m_FragShader);
vkDevice.destroyShaderEXT(m_FragShader);
}
if (m_ShadowVertShader) {
vkDevice.destroyShaderEXT(m_ShadowVertShader);
}
if (m_ShadowFragShader) {
vkDevice.destroyShaderEXT(m_ShadowFragShader);
}
m_Device->GetDevice().destroyPipelineLayout(m_PipelineLayout);
m_Device->GetDevice().destroyDescriptorSetLayout(m_UBOSetLayout);
m_Device->GetDevice().destroyDescriptorSetLayout(m_TextureSetLayout);
vkDevice.destroySampler(m_ShadowSampler);
vkDevice.destroyPipelineLayout(m_PipelineLayout);
vkDevice.destroyPipelineLayout(m_ShadowPipelineLayout);
vkDevice.destroyDescriptorSetLayout(m_UBOSetLayout);
vkDevice.destroyDescriptorSetLayout(m_TextureSetLayout);
vkDevice.destroyDescriptorSetLayout(m_ShadowSetLayout);
}
void Renderer::BuildGraph()
@@ -162,6 +227,102 @@ 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});
// ---- Shadow pass:光源视角写深度(所有渲染体都写入,不做实体级开关——
// castShadow 应由材质/实体数据驱动剔除,而非 pass 逻辑特判)----
m_RenderGraph->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)));
vk::RenderingInfo renderingInfo;
renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}})
.setLayerCount(1)
.setPDepthAttachment(&depthAttachment);
cmd.beginRendering(renderingInfo);
std::array<vk::ShaderStageFlagBits, 2> stages = {
vk::ShaderStageFlagBits::eVertex,
vk::ShaderStageFlagBits::eFragment
};
std::array<vk::ShaderEXT, 2> shaders = {m_ShadowVertShader, m_ShadowFragShader};
cmd.bindShadersEXT(stages, shaders);
// ---- 动态状态(depth-only)----
cmd.setRasterizerDiscardEnable(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);
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);
vk::Viewport viewport(0.0f, 0.0f,
static_cast<float>(kShadowMapSize),
static_cast<float>(kShadowMapSize),
0.0f, 1.0f);
cmd.setViewportWithCount(viewport);
vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize});
cmd.setScissorWithCount(scissor);
// ---- 绘制:push {lightVP, model},与 Forward 用同一份 world 矩阵 ----
struct ShadowPush {
glm::mat4 lightVP;
glm::mat4 model;
};
m_BoundScene->Each<TransformComponent, MeshRendererComponent>(
[&](Entity, TransformComponent& transform,
MeshRendererComponent& meshRenderer) {
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
if (!model) return;
const ShadowPush push{m_LightSpace, transform.world};
cmd.pushConstants(m_ShadowPipelineLayout,
vk::ShaderStageFlagBits::eVertex,
0, kShadowPushSize, &push);
for (auto& mesh : model->GetMeshes()) {
mesh->Draw(cmd);
}
});
cmd.endRendering();
})
.Write(m_ShadowMap, RG::ResourceUse::DepthAttachment);
// ---- Forward pass:直接渲染到 swapchain ----
// 场景数据每帧经 m_BoundScene 从 ECS 读取
m_RenderGraph->AddPass("Forward",
@@ -203,10 +364,13 @@ namespace Engine
std::array<vk::ShaderEXT, 2> shaders = {m_VertShader, m_FragShader};
cmd.bindShadersEXT(stages, shaders);
// Set 0:本帧槽位的 UBO
// Set 0:本帧槽位的 UBO;Set 2:本帧槽位的阴影图
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
m_PipelineLayout, kUBOSetIndex,
m_UBODescriptorSets[ctx.frame.frameSlot], {});
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
m_PipelineLayout, kShadowSetIndex,
m_ShadowDescriptorSets[ctx.frame.frameSlot], {});
// ---- 动态状态 ----
cmd.setRasterizerDiscardEnable(VK_FALSE);
@@ -287,7 +451,9 @@ namespace Engine
cmd.endRendering();
})
.Write(m_Backbuffer, RG::ResourceUse::ColorAttachment)
.Write(m_Depth, RG::ResourceUse::DepthAttachment);
.Write(m_Depth, RG::ResourceUse::DepthAttachment)
.Read(m_ShadowMap, RG::ResourceUse::ShaderRead,
vk::ShaderStageFlagBits::eFragment);
// ---- Present pass:transition 到 ePresentSrcKHR ----
m_RenderGraph->AddPass("Present",
@@ -313,6 +479,20 @@ namespace Engine
// RG 重编译:物理镜像按新尺寸轮换
m_RenderGraph->SetTargetExtent(m_Extent);
// 重编译后物理镜像可能变化,阴影描述符重新绑定
UpdateShadowDescriptors();
}
void Renderer::UpdateShadowDescriptors()
{
// 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
m_DescriptorManager->UpdateTexture(
m_ShadowDescriptorSets[slot], 0,
m_ShadowSampler,
m_RenderGraph->GetPhysicalView(m_ShadowMap, slot));
}
}
void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets)
@@ -345,13 +525,18 @@ namespace Engine
ubo.ambientColor = scene.GetSettings().ambientColor;
u32 lightCount = 0;
glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射
scene.Each<LightComponent>([&](Entity, LightComponent& light) {
if (lightCount >= kMaxLights) return;
ubo.lights[lightCount].direction =
glm::length(light.direction) > 0.0f
? glm::vec4(glm::normalize(light.direction), 0.0f)
: glm::vec4(0.0f);
glm::vec3 dir = glm::length(light.direction) > 0.0f
? glm::normalize(light.direction)
: glm::vec3(0.0f);
ubo.lights[lightCount].direction = glm::vec4(dir, 0.0f);
ubo.lights[lightCount].color = light.color;
// 第一个灯作为阴影光源
if (lightCount == 0) shadowDir = dir;
++lightCount;
});
for (u32 i = lightCount; i < kMaxLights; ++i) {
@@ -359,6 +544,30 @@ namespace Engine
ubo.lights[i].color = glm::vec4(0.0f);
}
// ---- 阴影光空间矩阵:写入(Shadow pass)与采样(Forward UBO)共用,
// 从根上排除任何"旋转/镜像不一致"的可能 ----
// 光源位置在光方向的反方向远处,看向场景中心。
{
const glm::vec3 lightPos = -shadowDir * kLightDistance;
// up 与视线平行时叉积退化(NaN),切换 up
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;
m_LightSpace = lightProj * lightView;
ubo.lightSpace = m_LightSpace;
}
// 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮),
// 再写 UBO,写-读竞争关闭。
m_SyncManager->WaitFence(frameSlot);