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:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user