Make shadows follow player movement continuously

Remove freeze-while-moving and idle smoothing logic from shadow
center computation. Texel snapping already prevents shimmer, so
the shadow projection can track the player directly each frame.
This commit is contained in:
Kelsi 2026-02-23 08:47:38 -08:00
parent 5072c536b5
commit d65b170774
2 changed files with 4 additions and 40 deletions

View file

@ -238,7 +238,7 @@ private:
glm::vec3 shadowCenter = glm::vec3(0.0f); glm::vec3 shadowCenter = glm::vec3(0.0f);
bool shadowCenterInitialized = false; bool shadowCenterInitialized = false;
bool shadowsEnabled = true; bool shadowsEnabled = true;
int shadowPostMoveFrames_ = 0; // transition marker for movement->idle shadow recenter
public: public:
// Character preview registration (for off-screen composite pass) // Character preview registration (for off-screen composite pass)

View file

@ -3713,52 +3713,16 @@ glm::mat4 Renderer::computeLightSpaceMatrix() {
sunDir = glm::normalize(sunDir); sunDir = glm::normalize(sunDir);
} }
// Keep a stable shadow focus center and move it smoothly toward the player // Shadow center follows the player directly; texel snapping below
// to avoid visible shadow "state jumps" during movement. // prevents shimmer without needing to freeze the projection.
glm::vec3 desiredCenter = characterPosition; glm::vec3 desiredCenter = characterPosition;
// Don't initialize shadow center until the player has a real world position.
// characterPosition starts at (0,0,0) and gets set once the server places us.
if (!shadowCenterInitialized) { if (!shadowCenterInitialized) {
if (glm::dot(desiredCenter, desiredCenter) < 1.0f) { if (glm::dot(desiredCenter, desiredCenter) < 1.0f) {
// Position not set yet — skip shadow rendering this frame.
return glm::mat4(0.0f); return glm::mat4(0.0f);
} }
shadowCenter = desiredCenter;
shadowCenterInitialized = true; shadowCenterInitialized = true;
} else {
const bool movingNow = cameraController && cameraController->isMoving();
if (movingNow) {
// Hold projection center fixed while moving to eliminate
// frame-to-frame surface flicker from projection churn.
shadowPostMoveFrames_ = 1; // transition marker: was moving last frame
} else {
if (shadowPostMoveFrames_ == 1) {
// First frame after movement: snap once so there's no delayed catch-up.
shadowCenter = desiredCenter;
} else {
// Normal idle smoothing.
constexpr float kCenterLerp = 0.12f;
constexpr float kMaxHorizontalStep = 1.5f;
constexpr float kMaxVerticalStep = 0.6f;
glm::vec2 deltaXY(desiredCenter.x - shadowCenter.x, desiredCenter.y - shadowCenter.y);
float distXY = glm::length(deltaXY);
if (distXY > 0.001f) {
float step = std::min(distXY * kCenterLerp, kMaxHorizontalStep);
glm::vec2 move = (deltaXY / distXY) * step;
shadowCenter.x += move.x;
shadowCenter.y += move.y;
}
float deltaZ = desiredCenter.z - shadowCenter.z;
if (std::abs(deltaZ) > 0.001f) {
float stepZ = std::clamp(deltaZ * kCenterLerp, -kMaxVerticalStep, kMaxVerticalStep);
shadowCenter.z += stepZ;
}
}
shadowPostMoveFrames_ = 0;
}
} }
shadowCenter = desiredCenter;
glm::vec3 center = shadowCenter; glm::vec3 center = shadowCenter;
// Snap to shadow texel grid to keep projection stable while moving. // Snap to shadow texel grid to keep projection stable while moving.