From d55f644b682024498ce9d7a4ba7e4ff87ae7060c Mon Sep 17 00:00:00 2001 From: atdunbg Date: Tue, 22 Sep 2026 14:30:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20Shadow=20pass=E2=80=94=E2=80=94?= =?UTF-8?q?=E4=B8=BB=E5=85=89=E6=96=B9=E5=90=91=E5=85=89=E9=98=B4=E5=BD=B1?= =?UTF-8?q?=EF=BC=88RenderGraph=20=E5=8F=8C=20pass=20=E7=AE=A1=E7=BA=BF?= =?UTF-8?q?=E9=A6=96=E4=B8=AA=E9=AA=8C=E8=AF=81=E6=A1=88=E4=BE=8B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RenderGraph 声明 ShadowMap 虚拟资源(D32,2048²固定分辨率,独立于 swapchain 尺寸),Shadow pass 写深度 → Forward 声明 ShaderRead 采样, barrier/layout(DepthAttachment→ShaderReadOnly)全部由图自动预算 - Shadow pass:空 fragment shader + 深度偏移(斜率缩放)抗 shadow acne - Forward:UBO 增加 lightSpace 矩阵;Set 2 采样阴影图,手动对比 + 斜率缩放偏差;clamp-to-border 白色边界(光视锥外 = 全亮) - 光空间矩阵:主光方向反推 30 单位正交盒;深度重映射 [-1,1]→[0,1] - GetPhysicalView(slot):物理镜像按帧槽位隔离,描述符 Compile 后一次性写入 - 修复 RG 隐蔽 bug:ShaderStage 与 PipelineStage2 位值不同 (eFragment=0x10 vs eFragmentShader=0x80),位值直转会撞出 TESSELLATION_CONTROL stage;改为逐位显式映射 - 修复 Camera 投影深度未重映射到 [0,1] 的潜在裁剪问题 validation 零错误零警告(3 passes / 5 barriers / 4 gpu images) --- .../include/Engine/RenderGraph/RenderGraph.h | 4 + Engine/include/Engine/Renderer/Renderer.h | 16 ++ Engine/src/RenderGraph/RenderGraph.cpp | 33 ++- Engine/src/Renderer/Renderer.cpp | 222 +++++++++++++++++- shaders/shadow.frag | 7 + shaders/shadow.vert | 15 ++ shaders/simple_shader.frag | 29 ++- shaders/simple_shader.vert | 6 +- 8 files changed, 316 insertions(+), 16 deletions(-) create mode 100644 shaders/shadow.frag create mode 100644 shaders/shadow.vert diff --git a/Engine/include/Engine/RenderGraph/RenderGraph.h b/Engine/include/Engine/RenderGraph/RenderGraph.h index 51d9ae0..ea06482 100644 --- a/Engine/include/Engine/RenderGraph/RenderGraph.h +++ b/Engine/include/Engine/RenderGraph/RenderGraph.h @@ -220,6 +220,10 @@ namespace Engine::RG { // 每帧执行:按编译好的顺序录命令。要求 cmd 处于 recording 状态。 void Execute(const FrameContext& frame); + // 物理镜像访问器:给"按帧槽位预写描述符"的场景用(如阴影深度图采样)。 + // 仅 created 资源有效;imported 资源(swapchain)每帧才绑定,无物理视图。 + vk::ImageView GetPhysicalView(TextureHandle h, u32 frameSlot) const; + // ---- 统计 ---- u32 GetPassCount() const; diff --git a/Engine/include/Engine/Renderer/Renderer.h b/Engine/include/Engine/Renderer/Renderer.h index 305e29a..afecb0b 100644 --- a/Engine/include/Engine/Renderer/Renderer.h +++ b/Engine/include/Engine/Renderer/Renderer.h @@ -56,6 +56,7 @@ namespace Engine { private: void BuildGraph(); // 声明 pass 结构 + 首次 Compile void HandleResize(); + void UpdateShadowDescriptors(); // Compile 后把阴影深度图物理视图写进 Set 2 Window& m_Window; @@ -78,9 +79,24 @@ namespace Engine { vk::ShaderEXT m_VertShader{}; vk::ShaderEXT m_FragShader{}; + // ---- 阴影 ---- + // Shadow pass 只绑定顶点 shader(深度写入,无颜色输出), + // 独立 pipeline layout:空描述符 + 128B push(lightVP + model) + vk::PipelineLayout m_ShadowPipelineLayout; + vk::ShaderEXT m_ShadowVertShader{}; + vk::ShaderEXT m_ShadowFragShader{}; + + // Set 2:阴影深度图采样。每槽位一组描述符,Compile 后用 + // GetPhysicalView(slot) 一次性写入(物理镜像按槽位隔离,无需每帧更新) + vk::DescriptorSetLayout m_ShadowSetLayout; + vk::Sampler m_ShadowSampler; + std::vector m_ShadowDescriptorSets; // 每帧槽位一个 + glm::mat4 m_LightSpaceMatrix{1.0f}; // 每帧按主光方向重算 + // ---- RenderGraph 资源句柄 ---- RG::TextureHandle m_Backbuffer{}; RG::TextureHandle m_Depth{}; + RG::TextureHandle m_ShadowMap{}; // ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)---- ECS::Scene* m_BoundScene = nullptr; diff --git a/Engine/src/RenderGraph/RenderGraph.cpp b/Engine/src/RenderGraph/RenderGraph.cpp index e0dcb98..dac8177 100644 --- a/Engine/src/RenderGraph/RenderGraph.cpp +++ b/Engine/src/RenderGraph/RenderGraph.cpp @@ -612,6 +612,15 @@ namespace Engine::RG { // ---------- RenderGraphContext ---------- + vk::ImageView RenderGraph::GetPhysicalView(TextureHandle h, u32 frameSlot) const + { + const TextureEntry& entry = GetEntry(h); + if (entry.imported) { + return entry.importedView; // 导入资源回退到当帧绑定视图 + } + return entry.physical[frameSlot]->view; + } + vk::ImageView RenderGraphContext::View(TextureHandle h) const { const auto& entry = graph->GetEntry(h); @@ -679,11 +688,25 @@ namespace Engine::RG { case ResourceUse::DepthAttachment: return vk::PipelineStageFlagBits2::eEarlyFragmentTests | vk::PipelineStageFlagBits2::eLateFragmentTests; - case ResourceUse::ShaderRead: - // ShaderStageFlags 与 PipelineStageFlags2 位值兼容,需显式转换 - return vk::PipelineStageFlags2( - static_cast( - static_cast(use.readStage))); + case ResourceUse::ShaderRead: { + // 关键:ShaderStage 与 PipelineStage2 的位值完全不同! + // ShaderStage::eFragment = 0x10,PipelineStage2::eFragmentShader = 0x80 + // 位值直转会变成完全错误的 stage(0x10 在 Pipeline2 里是 + // TESSELLATION_CONTROL)。必须逐位显式映射。 + const vk::ShaderStageFlags stages = use.readStage; + vk::PipelineStageFlags2 out{}; + auto mapStage = [&](vk::ShaderStageFlagBits from, + vk::PipelineStageFlagBits2 to) { + if (stages & from) out |= to; + }; + mapStage(vk::ShaderStageFlagBits::eVertex, vk::PipelineStageFlagBits2::eVertexShader); + mapStage(vk::ShaderStageFlagBits::eTessellationControl, vk::PipelineStageFlagBits2::eTessellationControlShader); + mapStage(vk::ShaderStageFlagBits::eTessellationEvaluation, vk::PipelineStageFlagBits2::eTessellationEvaluationShader); + mapStage(vk::ShaderStageFlagBits::eGeometry, vk::PipelineStageFlagBits2::eGeometryShader); + mapStage(vk::ShaderStageFlagBits::eFragment, vk::PipelineStageFlagBits2::eFragmentShader); + mapStage(vk::ShaderStageFlagBits::eCompute, vk::PipelineStageFlagBits2::eComputeShader); + return out; + } case ResourceUse::TransferSrc: case ResourceUse::TransferDst: return vk::PipelineStageFlagBits2::eTransfer; diff --git a/Engine/src/Renderer/Renderer.cpp b/Engine/src/Renderer/Renderer.cpp index 373079c..cc7e7ae 100644 --- a/Engine/src/Renderer/Renderer.cpp +++ b/Engine/src/Renderer/Renderer.cpp @@ -40,14 +40,23 @@ namespace Engine struct UniformBufferObject { glm::mat4 view; glm::mat4 proj; + glm::mat4 lightSpace; // 主光(lights[0])光空间变换(阴影用) glm::vec4 ambientColor; Light lights[4]; }; + // Shadow pass 推送块:lightVP + model = 128B(最小保证上限内) + struct ShadowPushConstants { + glm::mat4 lightVP; + glm::mat4 model; + }; + constexpr u32 kUboBinding = 0; constexpr u32 kUBOSetIndex = 0; constexpr u32 kTextureSetIndex = 1; + constexpr u32 kShadowSetIndex = 2; constexpr u32 kMaxLights = 4; + constexpr u32 kShadowMapSize = 2048; // 固定分辨率(独立于 swapchain 尺寸) } Renderer::Renderer(Window& window) @@ -75,9 +84,16 @@ namespace Engine }; m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings); - // 6. PipelineLayout:两个 set + // 5.5 Set 2: 阴影深度图(主 shader 采样对比) + std::vector shadowBindings = { + {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, + }; + m_ShadowSetLayout = m_DescriptorManager->CreateLayout(shadowBindings); + + // 6. PipelineLayout:三个 set(主 shader 64B push;阴影 shader 单独 128B) m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout( - {m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4)); + {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, + sizeof(glm::mat4)); // 7. ShaderManager(VK_EXT_shader_object) m_ShaderManager = CreateScope(*m_Device); @@ -86,19 +102,53 @@ 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}, + sizeof(glm::mat4)); 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}, + sizeof(glm::mat4)); m_VertShader = vert.Handle; m_FragShader = frag.Handle; - if (!m_VertShader || !m_FragShader) { + // 阴影 shader:无描述符,128B push(lightVP + model) + m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout( + std::vector{}, sizeof(ShadowPushConstants)); + auto shadowVert = m_ShaderManager->CreateFromFile( + shaderDir + "shadow.vert.spv", + vk::ShaderStageFlagBits::eVertex, + {}, sizeof(ShadowPushConstants)); + auto shadowFrag = m_ShaderManager->CreateFromFile( + shaderDir + "shadow.frag.spv", + vk::ShaderStageFlagBits::eFragment, + {}, sizeof(ShadowPushConstants)); + + 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 + 白色边界(视锥外 = 全亮), + // 手动对比而非 comparison sampler,逻辑清晰且无 format feature 依赖 + vk::SamplerCreateInfo samplerInfo; + samplerInfo.setMagFilter(vk::Filter::eLinear) + .setMinFilter(vk::Filter::eLinear) + .setMipmapMode(vk::SamplerMipmapMode::eNearest) + .setAddressModeU(vk::SamplerAddressMode::eClampToBorder) + .setAddressModeV(vk::SamplerAddressMode::eClampToBorder) + .setAddressModeW(vk::SamplerAddressMode::eClampToBorder) + .setMipLodBias(0.0f) + .setAnisotropyEnable(VK_FALSE) + .setCompareEnable(VK_FALSE) + .setMinLod(0.0f) + .setMaxLod(0.0f) + .setBorderColor(vk::BorderColor::eFloatOpaqueWhite); + m_ShadowSampler = m_Device->GetDevice().createSampler(samplerInfo); + // 8. Swapchain(只管呈现;深度图由 RenderGraph 创建) m_Swapchain = CreateScope(*m_Device, *m_Allocator, m_Window); m_Extent = m_Swapchain->GetExtent(); @@ -121,6 +171,15 @@ namespace Engine m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize()); } + // 10.5 阴影深度图描述符:每槽位一组。 + // Compile 后由 UpdateShadowDescriptors 写入物理视图(槽位间隔离, + // 物理镜像不重建则无需再更新——轮换重编译时会重新调用)。 + m_ShadowDescriptorSets.resize(kMaxFramesInFlight); + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_ShadowDescriptorSets[slot] = + m_DescriptorManager->AllocateDescriptorSet(m_ShadowSetLayout); + } + // 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定) BuildGraph(); @@ -137,10 +196,34 @@ namespace Engine if (m_FragShader) { m_Device->GetDevice().destroyShaderEXT(m_FragShader); } + if (m_ShadowVertShader) { + m_Device->GetDevice().destroyShaderEXT(m_ShadowVertShader); + } + if (m_ShadowFragShader) { + m_Device->GetDevice().destroyShaderEXT(m_ShadowFragShader); + } m_Device->GetDevice().destroyPipelineLayout(m_PipelineLayout); + m_Device->GetDevice().destroyPipelineLayout(m_ShadowPipelineLayout); m_Device->GetDevice().destroyDescriptorSetLayout(m_UBOSetLayout); m_Device->GetDevice().destroyDescriptorSetLayout(m_TextureSetLayout); + m_Device->GetDevice().destroyDescriptorSetLayout(m_ShadowSetLayout); + m_Device->GetDevice().destroySampler(m_ShadowSampler); + } + + void Renderer::UpdateShadowDescriptors() + { + // 把阴影深度图各槽位的物理视图写进 Set 2。 + // 必须在 Compile 之后调用(物理镜像此时才存在); + // swapchain 轮换重编译会重建物理镜像,因此 HandleResize 也要重新调用。 + for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { + m_DescriptorManager->UpdateTexture( + m_ShadowDescriptorSets[slot], 0, + m_ShadowSampler, + m_RenderGraph->GetPhysicalView(m_ShadowMap, slot)); + } + + ENGINE_INFO("Shadow descriptors updated ({} slots)", kMaxFramesInFlight); } void Renderer::BuildGraph() @@ -162,6 +245,102 @@ namespace Engine m_Depth = m_RenderGraph->CreateTexture( "MainDepth", RG::TextureDesc{depthFormat}); + // 阴影深度图:固定分辨率(独立于 swapchain 尺寸), + // usage 由 uses 自动推导:DepthAttachment | Sampled + m_ShadowMap = m_RenderGraph->CreateTexture( + "ShadowMap", + RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize}); + + // ---- Shadow pass:主光视角写深度 ---- + // 只写深度、无颜色附件;只绑顶点 shader 也可以,这里同样绑了 + // 空 fragment 以最大化驱动兼容性。 + m_RenderGraph->AddPass("Shadow", + [this](RG::RenderGraphContext& ctx) { + const vk::CommandBuffer cmd = ctx.cmd; + + vk::ClearValue clearValue; + clearValue.setDepthStencil(vk::ClearDepthStencilValue(1.0f, 0)); + + vk::RenderingAttachmentInfo depthAttachment; + depthAttachment.setImageView(ctx.View(m_ShadowMap)) + .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) + .setLoadOp(vk::AttachmentLoadOp::eClear) + .setStoreOp(vk::AttachmentStoreOp::eStore) + .setClearValue(clearValue); + + vk::RenderingInfo renderingInfo; + renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}}) + .setLayerCount(1) + .setPDepthAttachment(&depthAttachment); // 无颜色附件 + + cmd.beginRendering(renderingInfo); + + std::array stages = { + vk::ShaderStageFlagBits::eVertex, + vk::ShaderStageFlagBits::eFragment + }; + std::array shaders = { + m_ShadowVertShader, m_ShadowFragShader}; + cmd.bindShadersEXT(stages, shaders); + + // ---- 动态状态(阴影 pass 自带一套,与 Forward 无关)---- + 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); + + // 深度偏移:缓解 shadow acne(斜率缩放) + cmd.setDepthBiasEnable(VK_TRUE); + cmd.setDepthBias(1.25f, 0.0f, 1.75f); + + 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(kShadowMapSize), + static_cast(kShadowMapSize), + 0.0f, 1.0f); + cmd.setViewportWithCount(viewport); + vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize}); + cmd.setScissorWithCount(scissor); + + // ---- 绘制:主光视角下的全部渲染体 ---- + m_BoundScene->Each( + [&](Entity, TransformComponent& transform, + MeshRendererComponent& meshRenderer) { + Model* model = m_BoundAssets->GetModel(meshRenderer.model); + if (!model) return; + + ShadowPushConstants push{m_LightSpaceMatrix, transform.world}; + cmd.pushConstants(m_ShadowPipelineLayout, + vk::ShaderStageFlagBits::eVertex, + 0, sizeof(ShadowPushConstants), &push); + + for (auto& mesh : model->GetMeshes()) { + mesh->Draw(cmd); // 阴影 pass 不需要材质描述符 + } + }); + + cmd.endRendering(); + }) + .Write(m_ShadowMap, RG::ResourceUse::DepthAttachment); + // ---- Forward pass:直接渲染到 swapchain ---- // 场景数据每帧经 m_BoundScene 从 ECS 读取 m_RenderGraph->AddPass("Forward", @@ -203,10 +382,13 @@ namespace Engine std::array 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 +469,10 @@ namespace Engine cmd.endRendering(); }) .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment) - .Write(m_Depth, RG::ResourceUse::DepthAttachment); + .Write(m_Depth, RG::ResourceUse::DepthAttachment) + // 采样阴影深度图:RG 自动在 Shadow(写) → Forward(读) 之间 + // 插入 eDepthAttachmentOptimal → eShaderReadOnlyOptimal 的 barrier + .Read(m_ShadowMap, RG::ResourceUse::ShaderRead); // ---- Present pass:transition 到 ePresentSrcKHR ---- m_RenderGraph->AddPass("Present", @@ -298,6 +483,7 @@ namespace Engine .Read(m_Backbuffer, RG::ResourceUse::Present); m_RenderGraph->SetTargetExtent(m_Extent); // 触发首次 Compile + UpdateShadowDescriptors(); // 物理镜像就位,写 Set 2 } void Renderer::HandleResize() @@ -311,8 +497,9 @@ namespace Engine m_SyncManager = CreateScope( *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); - // RG 重编译:物理镜像按新尺寸轮换 + // RG 重编译:物理镜像按新尺寸轮换(阴影图是固定尺寸,会被重建) m_RenderGraph->SetTargetExtent(m_Extent); + UpdateShadowDescriptors(); } void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets) @@ -359,6 +546,25 @@ namespace Engine ubo.lights[i].color = glm::vec4(0.0f); } + // 1.5 光空间矩阵:主光(lights[0])视角的正交盒包住场景。 + // 光源"位置" = 光方向反向拉远 30 个单位,看向原点。 + // 深度重映射 [-1,1] → [0,1](Vulkan clip volume),与相机投影同理。 + glm::vec3 lightDir(ubo.lights[0].direction); + if (glm::length(lightDir) > 0.0f) { + glm::mat4 lightView = glm::lookAt( + lightDir * 30.0f, // 沿光方向反推的光源位置 + glm::vec3(0.0f), // 看向原点 + glm::vec3(0.0f, 1.0f, 0.0f)); + glm::mat4 lightProj = glm::ortho( + -20.0f, 20.0f, -20.0f, 20.0f, 0.1f, 100.0f); + + lightProj[2][2] = lightProj[2][2] * 0.5f + 0.5f; + lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f; + + m_LightSpaceMatrix = lightProj * lightView; + } + ubo.lightSpace = m_LightSpaceMatrix; + // 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮), // 再写 UBO,写-读竞争关闭。 m_SyncManager->WaitFence(frameSlot); diff --git a/shaders/shadow.frag b/shaders/shadow.frag new file mode 100644 index 0000000..0799aa2 --- /dev/null +++ b/shaders/shadow.frag @@ -0,0 +1,7 @@ +#version 450 + +// Shadow pass 片段着色器:深度-only pass,无任何输出。 +// 注意:不能声明 out 变量——renderingInfo 无颜色附件时写 out 会被 validation 拒绝。 + +void main() { +} diff --git a/shaders/shadow.vert b/shaders/shadow.vert new file mode 100644 index 0000000..bce0de9 --- /dev/null +++ b/shaders/shadow.vert @@ -0,0 +1,15 @@ +#version 450 + +// Shadow pass 顶点着色器:只关心位置,输出光空间深度。 +// push_constant 两个字 mat4 = 128 字节,正好在最小保证上限内。 + +layout(location = 0) in vec3 inPosition; + +layout(push_constant) uniform PushConstants { + mat4 lightVP; // 光空间变换(正交) + mat4 model; // 实体世界矩阵 +} pc; + +void main() { + gl_Position = pc.lightVP * pc.model * vec4(inPosition, 1.0); +} diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 66132b0..d6fd8ba 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -10,18 +10,42 @@ struct Light { layout(location = 0) in vec2 fragUV; layout(location = 1) in vec3 fragNormal; layout(location = 2) in vec3 fragWorldPos; +layout(location = 3) in vec4 fragLightPos; layout(location = 0) out vec4 outColor; layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 view; mat4 proj; + mat4 lightSpace; vec4 ambientColor; Light lights[MAX_LIGHTS]; } ubo; layout(set = 1, binding = 0) uniform sampler2D texSampler; +// 阴影深度图:手动对比(非 comparison sampler,逻辑清晰且免 format feature 依赖) +layout(set = 2, binding = 0) uniform sampler2D shadowMap; + +// 主光(lights[0])阴影因子:出光视锥外/未被遮挡 = 1.0 +float ShadowFactor(vec3 N, vec3 L) +{ + vec3 proj = fragLightPos.xyz / fragLightPos.w; + proj = proj * 0.5 + 0.5; // NDC [-1,1] -> UV [0,1] + + if (proj.x < 0.0 || proj.x > 1.0 || + proj.y < 0.0 || proj.y > 1.0 || + proj.z > 1.0) { + return 1.0; // 光视锥外:不施加阴影(边界由 border color 兜底) + } + + // 斜率缩放偏差,缓解 shadow acne + float bias = max(0.004 * (1.0 - dot(N, L)), 0.0008); + float closest = texture(shadowMap, proj.xy).r; + + return (proj.z - bias > closest) ? 0.25 : 1.0; +} + void main() { vec3 albedo = texture(texSampler, fragUV).rgb; vec3 N = normalize(fragNormal); @@ -31,8 +55,9 @@ 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); - result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo; + float shadow = (i == 0) ? ShadowFactor(N, L) : 1.0; + result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo * shadow; } outColor = vec4(result, 1.0); -} \ No newline at end of file +} diff --git a/shaders/simple_shader.vert b/shaders/simple_shader.vert index 786be0f..0bed954 100644 --- a/shaders/simple_shader.vert +++ b/shaders/simple_shader.vert @@ -14,6 +14,7 @@ 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 vec4 fragLightPos; // 光空间裁剪坐标(阴影采样用) layout(push_constant) uniform PushConstants { mat4 model; @@ -22,6 +23,7 @@ layout(push_constant) uniform PushConstants { layout(set = 0, binding = 0) uniform UniformBufferObject { mat4 view; mat4 proj; + mat4 lightSpace; // 主光(lights[0])的正交光空间变换 vec4 ambientColor; Light lights[MAX_LIGHTS]; } ubo; @@ -36,4 +38,6 @@ void main() { fragNormal = normalize(normalMatrix * inNormal); fragUV = inUV; -} \ No newline at end of file + + fragLightPos = ubo.lightSpace * worldPos; +}