MONKEY (visualfix): whitecaps break in scattered short runs, not a lattice

The fold mask came from the two long wave bands only, which cross in a regular diamond lattice:
every crest broke into the same elongated patch. The mask adds the next two bands' compression,
a slow domain-warped gust field moves the break threshold and calms patches, and ~6 yd warped
segments break each crest into short runs. Geometry is unchanged; crest foam stays on steep waves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pkuzic
2026-09-26 06:00:26 +01:00
co-authored by Claude Opus 5.5
parent 9c2eadd98d
commit d26898a3e8
2 changed files with 31 additions and 3 deletions
+1 -1
View File
@@ -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 |
@@ -282,6 +282,24 @@ fn water_gerstner(p: vec2<f32>, t: f32, shore: f32) -> vec4<f32> {
return vec4<f32>(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<f32>, 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<f32>(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<f32>, deep: vec4<f32>) -> 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<f32>, deep: vec4<f32>) -> 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<f32>(foam_noise(p * 0.013 + vec2<f32>(t * 0.004, 7.3)),
foam_noise(p * 0.013 + vec2<f32>(-4.1, t * 0.003))) - vec2<f32>(0.5);
let gust = foam_noise(p * 0.037 + gust_warp * 2.3 + t * vec2<f32>(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<f32>(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.