From 8c0a11d2e856f6e82221c1ea150b700bd7f5dddc Mon Sep 17 00:00:00 2001 From: atdunbg Date: Wed, 23 Sep 2026 23:18:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20CSM=20=E8=BD=AF=E9=98=B4=E5=BD=B1?= =?UTF-8?q?=E2=80=94=E2=80=94sampler2DShadow=20=E7=A1=AC=E4=BB=B6=20PCF=20?= =?UTF-8?q?+=203=C3=973=20=E7=BA=B9=E7=B4=A0=20tap?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 阴影采样器开启硬件深度比较(compareOp=eLess + linear filter): 单次采样 = 2×2 深度比较插值,clamp-to-border 白色语义不变 - ShadowFactor 改 3×3 纹素偏移 tap(等效 36 次比较),影中保留 0.3 环境感并按 PCF 结果插值——阴影边缘软过渡,消除二值硬边锯齿 --- Engine/src/Renderer/Passes/SceneGpu.cpp | 7 +++-- shaders/simple_shader.frag | 39 +++++++++++++++++-------- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Engine/src/Renderer/Passes/SceneGpu.cpp b/Engine/src/Renderer/Passes/SceneGpu.cpp index 2be4183..7edc676 100644 --- a/Engine/src/Renderer/Passes/SceneGpu.cpp +++ b/Engine/src/Renderer/Passes/SceneGpu.cpp @@ -103,7 +103,9 @@ namespace Engine::Passes ENGINE_FATAL("SceneGpu: failed to create shader objects"); } - // 阴影采样器:clamp-to-border 白色 = 光视锥之外无阴影 + // 阴影采样器:clamp-to-border 白色 = 光视锥外自动无阴影 + //(省 shader 出界分支);开启硬件深度比较(sampler2DShadow) + // ——单次采样即 2×2 PCF,配合 shader 3×3 tap 得软阴影 vk::SamplerCreateInfo shadowSamplerInfo; shadowSamplerInfo.setMagFilter(vk::Filter::eLinear) .setMinFilter(vk::Filter::eLinear) @@ -113,7 +115,8 @@ namespace Engine::Passes .setAddressModeW(vk::SamplerAddressMode::eClampToBorder) .setBorderColor(vk::BorderColor::eFloatOpaqueWhite) .setUnnormalizedCoordinates(VK_FALSE) - .setCompareEnable(VK_FALSE); + .setCompareEnable(VK_TRUE) + .setCompareOp(vk::CompareOp::eLess); shadowSampler = device.GetDevice().createSampler(shadowSamplerInfo); // per-slot 描述符分配 + Set0 绑定(buffer 句柄全程稳定,不用重绑) diff --git a/shaders/simple_shader.frag b/shaders/simple_shader.frag index 043c1ea..61911d6 100644 --- a/shaders/simple_shader.frag +++ b/shaders/simple_shader.frag @@ -2,6 +2,7 @@ const int MAX_LIGHTS = 4; const float PI = 3.14159265359; +const int kCascadeRes = 1024; // CSM 级联图边长(与引擎 SceneTypes.h 同步) struct Light { vec4 color; // rgb = 颜色,a = 强度 @@ -43,16 +44,18 @@ layout(set = 1, binding = 5) uniform MaterialParams { vec4 factors; // x = metallic 因子,y = roughness 因子 } matParams; -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(最远) +// CSM 阴影(sampler2DShadow:单次采样 = 硬件 2×2 深度比较 PCF) +layout(set = 2, binding = 0) uniform sampler2DShadow shadowMap0; // CSM 级联 0(最近) +layout(set = 2, binding = 1) uniform sampler2DShadow shadowMap1; // 级联 1 +layout(set = 2, binding = 2) uniform sampler2DShadow shadowMap2; // 级联 2(最远) layout(set = 2, binding = 3) uniform samplerCube irradianceMap; // 漫反射环境光(IBL 阶段②) layout(set = 2, binding = 4) uniform samplerCube prefilteredMap; // 镜面环境(mip 分 roughness,阶段③) layout(set = 2, binding = 5) uniform sampler2D brdfLut; // BRDF 积分表 (NdotV, roughness) → (scale, bias) -// 第 0 盏方向光(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。 -// 级联按视图深度选择;采样器均 clamp-to-border 白色: -// 光视锥外自动 1.0(无阴影),省出界分支。 +// 第 0 盏方向光(KeyLight)的 CSM 阴影因子。 +// 级联按视图深度选择;3×3 纹素偏移采样(每 tap 硬件 2×2 PCF, +// 等效 36 次比较)→ 软阴影边缘;采样器 clamp-to-border 白色: +// 光视锥外自动 1.0(无阴影),省出界分支 float ShadowFactor(vec3 N, float viewDepth) { int cascade = 2; for (int i = 0; i < 3; ++i) { @@ -63,15 +66,27 @@ float ShadowFactor(vec3 N, float viewDepth) { vec3 proj = ls.xyz / ls.w; 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; - vec3 L = normalize(-ubo.lights[0].direction.xyz); float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012); - return (proj.z - bias > closest) ? 0.3 : 1.0; + float texel = 1.0 / float(kCascadeRes); // 与引擎 kCascadeRes 同步 + float shadow = 0.0; + for (int y = -1; y <= 1; ++y) { + for (int x = -1; x <= 1; ++x) { + float lit; + if (cascade == 0) + lit = texture(shadowMap0, vec3(uv + vec2(x, y) * texel, + proj.z - bias)); + else if (cascade == 1) + lit = texture(shadowMap1, vec3(uv + vec2(x, y) * texel, + proj.z - bias)); + else + lit = texture(shadowMap2, vec3(uv + vec2(x, y) * texel, + proj.z - bias)); + shadow += mix(0.3, 1.0, lit); // 影中保留 0.3 环境感 + } + } + return shadow / 9.0; } // 法线贴图 → 世界空间法线(TBN 构建)。