diff --git a/WATER.md b/WATER.md index ce61136c..fdf94e02 100644 --- a/WATER.md +++ b/WATER.md @@ -43,7 +43,7 @@ fork. Do not remove any of them. |---|---|---| | Quality tiers | `Classic` = the reference water, `Enhanced`, `High` (= Enhanced + scenery reflections) | Video options → Water Quality, cvar `waterQuality`, env `WOW_WATER=0\|1\|2` | | Procedural waves | multi-band analytic waves with exact normals; the long swell moves ocean vertices | tier | -| Open-sea whitecaps | High adds Gerstner crest gathering and fold-thresholded foam; calm canals remain clear | tier | +| Open-sea whitecaps | High adds Gerstner crest gathering and fold-thresholded foam; calm canals remain clear. The fold mask adds the next two wave bands, a slow domain-warped gust field moves the break threshold and calms patches, and ~6 yd segments break each crest into short runs, so the caps do not form a lattice (`whitecap_fold`, `enhanced_water.wgsl`) | tier | | Near mesh refinement | High builds a transient 4x liquid lattice within 64 yd, with an 80 yd release ring | tier | | Ocean / inland profiles | the sea and lakes/rivers have their own colour, energy and reflectivity | tier | | WMO pools | exterior canals use a calm drifting profile with outdoor glints; interiors use their authored MOMT colour and room-fog reflection | tier | diff --git a/crates/benilla-assets/src/shaders/enhanced_water.wgsl b/crates/benilla-assets/src/shaders/enhanced_water.wgsl index a8906e63..5a83f818 100644 --- a/crates/benilla-assets/src/shaders/enhanced_water.wgsl +++ b/crates/benilla-assets/src/shaders/enhanced_water.wgsl @@ -282,6 +282,24 @@ fn water_gerstner(p: vec2, t: f32, shore: f32) -> vec4 { return vec4(displacement, fold); } +// MONKEY (visualfix): the whitecap fold. The two long bands alone cross in a regular diamond +// lattice, so every crest broke at the same spots; the next two bands (non-integer length and +// direction ratios to the first two) add their compression at a reduced weight. Mask only: the +// displaced geometry still moves on the two long bands. +fn whitecap_fold(p: vec2, t: f32, shore: f32) -> f32 { + var fold = water_gerstner(p, t, shore).w; + let energy = clamp(water.mode.y, 0.0, 1.0); + let tempo = mix(0.4, 1.0, sqrt(energy)); + for (var i = 2u; i < 4u; i += 1u) { + let wave = WATER_WAVES[i]; + let direction = vec2(cos(wave.x), sin(wave.x)); + let k = 6.2831853 / wave.y; + let phase = k * (dot(direction, p) - sqrt(10.72 / k) * tempo * t) + wave.w; + fold += 0.55 * WATER_GERSTNER_CHOP * wave.z * energy * shore * k * sin(phase); + } + return max(fold, 0.0); +} + // MONKEY (water): seam for the later wet-weather lane. Its result is a height-gradient // perturbation in world XZ; zero preserves today's image until that lane supplies rain data. // MONKEY (wet): rain rings. Each layer is a grid of cells, one drop per cell at a hashed spot and @@ -634,7 +652,7 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec let wave = water_waves(p, t, length(eye_pos), footprint, shore, false); var open_sea_fold = 0.0; if water.mode.x > 1.5 && ocean_mesh { - open_sea_fold = water_gerstner(p, t, shore).w; + open_sea_fold = whitecap_fold(p, t, shore); } // One surface gradient: the procedural bands and the shore break. let surf_grad = wave.yz + shore_grad + rain_ripple_normal(in.world_position.xyz, footprint, @@ -840,7 +858,17 @@ fn enhanced_water(in: WaterFragment, shallow: vec4, deep: vec4) -> vec // MONKEY (water): WarcraftXL's white belongs to a connected horizontal fold, not to every // steep normal. Threshold jitter stops identical crests drawing identical white contours. let fold_jitter = (noise - 0.5) * 0.04; - let breaking = smoothstep(0.13, 0.25, open_sea_fold + fold_jitter); + // MONKEY (visualfix): a slow, domain-warped gust field (tens of yards, unrelated to the wave + // lengths) moves the break threshold and calms whole patches, so the caps do not repeat. + let gust_warp = vec2(foam_noise(p * 0.013 + vec2(t * 0.004, 7.3)), + foam_noise(p * 0.013 + vec2(-4.1, t * 0.003))) - vec2(0.5); + let gust = foam_noise(p * 0.037 + gust_warp * 2.3 + t * vec2(0.006, -0.005)); + let cap_lo = 0.13 + (0.5 - gust) * 0.16; + // A crest breaks in short runs, not along its whole length: ~6 yd warped segments. + let segment = smoothstep(0.42, 0.62, + foam_noise(p * 0.16 + gust_warp * 1.7 + t * vec2(0.011, 0.007))); + let breaking = smoothstep(cap_lo, cap_lo + 0.14, open_sea_fold + fold_jitter) + * smoothstep(0.25, 0.6, gust) * segment; let whitecap_alpha = FOAM_WHITECAP * breaking * mix(0.55, 1.0, breakup) * smoothstep(0.55, 1.0, energy); // The cached derivatives from the top of the function — same expression as before, one tap.