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
+80 -23
View File
@@ -16,6 +16,7 @@
#include <glm/gtc/matrix_transform.hpp>
#include <chrono>
#include <limits>
namespace Engine
{
@@ -23,11 +24,11 @@ namespace Engine
namespace
{
// 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应)
constexpr f32 kLightOrthoExtent = 15.0f;
constexpr f32 kLightDistance = 30.0f;
constexpr f32 kLightNear = 1.0f;
constexpr f32 kLightFar = 80.0f;
// 相机裁剪范围(与 BuildFrame 中 GetProjection 的参数一致)
constexpr f32 kCameraNear = 0.1f;
constexpr f32 kCameraFar = 100.0f;
// 实用分割插值因子(0=均匀,1=对数;0.6 是业界常用值)
constexpr f32 kCascadeLambda = 0.6f;
// Halton 低差异序列(TAA 子像素抖动)
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)
{
// 光源位置在光方向的反方向远处,看向场景中心
const glm::vec3 lightPos = -shadowDir * kLightDistance;
// up 与视线平行时叉积退化(NaN),切换 up
constexpr u32 N = kMaxCascades;
// ---- 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
? 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;
for (u32 c = 0; c < N; ++c) {
const f32 d0 = (c == 0) ? kCameraNear : splits[c - 1];
const f32 d1 = splits[c];
const f32 t0 = (d0 - kCameraNear) / (kCameraFar - kCameraNear);
const f32 t1 = (d1 - kCameraNear) / (kCameraFar - kCameraNear);
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,
@@ -121,7 +176,7 @@ namespace Engine
ubo.lights[i].direction = glm::vec4(0.0f);
ubo.lights[i].color = glm::vec4(0.0f);
}
ComputeLightSpace(shadowDir);
ComputeCascades(shadowDir);
// ---- 3. GPUDriven:单趟收集 + 双视图剔除 ----
// 每个实例对相机视锥、光源视锥分别做包围球测试:
@@ -140,9 +195,11 @@ namespace Engine
bool instanceTruncated = false;
// 剔除平面提取按 [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 shadowFrustum = Frustum::FromMatrix(ubo.lightSpace);
const Frustum shadowFrustum =
Frustum::FromMatrix(ubo.lightSpace[kMaxCascades - 1]);
scene.Each<TransformComponent, MeshRendererComponent>(
[&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) {