Loading...
Loading...
Build an ultra-realistic open ocean in Three.js with a deep-water Gerstner spectrum shaded per pixel from analytic derivatives, each wave faded at its own pixel footprint, plus Fresnel sky-cube and planar reflections, subsurface light through the crests, HDR sun glitter, Jacobian lace foam, a Kelvin wake, hull foam, buoyancy queries and aerial perspective into the sky's horizon haze. Use for seas, oceans, sunset or daylight water, sun glitter paths, ship or boat scenes, wakes, coastal flyovers and any 3D scene with a moving camera and a water horizon. Includes the approved water from Pirate Ship Sunset as a reusable module and demo.
npx skill4agent add mengto/skills 3d-ultra-realistic-watershaders-cursor-ripplesTHREEdemo/build.mjsimport { createWaves, bakeOceanDetail, createOcean, createWake, createPlanarReflection } from './ocean.mjs';
const waves = createWaves({ direction: 1.22 }); // wind heading, radians
const ocean = createOcean(THREE, {
waves,
detailTexture: bakeOceanDetail(THREE, renderer), // baked once, 512²
skyCube, // cube rendered from YOUR sky shader
hazeGLSL: SKY_GLSL + 'vec3 oceanHaze(vec3 d){ return hazeColor(d); }',
hazeUniforms: skyUniforms, // may include uSunDir
wake: createWake(THREE, renderer, { shipLength: 34 }), // optional
hull: { halfWidths, zMin: -18, zMax: 18, bowZ }, // optional, ship-local metres
});
scene.add(ocean.mesh);
const reflection = createPlanarReflection(THREE, renderer, { layer: 1 });
ocean.setReflection(reflection);
// every frame, in this order
waves.update(dt); // CPU phases, before any height() query
ship.y = waves.height(ship.x, ship.z); // buoyancy uses the same spectrum
ocean.update({ time, camera, ship: { x, z, fx, fz } });
wake.update(x, z, fx, fz, speed * dt);
reflection.render(scene, camera); // before the main pass
renderer.render(scene, camera); // into a HalfFloat target, then bloom + ACES| layer | what it does | constants |
|---|---|---|
| Spectrum | 8 Gerstner waves, deep-water dispersion | L 78 → 3.1 m (×0.63 per step), A 0.8 → 0.02 m, headings 0, +23, −17, +38, −31, +12, −49, +64° off the wind, Q 0.17 → 0.03 (Σ 0.77) |
| Grid | exponential rings centred under the camera | |
| Footprint fade | per wave, in both shaders | |
| Normal | per pixel, | Jacobian |
| Micro detail | baked 512² tile: 48 sines with integer wave vectors, amplitude ∝ |k|^−1.35 | two octaves at 19 m and 7.3 m (the second rotated 90°), strength |
| Reflection | Schlick with water F0 = 0.02; 256² sky cube re-rendered every 0.25 s; half-res planar pass for the ship and islands | planar UV nudged by |
| Body | deep colour plus sunlit subsurface through the wave crests | deep (0.006, 0.03, 0.048), scatter (0.035, 0.3, 0.27) linear; scatter × toward-sun² × crest height × (0.35 + 4·(1 − N.y)) |
| Glitter | HDR specular, left for the bloom to catch | |
| Foam | thresholds a Worley lattice by a mask, so the foam stays lacy | crest |
| Wake | 512² half-float field over 380 m, trailing the ship | Kelvin arm offset |
| Haze | fogs into the sky's own horizon colour | |
setSeaState()R.y = abs(R.y) + 0.002/9/3.3/11/4.1ω·tsin()height(x, z)disp(x, z).yrenderer.toneMapping = NoToneMappingshadowMap.autoUpdate = falsedtwaves.offX/offZ__OD.hold()setAnimationLoopdtheight()