A real-time water simulation with Three.js, featuring raytraced reflections, refractions, caustics, and interactive objects.
This project is a complete port of Evan Wallace's WebGL Water demo to Three.js, with significant enhancements including support for Three.js geometries, customizable pool shapes, and GLTF model loading.
threejs-water.mp4
https://jeantimex.github.io/threejs-water/
- Original WebGL Water by Evan Wallace
- Three.js port by Yong Su (jeantimex)
-
Three.js Geometry Support - Use any Three.js geometry (
SphereGeometry,BoxGeometry,TorusKnotGeometry, etc.) to create interactive objects with water displacement, caustic shadows, and raytraced reflections/refractions. -
GLTF Model Support - Load external 3D models with custom shader materials integrated into the water simulation.
-
Customizable Pool Shapes - Rectangular and rounded box pools with configurable dimensions.
-
Physics Simulation - Gravity, buoyancy, and density-based floating.
-
Real-time Caustics - Dynamic caustic patterns using the differential area method.
-
Raytraced Water Optics - Fresnel-based reflection/refraction blending.
The TorusKnotObject demonstrates how to integrate any Three.js geometry into the water simulation.
Geometry Creation - Uses THREE.TorusKnotGeometry directly:
const geometry = new THREE.TorusKnotGeometry(0.17, 0.045, 64, 8)Water Displacement - Complex shapes use CompoundSphereWaterDisplacement, which approximates the geometry with multiple overlapping spheres. The torus knot samples 24 points along its parametric curve, placing displacement spheres at each point.
Ray Intersection - For reflections/refractions, the water shaders use a bounding sphere approximation rather than exact torus knot intersection (which would be expensive). The optics descriptor specifies kind: 'mesh' with a boundingRadius.
Hit Testing - Mouse interaction uses Three.js's built-in Raycaster against the actual mesh geometry for accurate picking.
Extending - To add new geometries, implement SimulationObject interface, choose a displacement strategy, define an optics descriptor, create render shaders, and register in CreateSimulationObjects.ts.
The rounded box pool extends the original rectangular pool with configurable corner radius.
Geometry - Uses a custom RoundedBoxGeometry with adjustable width, depth, length, and corner radius.
Shaders - The rounded pool requires its own shader set:
RoundedBox.vert/frag- Pool wall rendering with triplanar UV mapping around curvesRoundedBoxCaustics.vert/frag- Caustic projection onto rounded surfacesRoundedBoxWaterAbove/Below.frag- Ray-pool intersection for reflections/refractions
SDF-based Intersection - The water shaders use a Signed Distance Function (SDF) to find ray intersections with the rounded box, enabling accurate reflections off curved corners.
Extending - To add new pool shapes, create the corresponding shader set (walls, caustics, water surface), implement ray-pool intersection in GLSL, and update Renderer.ts to switch between pool passes.
Wave Simulation - 2D wave equation solved on GPU using discrete Laplacian with ping-pong buffers.
Caustics - Differential area method: project light rays through water, brightness = originalArea / projectedArea.
Fresnel Effect - Schlick's approximation for reflection/refraction blending based on view angle.
Ray Intersection - Each object type needs GLSL intersection code (quadratic formula for spheres, slab method for boxes).
- Coordinate Systems - Water mesh is XY, rendered as XZ. Use
.xzyswizzle. - Shader Uniforms - All shaders receiving object uniforms need matching declarations.
- Displacement Scale - Too strong causes instability. Tune
displacementScale. - Frustum Culling - Set
mesh.frustumCulled = falsefor off-screen render targets. - iOS Safari WebGL Precision - Mobile Safari can show striped water, opaque sky-like surface artifacts, or blown-out object refractions if the simulation relies on unsupported float render target behavior. The water heightmap is explicitly cleared, uses nearest filtering, falls back to half-float render targets when needed, and clamps shader normal reconstruction to avoid NaNs.
MIT License - See LICENSE for details.
Original work Copyright (c) 2011 Evan Wallace Modified work Copyright (c) 2026 Yong Su