Skip to content

The water's mirror clip and haze fixes, and terrain tessellation at a density of 48 - #550

Merged
RyeMutt merged 4 commits into
developfrom
rye/water-shadow-fixes
Oct 22, 2026
Merged

RyeMutt merged 4 commits into
developfrom
rye/water-shadow-fixes

Conversation

@RyeMutt

@RyeMutt RyeMutt commented Oct 22, 2026

Copy link
Copy Markdown
Member

Three fixes to the water and its haze from a defect and perf review of the water and sun shadow paths, and a lower terrain tessellation default.

  • The water discards at a mirror's plane after its derivatives are taken (waterF.glsl, underWaterF.glsl). Both called mirrorClip first, which discards under MIRROR_CLIP, and then took derivatives: the wave normals' implicit-LOD reads and dFdx in water_wave_normals, and dFdx(pos) in the sun's shadow. GLSL leaves a quad's derivatives undefined for the lanes beside one that discards, so along a mirror's plane the reflected water read its normal maps at the wrong mip and filtered its shadow over the wrong footprint. Both now test with mirrorClipped and discard at the end, compiled only in the mirror variant so the water elsewhere keeps its early depth test, as pbrterrainF.glsl does.
  • The water haze leaves its depth test to the hardware (waterHazeF.glsl). From above, doWaterHaze draws the field with the hardware depth test on, and the shader repeated it by hand and discarded. The pass reads the scene's depth past a texture barrier or from a copy (LLPipeline::hazeDepth), so the manual test, its second copy of the depth convention, and the fragment stage's above_water are gone.
  • No water haze under water that is drawn opaque (LLPipeline::doWaterHaze). Where the view's water does not refract (ALRenderResourcePlan::refractsWater: RenderTransparentWater off, and every probe face), the opaque water drawn straight after hides everything the haze fogged, so the pass is skipped above water. Seen from below it is unchanged.
  • The terrain tessellates at a density of 48 by default, not 64 (AlchemyRenderTerrainTessDensity). A 16 m patch is split 48 ways within 16 m of the camera, 12 at 64 m, and not at all past 768 m, at a little over half the triangles. The fallback in LLDrawPoolTerrain and the setting's worked example follow it; a saved value is kept.

Testing. pipeline.cpp and lldrawpoolterrain.cpp compile clean with the viewer's own flags (vs2026-os, RelWithDebInfo, /W3 /WX). The shaders were not compiled standalone. In-world check owed: water seen in a mirror, the water haze from above and below with RenderTransparentWater on and off, and the terrain's look at 48.

🤖 Generated with Claude Code

RyeMutt and others added 4 commits October 22, 2026 08:46
…n, not before

waterF.glsl and underWaterF.glsl called mirrorClip first, which discards
under MIRROR_CLIP, and then took derivatives: the wave normals' implicit-LOD
reads and dFdx in water_wave_normals, and dFdx(pos) in the sun's shadow.
A lane that discards leaves its quad, and GLSL leaves the derivatives of the
lanes beside it undefined, so along a mirror's plane the reflected water read
its normal maps at the wrong mip and filtered its shadow over the wrong
footprint.

Both test the plane with mirrorClipped at the top and discard at the end, as
pbrterrainF.glsl does. The discard is compiled only in the mirror variant,
so the water elsewhere keeps its early depth test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From above, doWaterHaze draws the field with the hardware depth test on
(GEQUAL under reverse-Z, writing none), and waterHazeF.glsl repeated the
same test by hand and discarded. It did so because some GPUs once could not
depth test against a target bound for sampling; the pass now reads the
scene's depth past a texture barrier or from a copy (LLPipeline::hazeDepth),
and the hardware test runs either way. The manual test, its second copy of
the depth convention and the above_water uniform the fragment stage read for
it are gone; the vertex stage still reads it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
doWaterHaze fogs the scene under the surface, seen from above, by drawing
the whole field over it. Where the view's water does not refract
(ALRenderResourcePlan::refractsWater: RenderTransparentWater off, and every
probe face), the water is drawn opaque straight after and hides all of it,
so the pass read the depth, worked the fog and blended for nothing, once a
frame and once a probe face. Where an exclusion prim removed the water the
haze fogs nothing either (its NotExcluded stencil). Seen from below the pass
is a full-screen fog of what lies in front of the camera and stays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AlchemyRenderTerrainTessDensity's default comes down from 64 to 48: a 16 m
patch is split 48 ways (33 cm) within 16 m of the camera, 12 ways at 64 m,
and not at all past 768 m, where 64 split it to 1 km. The triangle count
grows with the square of the density, so the terrain draws a little over
half the triangles it did, for an edge a third longer near the camera.
The fallback in LLDrawPoolTerrain and the setting's worked example follow
it; a saved value is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 41 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 09b99d1f-3792-446c-a0be-f00d5fc2f192
📥 Commits

Reviewing files that changed from the base of the PR and between 2ff4ef0 and d96fe27.

⛔ Files ignored due to path filters: 3
  • indra/newview/app_settings/shaders/class3/deferred/waterHazeF.glsl is excluded by !**/*.glsl
  • indra/newview/app_settings/shaders/class3/environment/underWaterF.glsl is excluded by !**/*.glsl
  • indra/newview/app_settings/shaders/class3/environment/waterF.glsl is excluded by !**/*.glsl
📒 Files selected for processing: 3
  • indra/newview/app_settings/settings_alchemy.xml
  • indra/newview/lldrawpoolterrain.cpp
  • indra/newview/pipeline.cpp
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@RyeMutt
RyeMutt merged commit 6ce29b4 into develop Oct 22, 2026
17 checks passed
@RyeMutt
RyeMutt deleted the rye/water-shadow-fixes branch October 22, 2026 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant