Water surfaces and optical depth
Trace analytical waves, scene-color refraction, absorption, water reflections, and focused caustic lighting.
Asterune shades water from an analytical wave surface, a retained scene image, and scene depth. The water pass combines refracted background light, absorption, scattering, reflected light, and foam. Its optical calculations depend on the scene before the water is drawn, rather than a separate simulation of light travel through every water volume.
The water shader has its own reflection and motion paths. Their inputs and sampling rules differ from the opaque screen-space reflection pass described in Lighting and reflections.
Wave displacement and sampling footprint
The vertex shader displaces the surface with 2 main sinusoidal waves and 2 smaller crossing swells. The smaller waves use different directions and phase rates. A nonlinear crest function sharpens positive crests and compensates its mean, rather than raising the whole surface as wave sharpness increases.
Displacement fades near the mesh's UV edges. This keeps a bounded water surface from moving its entire perimeter by the full swell height. Analytical gradients of the wave function produce the broad surface normal; fine ripples add a separate shading-normal contribution.
Ripple gradients combine directional wavelets at several frequencies. The shader reduces middle and fine frequencies as their projected footprint grows. It also fades ripple detail with camera distance. Unresolved ripple variation raises effective roughness from 0.035 toward 0.16, broadening highlights instead of retaining arbitrarily narrow distant sparkles.
Flow advects 2 ripple patterns with phases offset by half a cycle. Their blend weight transfers between the patterns as a phase wraps. That crossfade avoids an abrupt return of the visible pattern to its starting position each cycle. The caustic generator shares the flow-driven ripple function so the surface and focused-light pattern use related motion.
These waves change rendering. Water-volume containment and physical buoyancy have their own world-state calculations; a displaced vertex is not a replacement collision surface for the character or rigid-body solver.
Scene capture and depth reconstruction
Before shading water, the renderer retains a sampleable scene-color image. A single-sample scene copies color; a multisampled scene resolves color at the required pass boundary. The water shader reads this earlier picture while writing the ongoing scene attachment.
Water depth is captured separately into a single-sample texture. With multisampled depth, the copy selects the greatest reverse-Z value across samples, retaining the nearest covered surface. Color averaging and depth selection therefore perform different operations.
The shader linearizes scene depth and water-surface depth with the camera's projection terms. Their difference estimates the view-axis thickness behind the surface. It also reconstructs a floor position along the view ray to estimate vertical water depth. View-axis thickness, ray distance, and vertical depth have different uses in refraction, absorption, and shoreline foam.
Screen-space refraction and absorption
Refraction bends the incident view direction using a water ratio of approximately 0.752. Looking from below uses the inverse ratio. A zero refracted direction marks total internal reflection.
The shader advances from the water point along the refracted ray, limiting the initial depth-derived travel to 15 studs. It projects that point back into the retained scene image and bounds the UV displacement to 0.045. A depth test rejects a candidate that samples geometry in front of the water surface, falling back to the straight-through sample.
A small RGB sampling separation adds dispersion along the refraction offset. All sample positions remain inside the view rectangle. This is a reconstruction from the retained picture: an object absent from that picture cannot appear behind the surface through this refraction path.
Absorption uses channel-dependent exponential transmittance. The CPU derives
extinction from water density and linear color. The shader evaluates
exp(-extinction × path_length) for both view travel and a sun-travel estimate,
with bounded path lengths to keep the optical calculation finite.
Scattered body light uses water albedo, received irradiance, and the complement of view transmittance. Increasing optical depth therefore removes more of the background and adds more water-colored scattering. The result is not equivalent to applying a uniform transparent tint to the earlier scene image.
Below the surface, the through-surface lighting path uses environment light. When total internal reflection occurs, reflectance becomes 1. The water shader also applies the atmosphere integration to its final surface color.
Reflection fallback and the water ray search
Water starts with probe and environment reflection. It combines that result with refracted light using the environment BRDF and a base reflectance of 0.02. Direct sun and local-light specular contributions are added separately.
The water ray search uses at most 12 screen-space steps. Step distance starts at 0.9 studs and grows by 1.62 after each sample. The shader projects each candidate, compares it with captured scene depth, and bounds the accepted depth penetration by the preceding step interval.
A hit below the water surface is rejected as a reflection source. Accepted hits read the retained scene picture and fade near its border. The search fades with distance and viewing angle and runs only on the above-water side. A miss retains probe and environment reflection.
This search differs from the opaque reflection pass's linear candidates and bisection refinement. It runs within water shading and reads the water capture, so its quality and visibility limits cannot be inferred from opaque reflection settings alone.
Focused caustic lighting
The caustic pass produces a 512 × 512 floating-point texture over a 96-stud camera-centered window. Its window origin snaps to texel spacing so a small camera translation does not shift every sample's world position continuously. The pass runs when water is visible or the camera is underwater, the sun is above the required elevation, and water detail is at least 2.
For each source position, the compute shader derives a surface slope and refracts incoming sunlight toward a reference receiver depth of 34 studs. It evaluates neighboring landing positions to estimate the area of the refracted footprint. Intensity is the inverse of that area, bounded to prevent an arbitrarily bright singular focus.
Surface shading samples this focused-light texture where its world window covers the receiver. Outside the covered region, the shared lighting code uses an analytical pattern. The caustic amount also depends on water density, receiver depth, and light visibility. This produces a local focused pattern without tracing a separate photon path for every shaded floor pixel.
Foam and temporal motion
Shore foam uses reconstructed floor depth to locate a band near the waterline. Several noise scales break that band into surf and thinner seams. Wave height controls its width and strength. Crest foam adds another contribution only above the crest threshold, with its own noise breakup. The final foam mixes received-light color into the water rather than creating separate geometry.
On the single-sample path, water writes motion into the same attachment used by opaque geometry. Current and previous projections include camera and object movement. Both positions use the current wave displacement, deliberately excluding the wave's own time evolution from geometric motion.
This prevents water from retaining the motion of the opaque floor beneath it, while keeping the distinction between surface movement and changing ripple shading. Multisampled water omits that motion output, following the renderer's separate MSAA frame path.