JSFiddle - React, Tailwind, and code Playground
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ocean Surface Simulation — Gerstner Waves 3D</title>
<meta name="description"
content="Real-time 3D ocean surface simulation using Gerstner waves, Fresnel reflection, subsurface scattering, and procedural foam rendering.">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
background: #000;
font-family: 'Helvetica Neue', Arial, sans-serif;
}
canvas {
display: block;
}
#overlay {
position: absolute;
bottom: 24px;
left: 50%;
transform: translateX(-50%);
color: rgba(255, 255, 255, 0.35);
font-size: 11px;
letter-spacing: 3px;
text-transform: uppercase;
pointer-events: none;
user-select: none;
}
#fps {
position: absolute;
top: 16px;
right: 20px;
color: rgba(255, 255, 255, 0.3);
font-size: 11px;
font-family: monospace;
pointer-events: none;
}
</style>
</head>
<body>
<div id="overlay">Ocean Surface Simulation</div>
<div id="fps"></div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
</body>
</html>
JavaScript
// ═══════════════════════════════════════════════════════════════
// Ocean Surface Simulation — GLSL Shaders
// Gerstner Waves + PBR Ocean Rendering
// ═══════════════════════════════════════════════════════════════
const oceanVertexShader = /* glsl */ `
precision highp float;
uniform float uTime;
uniform vec4 uWaves[12];
varying vec3 vWorldPosition;
varying vec3 vWorldNormal;
varying float vHeight;
varying float vFoamFactor;
varying vec2 vUv;
#define PI 3.14159265359
#define GRAVITY 9.81
vec3 gerstnerWave(vec4 wave, vec3 p, inout vec3 tangent, inout vec3 binormal) {
float steepness = wave.z;
float wavelength = wave.w;
float k = 2.0 * PI / wavelength;
float c = sqrt(GRAVITY / k);
vec2 d = normalize(wave.xy);
float f = k * (dot(d, p.xz) - c * uTime);
float a = steepness / k;
tangent += vec3(
-d.x * d.x * steepness * sin(f),
d.x * steepness * cos(f),
-d.x * d.y * steepness * sin(f)
);
binormal += vec3(
-d.x * d.y * steepness * sin(f),
d.y * steepness * cos(f),
-d.y * d.y * steepness * sin(f)
);
return vec3(
d.x * a * cos(f),
a * sin(f),
d.y * a * cos(f)
);
}
// ── Hash-based value noise for vertex displacement chaos ──
float hash31(vec3 p) {
p = fract(p * vec3(0.1031, 0.1030, 0.0973));
p += dot(p, p.yxz + 33.33);
return fract((p.x + p.y) * p.z);
}
float vnoise(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
f = f * f * (3.0 - 2.0 * f);
return mix(
mix(mix(hash31(i), hash31(i+vec3(1,0,0)), f.x),
mix(hash31(i+vec3(0,1,0)), hash31(i+vec3(1,1,0)), f.x), f.y),
mix(mix(hash31(i+vec3(0,0,1)), hash31(i+vec3(1,0,1)), f.x),
mix(hash31(i+vec3(0,1,1)), hash31(i+vec3(1,1,1)), f.x), f.y), f.z);
}
float vfbm(vec3 p) {
float f = 0.0;
f += 0.500 * vnoise(p); p *= 2.03;
f += 0.250 * vnoise(p); p *=...