TL;DR:
Extended Mirko Kunze’s CheapWater to support player movement while keeping ripples in sync with world positions. Ripples are positioned in world-space and rendered to a separate normal scene, which the water shader samples.
The Original CheapWater
CheapWater is brilliant for its simplicity and performance:
-
Ripples render straight into screen space.
-
Water shader samples using
gl_FragCoord / resolution. -
Extremely efficient, looks great as long as the camera doesn’t move in world space.
CheapWater keeps ripples aligned correctly with the world as long as the normal map and main scene are rendered with the same camera, even if the camera moves or rotates.
The Adaptation
I integrated CheapWater with a moving player and camera setup suitable for games:
-
World-Space Ripple Placement – Ripples remain anchored in world coordinates.
-
Separate Normal Scene – Rendered to a texture that the water shader samples.
-
Player / Camera Movement – Main camera moves freely and follows the player.
-
Ripple texture behaves like a top-down snapshot of world-space ripples.
Result: Efficient, screen-sized ripple textures that remain anchored to world positions, fully compatible with a moving player camera.
Full implementation available here: Screen-Space Water Ripples with World-Space Player Movement in Three.js
Performance Considerations
This approach uses two render passes like the original:
-
Render ripple normal map to off-screen texture.
-
Render main scene using ripple texture.
Why it’s fast:
-
Simple geometry: just quads with basic materials.
-
No lighting: additive/subtractive blending only.
-
Small texture: screen resolution, not world-scale.
-
Minimal overhead: typically <0.1ms even with hundreds of ripples.
Demo Features
-
WASD / Arrow key movement on XZ plane.
-
Camera follows player smoothly with OrbitControls.
-
Ripples generated by player movement and falling drops.
-
Animated demo ball for visual comparison.
-
Real-time performance stats.
Future Improvement
- Use instancing for improved performance.
Discussion
Has anyone else tackled screen-space to world-space ripple mapping in Three.js or similar engines? Would love to hear alternative approaches or performance tips.
Demo: Screen-Space Water Ripples with World-Space Player Movement in Three.js
License: MIT
Hope this helps someone out there!