ADD: Clipmap shadow
This commit is contained in:
@@ -30,16 +30,19 @@ const vec2 POISSON_16[16] = vec2[16](
|
|||||||
vec2(0.1197, 0.0779), vec2(-0.0905, -0.1203)
|
vec2(0.1197, 0.0779), vec2(-0.0905, -0.1203)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Clipmap selection: choose the smallest level whose light-space XY NDC contains the point.
|
||||||
uint selectCascadeIndex(vec3 worldPos)
|
uint selectCascadeIndex(vec3 worldPos)
|
||||||
{
|
{
|
||||||
// Compute view-space positive depth
|
for (uint i = 0u; i < 4u; ++i)
|
||||||
vec4 vpos = sceneData.view * vec4(worldPos, 1.0);
|
{
|
||||||
float depthVS = -vpos.z;
|
vec4 lclip = sceneData.lightViewProjCascades[i] * vec4(worldPos, 1.0);
|
||||||
// Near/simple map covers [0, sceneData.cascadeSplitsView.x)
|
vec3 ndc = lclip.xyz / max(lclip.w, 1e-6);
|
||||||
if (depthVS < sceneData.cascadeSplitsView.x) return 0u;
|
if (abs(ndc.x) <= 1.0 && abs(ndc.y) <= 1.0 && ndc.z >= 0.0 && ndc.z <= 1.0)
|
||||||
if (depthVS < sceneData.cascadeSplitsView.y) return 1u;
|
{
|
||||||
if (depthVS < sceneData.cascadeSplitsView.z) return 2u;
|
return i;
|
||||||
return 3u; // last cascade extends to w
|
}
|
||||||
|
}
|
||||||
|
return 3u; // fallback to farthest level
|
||||||
}
|
}
|
||||||
|
|
||||||
float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
|
float calcShadowVisibility(vec3 worldPos, vec3 N, vec3 L)
|
||||||
|
|||||||
@@ -17,3 +17,11 @@ inline constexpr float kShadowMapResolution = 2048.0f;
|
|||||||
inline constexpr float kShadowCascadeRadiusScale = 2.5f;
|
inline constexpr float kShadowCascadeRadiusScale = 2.5f;
|
||||||
// Additive XY margin in world units (light-space) beyond scaled radius
|
// Additive XY margin in world units (light-space) beyond scaled radius
|
||||||
inline constexpr float kShadowCascadeRadiusMargin = 40.0f;
|
inline constexpr float kShadowCascadeRadiusMargin = 40.0f;
|
||||||
|
|
||||||
|
// Clipmap shadow configuration (used when cascades operate in clipmap mode)
|
||||||
|
// Base coverage radius of level 0 around the camera (world units). Each level doubles the radius.
|
||||||
|
inline constexpr float kShadowClipBaseRadius = 40.0f;
|
||||||
|
// Pullback distance of the light eye from the clipmap center along the light direction (world units)
|
||||||
|
inline constexpr float kShadowClipLightPullback = 80.0f;
|
||||||
|
// Additional Z padding for the orthographic frustum along light direction
|
||||||
|
inline constexpr float kShadowClipZPadding = 80.0f;
|
||||||
|
|||||||
@@ -104,11 +104,9 @@ void SceneManager::update_scene()
|
|||||||
sceneData.proj = projection;
|
sceneData.proj = projection;
|
||||||
sceneData.viewproj = projection * view;
|
sceneData.viewproj = projection * view;
|
||||||
|
|
||||||
// Mixed Near + CSM shadow setup
|
// Clipmap shadow setup (directional). Each level i covers a square region
|
||||||
// - Cascade 0: legacy/simple shadow (near range around camera)
|
// around the camera in the light's XY plane with radius R_i = R0 * 2^i.
|
||||||
// - Cascades 1..N-1: cascaded shadow maps covering farther ranges up to kShadowCSMFar
|
// The region center is snapped to the light-space texel grid for stability.
|
||||||
|
|
||||||
// ---- Mixed Near + CSM shadow setup (fixed) ----
|
|
||||||
{
|
{
|
||||||
const glm::mat4 invView = glm::inverse(view);
|
const glm::mat4 invView = glm::inverse(view);
|
||||||
const glm::vec3 camPos = glm::vec3(invView[3]);
|
const glm::vec3 camPos = glm::vec3(invView[3]);
|
||||||
@@ -119,96 +117,50 @@ void SceneManager::update_scene()
|
|||||||
glm::vec3 right = glm::cross(L, worldUp);
|
glm::vec3 right = glm::cross(L, worldUp);
|
||||||
if (glm::length2(right) < 1e-6f) right = glm::vec3(1, 0, 0);
|
if (glm::length2(right) < 1e-6f) right = glm::vec3(1, 0, 0);
|
||||||
right = glm::normalize(right);
|
right = glm::normalize(right);
|
||||||
glm::vec3 up = glm::normalize(glm::cross(right, L)); {
|
glm::vec3 up = glm::normalize(glm::cross(right, L));
|
||||||
const float orthoRange = 10.0f;
|
|
||||||
const float nearDist = 0.1f;
|
|
||||||
const float farDist = 200.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,
|
auto level_radius = [](int level) {
|
||||||
nearDist, farDist);
|
return kShadowClipBaseRadius * powf(2.0f, float(level));
|
||||||
|
|
||||||
const glm::mat4 lightVP = projLight * viewLight;
|
|
||||||
sceneData.lightViewProj = lightVP;
|
|
||||||
sceneData.lightViewProjCascades[0] = lightVP;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float farView = kShadowCSMFar;
|
|
||||||
const float nearSplit = 5.0f;
|
|
||||||
const float lambda = 1.0f;
|
|
||||||
float splits[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);
|
|
||||||
|
|
||||||
auto frustum_corners_world = [&](float zn, float zf) {
|
|
||||||
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;
|
|
||||||
|
|
||||||
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) {
|
// Keep a copy of level radii in cascadeSplitsView for debug/visualization
|
||||||
auto ws = frustum_corners_world(zNearVS, zFarVS);
|
sceneData.cascadeSplitsView = glm::vec4(
|
||||||
|
level_radius(0), level_radius(1), level_radius(2), level_radius(3));
|
||||||
|
|
||||||
glm::vec3 center(0.0f);
|
for (int ci = 0; ci < kShadowCascadeCount; ++ci)
|
||||||
for (auto &p: ws) center += p;
|
|
||||||
center *= (1.0f / 8.0f);
|
|
||||||
const float lightPullback = 20.0f;
|
|
||||||
glm::mat4 V = glm::lookAtRH(center - L * lightPullback, center, up);
|
|
||||||
|
|
||||||
glm::vec3 minLS(FLT_MAX), maxLS(-FLT_MAX);
|
|
||||||
for (auto &p: ws)
|
|
||||||
{
|
{
|
||||||
glm::vec3 q = glm::vec3(V * glm::vec4(p, 1.0f));
|
const float radius = level_radius(ci);
|
||||||
minLS = glm::min(minLS, q);
|
|
||||||
maxLS = glm::max(maxLS, q);
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec2 extXY = glm::vec2(maxLS.x - minLS.x, maxLS.y - minLS.y);
|
// Compute camera coordinates in light's orthonormal basis (world -> light XY)
|
||||||
float radius = 0.5f * glm::max(extXY.x, extXY.y);
|
const float u = glm::dot(camPos, right);
|
||||||
radius = radius * kShadowCascadeRadiusScale + kShadowCascadeRadiusMargin;
|
const float v = glm::dot(camPos, up);
|
||||||
|
|
||||||
glm::vec2 centerXY = 0.5f * glm::vec2(maxLS.x + minLS.x, maxLS.y + minLS.y);
|
|
||||||
|
|
||||||
|
// Texel size in light-space at this level
|
||||||
const float texel = (2.0f * radius) / float(kShadowMapResolution);
|
const float texel = (2.0f * radius) / float(kShadowMapResolution);
|
||||||
centerXY.x = floorf(centerXY.x / texel) * texel;
|
const float uSnapped = floorf(u / texel) * texel;
|
||||||
centerXY.y = floorf(centerXY.y / texel) * texel;
|
const float vSnapped = floorf(v / texel) * texel;
|
||||||
|
const float du = uSnapped - u;
|
||||||
|
const float dv = vSnapped - v;
|
||||||
|
|
||||||
glm::mat4 Vsnapped = glm::translate(glm::mat4(1.0f),
|
// World-space snapped center of this clip level
|
||||||
-glm::vec3(centerXY.x, centerXY.y, 0.0f)) * V;
|
const glm::vec3 center = camPos + right * du + up * dv;
|
||||||
|
|
||||||
const float zPad = 50.0f;
|
// Build light view matrix looking at the snapped center
|
||||||
float zNear = glm::max(0.1f, -maxLS.z - zPad);
|
const glm::vec3 eye = center - L * kShadowClipLightPullback;
|
||||||
float zFar = -minLS.z + zPad;
|
const glm::mat4 V = glm::lookAtRH(eye, center, up);
|
||||||
|
|
||||||
glm::mat4 P = glm::orthoRH_ZO(-radius, radius, -radius, radius, zNear, zFar);
|
// Conservative Z range along light direction
|
||||||
|
const float zNear = 0.1f;
|
||||||
|
const float zFar = kShadowClipLightPullback + kShadowClipZPadding;
|
||||||
|
|
||||||
return P * Vsnapped;
|
const glm::mat4 P = glm::orthoRH_ZO(-radius, radius, -radius, radius, zNear, zFar);
|
||||||
};
|
const glm::mat4 lightVP = P * V;
|
||||||
|
|
||||||
float prev = nearSplit;
|
sceneData.lightViewProjCascades[ci] = lightVP;
|
||||||
for (int ci = 1; ci < kShadowCascadeCount; ++ci)
|
if (ci == 0)
|
||||||
{
|
{
|
||||||
float end = (ci < kShadowCascadeCount - 1) ? splits[ci - 1] : farView;
|
sceneData.lightViewProj = lightVP;
|
||||||
sceneData.lightViewProjCascades[ci] = build_light_matrix_for_slice(prev, end);
|
}
|
||||||
prev = end;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user