ADD: Clipmap shadow better quality

This commit is contained in:
2025-10-26 06:57:13 +09:00
parent 3127658b01
commit 85d93fbd67
4 changed files with 100 additions and 27 deletions

View File

@@ -13,10 +13,29 @@ inline constexpr int kShadowCascadeCount = 4;
inline constexpr float kShadowCSMFar = 800.0f;
// Shadow map resolution used for stabilization (texel snapping). Must match actual image size.
inline constexpr float kShadowMapResolution = 2048.0f;
// Extra XY expansion for cascade footprint (safety against FOV/aspect changes)
inline constexpr float kShadowCascadeRadiusScale = 1.1f;
// Additive XY margin in world units beyond the scaled half-size
inline constexpr float kShadowCascadeRadiusMargin = 10.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 = 20.0f;
// Pullback distance of the light eye from the clipmap center along the light direction (world units)
inline constexpr float kShadowClipLightPullback = 160.0f;
inline constexpr float kShadowClipBaseRadius = 30.0f;
// When using dynamic pullback, compute it from the covered XY range of each level.
// pullback = max(kShadowClipPullbackMin, cover * kShadowClipPullbackFactor)
inline constexpr float kShadowClipPullbackFactor = 1.5f; // fraction of XY half-size behind center
inline constexpr float kShadowClipForwardFactor = 1.5f; // fraction of XY half-size in front of center for zFar
inline constexpr float kShadowClipPullbackMin = 10.0f; // lower bound on pullback so near levels dont collapse
// Additional Z padding for the orthographic frustum along light direction
inline constexpr float kShadowClipZPadding = 80.0f;
inline constexpr float kShadowClipZPadding = 40.0f;
// Shadow quality & filtering
// Soft cross-fade band between cascades in light-space NDC (0..1)
inline constexpr float kShadowBorderSmoothNDC = 0.08f;
// Base PCF radius in texels for cascade 0; higher cascades scale up slightly
inline constexpr float kShadowPCFBaseRadius = 1.35f;
// Additional radius added by the farthest cascade (0..+)
inline constexpr float kShadowPCFCascadeGain = 2.0f;
// Raster depth-bias parameters for shadow map rendering (tuned conservatively)
inline constexpr float kShadowDepthBiasConstant = 1.25f;
inline constexpr float kShadowDepthBiasSlope = 1.5f;

View File

@@ -17,6 +17,7 @@
#include "core/asset_manager.h"
#include "render/vk_pipelines.h"
#include "core/vk_types.h"
#include "core/config.h"
void ShadowPass::init(EngineContext *context)
{
@@ -51,8 +52,8 @@ void ShadowPass::init(EngineContext *context)
// Static depth bias to help with surface acne (tune later)
b._rasterizer.depthBiasEnable = VK_TRUE;
b._rasterizer.depthBiasConstantFactor = 2.0f;
b._rasterizer.depthBiasSlopeFactor = 2.0f;
b._rasterizer.depthBiasConstantFactor = kShadowDepthBiasConstant;
b._rasterizer.depthBiasSlopeFactor = kShadowDepthBiasSlope;
b._rasterizer.depthBiasClamp = 0.0f;
};

View File

@@ -113,6 +113,7 @@ void SceneManager::update_scene()
{
const glm::mat4 invView = glm::inverse(view);
const glm::vec3 camPos = glm::vec3(invView[3]);
const glm::vec3 camFwd = -glm::vec3(invView[2]);
glm::vec3 L = glm::normalize(-glm::vec3(sceneData.sunlightDirection));
if (glm::length(L) < 1e-5f) L = glm::vec3(0.0f, -1.0f, 0.0f);
@@ -126,37 +127,37 @@ void SceneManager::update_scene()
return kShadowClipBaseRadius * powf(2.0f, float(level));
};
// Keep a copy of level radii in cascadeSplitsView for debug/visualization
sceneData.cascadeSplitsView = glm::vec4(
level_radius(0), level_radius(1), level_radius(2), level_radius(3));
for (int ci = 0; ci < kShadowCascadeCount; ++ci)
{
const float radius = level_radius(ci);
const float cover = radius * kShadowCascadeRadiusScale + kShadowCascadeRadiusMargin;
// Compute camera coordinates in light's orthonormal basis (world -> light XY)
const float u = glm::dot(camPos, right);
const float v = glm::dot(camPos, up);
const float ahead = radius * 0.5;
const float fu = glm::dot(camFwd, right);
const float fv = glm::dot(camFwd, up);
// Texel size in light-space at this level
const float texel = (2.0f * radius) / float(kShadowMapResolution);
const float u = glm::dot(camPos, right) + fu * ahead;
const float v = glm::dot(camPos, up) + fv * ahead;
const float texel = (2.0f * cover) / float(kShadowMapResolution);
const float uSnapped = floorf(u / texel) * texel;
const float vSnapped = floorf(v / texel) * texel;
const float du = uSnapped - u;
const float dv = vSnapped - v;
// World-space snapped center of this clip level
const glm::vec3 center = camPos + right * du + up * dv;
// Build light view matrix looking at the snapped center
const glm::vec3 eye = center - L * kShadowClipLightPullback;
const float pullback = glm::max(kShadowClipPullbackMin, cover * kShadowClipPullbackFactor);
const glm::vec3 eye = center - L * pullback;
const glm::mat4 V = glm::lookAtRH(eye, center, up);
// Conservative Z range along light direction
const float zNear = 0.1f;
const float zFar = kShadowClipLightPullback + kShadowClipZPadding;
const float zFar = pullback + cover * kShadowClipForwardFactor + kShadowClipZPadding;
const glm::mat4 P = glm::orthoRH_ZO(-radius, radius, -radius, radius, zNear, zFar);
const glm::mat4 P = glm::orthoRH_ZO(-cover, cover, -cover, cover, zNear, zFar);
const glm::mat4 lightVP = P * V;
sceneData.lightViewProjCascades[ci] = lightVP;