ADD: CSM with base shadow map

This commit is contained in:
2025-10-21 21:24:35 +09:00
parent 3b7f0869a2
commit fde6a18f80
6 changed files with 154 additions and 47 deletions

View File

@@ -104,15 +104,16 @@ void SceneManager::update_scene()
sceneData.proj = projection;
sceneData.viewproj = projection * view;
// Build a simple directional light view-projection (reversed-Z orthographic)
// Centered around the camera for now. For the initial CSM-plumbing test,
// duplicate this single shadow matrix across all cascades so we render
// four identical shadow maps. This verifies the pass/descriptor wiring.
// Mixed Near + CSM shadow setup
// - Cascade 0: legacy/simple shadow (near range around camera)
// - Cascades 1..N-1: cascaded shadow maps covering farther ranges up to kShadowCSMFar
{
const glm::vec3 camPos = glm::vec3(glm::inverse(view)[3]);
const glm::mat4 invView = glm::inverse(view);
const glm::vec3 camPos = glm::vec3(invView[3]);
// Sun direction and light basis
glm::vec3 L = glm::normalize(-glm::vec3(sceneData.sunlightDirection));
if (glm::length(L) < 1e-5f) L = glm::vec3(0.0f, -1.0f, 0.0f);
const glm::vec3 worldUp(0.0f, 1.0f, 0.0f);
glm::vec3 right = glm::normalize(glm::cross(worldUp, L));
glm::vec3 up = glm::normalize(glm::cross(L, right));
@@ -122,32 +123,111 @@ void SceneManager::update_scene()
up = glm::normalize(glm::cross(L, right));
}
const float orthoRange = 40.0f; // XY half-extent
const float nearDist = 0.1f;
const float farDist = 200.0f;
const glm::vec3 lightPos = camPos - L * 100.0f;
glm::mat4 viewLight = glm::lookAtRH(lightPos, camPos, up);
// Standard RH ZO ortho with near<far (works with our reversed-Z depth setup
// as we clamp and bias in shader). We'll stabilize/flip later when CSM lands.
glm::mat4 projLight = glm::orthoRH_ZO(-orthoRange, orthoRange, -orthoRange, orthoRange,
nearDist, farDist);
sceneData.lightViewProj = projLight * viewLight;
// Fill cascade arrays with the same matrix for now so the shadow
// pass can run four times using identical transforms.
for (int c = 0; c < kShadowCascadeCount; ++c)
// 0) Legacy near/simple shadow matrix (kept for cascade 0)
{
sceneData.lightViewProjCascades[c] = sceneData.lightViewProj;
const float orthoRange = 30.0f; // XY half-extent around camera
const float nearDist = 0.1f;
const float farDist = 150.0f;
const glm::vec3 lightPos = camPos - L * 50.0f;
const glm::mat4 viewLight = glm::lookAtRH(lightPos, camPos, up);
const glm::mat4 projLight = glm::orthoRH_ZO(-orthoRange, orthoRange, -orthoRange, orthoRange,
nearDist, farDist);
const glm::mat4 lightVP = projLight * viewLight;
sceneData.lightViewProj = lightVP; // kept for debug/compat
sceneData.lightViewProjCascades[0] = lightVP; // cascade 0 uses the simple map
}
// Provide a simple increasing split hint (view-space distances).
// Not used yet by shaders, but helps when we switch to CSM.
// 1) Build cascade split distances (view-space, positive forward)
const float farView = kShadowCSMFar;
sceneData.cascadeSplitsView = glm::vec4(0.1f * farView,
0.3f * farView,
0.6f * farView,
1.0f * farView);
// Choose a near/CSM boundary tuned for close-up detail
const float nearSplit = 100.0;
// Practical split scheme for remaining 3 cascades
const float lambda = 0.6f;
float cStart = nearSplit;
float splits[3]{}; // end distances for cascades 1..3
for (int i = 1; i <= 3; ++i)
{
float si = float(i) / 3.0f;
float logd = nearSplit * powf(farView / nearSplit, si);
float lind = glm::mix(nearSplit, farView, si);
splits[i - 1] = glm::mix(lind, logd, lambda);
}
sceneData.cascadeSplitsView = glm::vec4(nearSplit, splits[0], splits[1], farView);
// 2) For cascades 1..3, compute light-space ortho matrices that bound the camera frustum slice
auto frustum_corners_world = [&](float zn, float zf)
{
// camera looks along -Z in view space
const float tanHalfFov = tanf(fov * 0.5f);
const float yN = tanHalfFov * zn;
const float xN = yN * aspect;
const float yF = tanHalfFov * zf;
const float xF = yF * aspect;
// view-space corners
glm::vec3 vs[8] = {
{-xN, -yN, -zn}, {+xN, -yN, -zn}, {+xN, +yN, -zn}, {-xN, +yN, -zn},
{-xF, -yF, -zf}, {+xF, -yF, -zf}, {+xF, +yF, -zf}, {-xF, +yF, -zf}
};
std::array<glm::vec3, 8> ws{};
for (int i = 0; i < 8; ++i)
{
ws[i] = glm::vec3(invView * glm::vec4(vs[i], 1.0f));
}
return ws;
};
auto build_light_matrix_for_slice = [&](float zNearVS, float zFarVS)
{
auto ws = frustum_corners_world(zNearVS, zFarVS);
// Light view looking toward cascade center
glm::vec3 center(0.0f);
for (auto &p : ws) center += p; center *= (1.0f / 8.0f);
glm::vec3 lightPos = center - L * 200.0f;
glm::mat4 V = glm::lookAtRH(lightPos, center, up);
// Project corners to light space and compute AABB
glm::vec3 minLS(FLT_MAX), maxLS(-FLT_MAX);
for (auto &p : ws)
{
glm::vec3 q = glm::vec3(V * glm::vec4(p, 1.0f));
minLS = glm::min(minLS, q);
maxLS = glm::max(maxLS, q);
}
// Expand XY a bit to be safe/stable
glm::vec2 halfXY = 0.5f * glm::vec2(maxLS.x - minLS.x, maxLS.y - minLS.y);
float radius = glm::max(halfXY.x, halfXY.y) * kShadowCascadeRadiusScale + kShadowCascadeRadiusMargin;
glm::vec2 centerXY = 0.5f * glm::vec2(maxLS.x + minLS.x, maxLS.y + minLS.y);
// Optional texel snapping for stability
const float texel = (2.0f * radius) / kShadowMapResolution;
centerXY.x = floorf(centerXY.x / texel) * texel;
centerXY.y = floorf(centerXY.y / texel) * texel;
// Compose snapped view matrix by overriding translation in light space
glm::mat4 Vsnapped = V;
// Extract current translation in light space for center; replace x/y with snapped center
glm::vec3 centerLS = glm::vec3(V * glm::vec4(center, 1.0f));
glm::vec3 delta = glm::vec3(centerXY, centerLS.z) - centerLS;
// Apply delta in light space by post-multiplying with a translation
Vsnapped = glm::translate(glm::mat4(1.0f), -delta) * V;
float nearLS = minLS.z - 50.0f; // pull near/far generously around slice depth range
float farLS = maxLS.z + 250.0f;
glm::mat4 P = glm::orthoRH_ZO(-radius, radius, -radius, radius, std::max(0.1f, -nearLS), std::max(10.0f, farLS - nearLS + 10.0f));
return P * Vsnapped;
};
// Fill cascades 1..3
float prev = nearSplit;
for (int ci = 1; ci < kShadowCascadeCount; ++ci)
{
float end = (ci < kShadowCascadeCount - 1) ? splits[ci - 1] : farView;
sceneData.lightViewProjCascades[ci] = build_light_matrix_for_slice(prev, end);
prev = end;
}
}
auto end = std::chrono::system_clock::now();