Skip to content

fix(water): fix water blending (ghosting) and LOD gaps#2440

Merged
SkrubbySkrubInAShrub merged 7 commits into
community-shaders:devfrom
FIocker:fix/water-blend-ghosting-lod-gaps-20260530
Jun 1, 2026
Merged

fix(water): fix water blending (ghosting) and LOD gaps#2440
SkrubbySkrubInAShrub merged 7 commits into
community-shaders:devfrom
FIocker:fix/water-blend-ghosting-lod-gaps-20260530

Conversation

@FIocker
Copy link
Copy Markdown
Contributor

@FIocker FIocker commented May 30, 2026

Fixes water edge ghosting and dark outlines caused by stale water blend history, and prevents LOD water gaps that became visible once the history blending was cleaned up

  • The water blend pass discards non-water pixels, which means the history target can keep old coverage/color in those pixels. This clears the water history render target once per frame before the blend pass while keeping the discard in place, so the main scene is not overwritten

  • Since the cleared history is transparent black, water history is now stored premultiplied by coverage and un-premultiplied when sampled. This prevents bilinear filtering against cleared pixels from creating dark outlines along moving water edges

  • Also only culls Unified Water LOD tiles for attached cells that actually have water. Dry loaded cells do not create active water, so hiding their LOD tile could leave visible gaps

Summary by CodeRabbit

  • Bug Fixes
    • Water blending now un-premultiplies history before interpolation, preserving previous-frame color and alpha to reduce ghosting and color artifacts.
    • Output now stores premultiplied water color for more accurate compositing.
    • Dry attached terrain cells no longer force culling, restoring expected water on loaded terrain.
    • Imagespace render path clears and forwards the water blend target to reduce flicker.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Un‑premultiplies sampled waterHistory before blending and writes premultiplied final color with coverage; changes LOD‑4 attached‑cell culling to use the cell's kHasWater flag; adds an imagespace vfunc hook that may clear RTV slot 1 before chaining to the original ISWaterBlend render function.

Changes

Water Rendering Pipeline Fixes

Layer / File(s) Summary
Water history un‑premultiply and premultiplied output
package/Shaders/ISWaterBlend.hlsl
Un‑premultiplies waterHistory (divide RGB by max(waterHistory.w, 0.0001)) before lerp and writes premultiplied finalColor * waterCoverage into psout.Color1 with alpha=waterCoverage.
LOD‑4 water parent culling based on kHasWater
src/Features/UnifiedWater.cpp
For attached grid cells at LOD 4, cull is set based on the cell kHasWater flag instead of forcing true for attached cells.
Per‑frame RTV clearing hook for ISWaterBlend
src/Hooks.cpp
Adds WaterBlendHistory::BSImagespaceShader_Render thunk that checks shadowState render-target index 1, clears the corresponding runtime RTV to transparent black when present, then calls the original render function; installs the thunk in Hooks::Install().

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • SkrubbySkrubInAShrub
  • alandtse

"I un‑premultiplied yesterday's tide,
I nudged a dry mesh back into sight,
I cleared the second render slot clean,
Reflections whisper where waves convene,
— a rabbit, pleased beneath moonlight" 🐇💧

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: fixing water blending ghosting issues and LOD gaps. It directly reflects the three core objectives without being overly broad or vague.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

No actionable suggestions for changed features.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/Features/UnifiedWater.cpp (1)

484-487: 💤 Low value

Document/replace the CellState magic number (6) (src/Features/UnifiedWater.cpp:484-487)
static_cast<RE::TESObjectCELL::CellState>(6) isn’t backed by any RE::TESObjectCELL::CellState definition in this repo, so the meaning of 6 can’t be checked here. Replace it with a named constant (or versioned mapping + comment) explaining which CellState it corresponds to and why it’s required for SE/AE/VR compatibility.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Features/UnifiedWater.cpp` around lines 484 - 487, Replace the magic
literal static_cast<RE::TESObjectCELL::CellState>(6) used inside the
gridCells->GetCell(...) check with a named constant or versioned mapping (e.g.,
constexpr auto CellState_kAttachedAlternative or a function
MapCellStateForRuntime()) and update the cell->cellState.any(...) call to use
that named symbol; add a short comment documenting which runtime/version
(SE/AE/VR) this value maps to and why it’s required for compatibility, or
implement a small mapping helper that returns the correct
RE::TESObjectCELL::CellState for the current runtime and use that helper in
place of the literal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/Features/UnifiedWater.cpp`:
- Around line 484-487: Replace the magic literal
static_cast<RE::TESObjectCELL::CellState>(6) used inside the
gridCells->GetCell(...) check with a named constant or versioned mapping (e.g.,
constexpr auto CellState_kAttachedAlternative or a function
MapCellStateForRuntime()) and update the cell->cellState.any(...) call to use
that named symbol; add a short comment documenting which runtime/version
(SE/AE/VR) this value maps to and why it’s required for compatibility, or
implement a small mapping helper that returns the correct
RE::TESObjectCELL::CellState for the current runtime and use that helper in
place of the literal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8db8b00d-e75b-48bf-b6ef-abdd853f051c

📥 Commits

Reviewing files that changed from the base of the PR and between f9eb34f and 75b5d08.

📒 Files selected for processing (3)
  • package/Shaders/ISWaterBlend.hlsl
  • src/Features/UnifiedWater.cpp
  • src/Hooks.cpp

Comment thread src/Hooks.cpp Outdated
@FIocker FIocker force-pushed the fix/water-blend-ghosting-lod-gaps-20260530 branch from 75b5d08 to 3350784 Compare May 30, 2026 21:25
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 30, 2026

✅ A pre-release build is available for this PR:
Download

@FIocker FIocker requested a review from doodlum May 31, 2026 07:52
@SkrubbySkrubInAShrub SkrubbySkrubInAShrub merged commit 22ea056 into community-shaders:dev Jun 1, 2026
11 checks passed
ParticleTroned added a commit to ParticleTroned/skyrim-community-shaders that referenced this pull request Jun 4, 2026
Rationale: discarded non-water pixels can leave stale water history behind, and sampling cleared transparent history without premultiplication creates dark edge artifacts. Loaded dry cells can also hide LOD water even when no active water replaces it.

Implementation: clear the ISWaterBlend history render target before the blend pass, store water history premultiplied by coverage and un-premultiply it when sampled, and cull Unified Water LOD parents only when the attached grid cell actually has water while keeping deferred child-world cull completion accurate.

Acknowledgement: Manual port of community-shaders#2440 by FIocker.

Co-authored-by: FIocker <4461558+FIocker@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants