fix(gameplay): stabilize run animation and clean ghost/death visuals

This commit is contained in:
Kelsi 2026-03-14 08:27:32 -07:00
parent 1a4b21955c
commit cebca9a882
6 changed files with 51 additions and 27 deletions

View file

@ -1031,9 +1031,8 @@ void Renderer::beginFrame() {
// FXAA resource management — FXAA can coexist with FSR1 and FSR3.
// When both FXAA and FSR3 are enabled, FXAA runs as a post-FSR3 pass.
// Ghost mode also reuses this post pass for true grayscale when FXAA is
// disabled in settings.
const bool useFXAAPostPass = (fxaa_.enabled || ghostMode_);
// Do not force this pass for ghost mode; keep AA quality strictly user-controlled.
const bool useFXAAPostPass = fxaa_.enabled;
if ((fxaa_.needsRecreate || !useFXAAPostPass) && fxaa_.sceneFramebuffer) {
destroyFXAAResources();
fxaa_.needsRecreate = false;
@ -1250,7 +1249,7 @@ void Renderer::endFrame() {
// FSR3+FXAA combined: re-point FXAA's descriptor to the FSR3 temporal output
// so renderFXAAPass() applies spatial AA on the temporally-stabilized frame.
// This must happen outside the render pass (descriptor updates are CPU-side).
if ((fxaa_.enabled || ghostMode_) && fxaa_.descSet && fxaa_.sceneSampler) {
if (fxaa_.enabled && fxaa_.descSet && fxaa_.sceneSampler) {
VkImageView fsr3OutputView = VK_NULL_HANDLE;
if (fsr2_.useAmdBackend) {
if (fsr2_.amdFsr3FramegenRuntimeActive && fsr2_.framegenOutput.image)
@ -1309,7 +1308,7 @@ void Renderer::endFrame() {
// of RCAS sharpening. FXAA descriptor is temporarily pointed to the FSR3
// history buffer (which is already in SHADER_READ_ONLY_OPTIMAL). This gives
// FSR3 temporal stability + FXAA spatial edge smoothing ("ultra quality native").
if ((fxaa_.enabled || ghostMode_) && fxaa_.pipeline && fxaa_.descSet) {
if (fxaa_.enabled && fxaa_.pipeline && fxaa_.descSet) {
renderFXAAPass();
} else {
// Draw RCAS sharpening from accumulated history buffer
@ -1318,7 +1317,7 @@ void Renderer::endFrame() {
// Restore FXAA descriptor to its normal scene color source so standalone
// FXAA frames are not affected by the FSR3 history pointer set above.
if ((fxaa_.enabled || ghostMode_) && fxaa_.descSet && fxaa_.sceneSampler && fxaa_.sceneColor.imageView) {
if (fxaa_.enabled && fxaa_.descSet && fxaa_.sceneSampler && fxaa_.sceneColor.imageView) {
VkDescriptorImageInfo restoreInfo{};
restoreInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
restoreInfo.imageView = fxaa_.sceneColor.imageView;
@ -1342,7 +1341,7 @@ void Renderer::endFrame() {
}
fsr2_.frameIndex = (fsr2_.frameIndex + 1) % 256; // Wrap to keep Halton values well-distributed
} else if ((fxaa_.enabled || ghostMode_) && fxaa_.sceneFramebuffer) {
} else if (fxaa_.enabled && fxaa_.sceneFramebuffer) {
// End the off-screen scene render pass
vkCmdEndRenderPass(currentCmd);
@ -1857,7 +1856,18 @@ void Renderer::updateCharacterAnimation() {
CharAnimState newState = charAnimState;
bool moving = cameraController->isMoving();
const bool rawMoving = cameraController->isMoving();
const bool rawSprinting = cameraController->isSprinting();
constexpr float kLocomotionStopGraceSec = 0.12f;
if (rawMoving) {
locomotionStopGraceTimer_ = kLocomotionStopGraceSec;
locomotionWasSprinting_ = rawSprinting;
} else {
locomotionStopGraceTimer_ = std::max(0.0f, locomotionStopGraceTimer_ - lastDeltaTime_);
}
// Debounce brief input/state dropouts (notably during both-mouse steering) so
// locomotion clips do not restart every few frames.
bool moving = rawMoving || locomotionStopGraceTimer_ > 0.0f;
bool movingForward = cameraController->isMovingForward();
bool movingBackward = cameraController->isMovingBackward();
bool autoRunning = cameraController->isAutoRunning();
@ -1870,7 +1880,7 @@ void Renderer::updateCharacterAnimation() {
bool anyStrafeRight = strafeRight && !strafeLeft && pureStrafe;
bool grounded = cameraController->isGrounded();
bool jumping = cameraController->isJumping();
bool sprinting = cameraController->isSprinting();
bool sprinting = rawSprinting || (!rawMoving && moving && locomotionWasSprinting_);
bool sitting = cameraController->isSitting();
bool swim = cameraController->isSwimming();
bool forceMelee = meleeSwingTimer > 0.0f && grounded && !swim;
@ -2530,8 +2540,14 @@ void Renderer::updateCharacterAnimation() {
float currentAnimTimeMs = 0.0f;
float currentAnimDurationMs = 0.0f;
bool haveState = characterRenderer->getAnimationState(characterInstanceId, currentAnimId, currentAnimTimeMs, currentAnimDurationMs);
if (!haveState || currentAnimId != animId) {
// Some frames may transiently fail getAnimationState() while resources/instance state churn.
// Avoid reissuing the same clip on those frames, which restarts locomotion and causes hitches.
const bool requestChanged = (lastPlayerAnimRequest_ != animId) || (lastPlayerAnimLoopRequest_ != loop);
const bool shouldPlay = (haveState && currentAnimId != animId) || (!haveState && requestChanged);
if (shouldPlay) {
characterRenderer->playAnimation(characterInstanceId, animId, loop);
lastPlayerAnimRequest_ = animId;
lastPlayerAnimLoopRequest_ = loop;
}
}
@ -5075,7 +5091,7 @@ void Renderer::renderFXAAPass() {
vkCmdBindDescriptorSets(currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
fxaa_.pipelineLayout, 0, 1, &fxaa_.descSet, 0, nullptr);
// Pass rcpFrame + sharpness + desaturate (vec4, 16 bytes).
// Pass rcpFrame + sharpness + effect flag (vec4, 16 bytes).
// When FSR2/FSR3 is active alongside FXAA, forward FSR2's sharpness so the
// post-FXAA unsharp-mask step restores the crispness that FXAA's blur removes.
float sharpness = fsr2_.enabled ? fsr2_.sharpness : 0.0f;
@ -5083,7 +5099,7 @@ void Renderer::renderFXAAPass() {
1.0f / static_cast<float>(ext.width),
1.0f / static_cast<float>(ext.height),
sharpness,
ghostMode_ ? 1.0f : 0.0f // desaturate: 1=ghost grayscale, 0=normal
0.0f
};
vkCmdPushConstants(currentCmd, fxaa_.pipelineLayout,
VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, pc);
@ -5273,12 +5289,6 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
renderOverlay(tint, cmd);
}
}
// Ghost mode desaturation overlay (non-FXAA path approximation).
// When FXAA is active the FXAA shader applies true per-pixel desaturation;
// otherwise a high-opacity gray overlay gives a similar washed-out effect.
if (ghostMode_ && overlayPipeline && !(fxaa_.enabled || fxaa_.sceneFramebuffer)) {
renderOverlay(glm::vec4(0.5f, 0.5f, 0.55f, 0.82f), cmd);
}
if (minimap && minimap->isEnabled() && camera && window) {
glm::vec3 minimapCenter = camera->getPosition();
if (cameraController && cameraController->isThirdPerson())
@ -5413,10 +5423,6 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
renderOverlay(tint);
}
}
// Ghost mode desaturation overlay (non-FXAA path approximation).
if (ghostMode_ && overlayPipeline && !(fxaa_.enabled || fxaa_.sceneFramebuffer)) {
renderOverlay(glm::vec4(0.5f, 0.5f, 0.55f, 0.82f));
}
if (minimap && minimap->isEnabled() && camera && window) {
glm::vec3 minimapCenter = camera->getPosition();
if (cameraController && cameraController->isThirdPerson())