Hey everyone!
I built a fun parametric boat simulation that demonstrates dynamic water masking: keeping water out of a moving boat in real-time.
Live Demo on CodePen →
The Challenge: How do you cut a hole in a water plane that follows a moving, bobbing, rotating boat? The water needs to stay outside the hull while the boat moves freely across the surface.
The Solution:
-
Parametric boat geometry with thick hull (outer + inner surfaces + rim edges)
-
Real-time alpha mask generation using convex hull of submerged vertices
-
Canvas 2D for mask rendering, applied as alphaMap to water plane
-
Boat dynamics: pitch on acceleration/deceleration, roll on turns, natural bobbing
Features:
-
WASD/Arrow keys for movement and steering
-
Basic boat physics with weight shift
-
Dynamic shadows that follow the boat
Known Issue: Minor edge aliasing at waterline due to convex hull approximation vs exact geometry cross-section.
Additionally, there is room for improvement performance-wise: reducing the mask canvas, throttling mask updates, smarter/selective sampling of boat vertices, etc.
Inspired by some of @PavelBoytchev‘s amazing work. Thank you, Pavel!
Feedback and suggestions welcome!
—————————
2025-11-19 UPDATE: I added these ripples and it looks decent.
—————————
2025-11-27 UPDATE #2: precise underwater cutout, vertex waves, multi-boat
NEW Demo on CodePen →
I obsessed over making the submerged hull cutout precise, not an approximation. The water now clips per‑fragment against live wave height using an underwater ortho camera and a mask render target, so only truly submerged pixels render. I also added vertex‑level surface motion and screen‑space ripples, and the multi‑boat setup gives it a playful, game‑like vibe.
Highlights:
-
Wave‑aware cutout: underwater camera renders silhouettes; water shader discards above surface. Instead of painting a mask on the CPU, I render boat silhouettes from an underwater orthographic camera and clip per-fragment in the shader against the live wave height. Only the truly submerged hull pixels survive. No approximations, no stale silhouettes.
-
Alive surface: added layered vertex waves, in addition to the ripple normals. Boat and water motion feel coherent and properly synced.
-
Game feel: Simple multi-boat selection with some very basic collision detection add “game” energy.
The Boat class can easily be modified for presets (Skiff, Canoe, Patrol, Inflatable) via hull params and dynamics. Hope you enjoy!
5 Likes
Very cool.
I’d love to see some waves/fake bouancy!
Edit: Wow I didn’t realize you had movement controls wired up.. that has quite nice motion actually!
Love it!
2 Likes
Are you blanking out parts of the material? Or are you adjusting vertices for the water plane?
Here is a little demo I created that might benefit from your solution.
1 Like
I’m blanking out parts of the water material using an alpha mask. The water is just a static plane; no vertex manipulation.
The boat “punches a hole” in the water as it moves around.
Checking out your demo. Nice setup! It looks like you’re dealing with a similar masking challenge. The alphaMap approach might indeed work with your vertex-displaced ocean, though it might be a tad more intricate to integrate with your existing Ocean.js setup. The key would be rendering the boat’s footprint to a canvas texture and applying it as an alphaMap to discard fragments under the hull. The vertex displacement would stay intact.
I’ll give this some thought as time allows and might post a follow-up if I work out a clean integration approach!
1 Like
@manthrax Thank you! Glad the motion feels right.
Your Three.js explorations are awesome. Love the variety, so many goodies packed in there.
Real wave interaction (boat riding actual wave geometry) would be the next level challenge. Would need proper buoyancy calculations based on hull displacement at the waterline… fun to think about for the future, for sure!
1 Like
Attila Schroeder, who created the current WebGPU version of my Ocean.js, has done work on buoyancy. He took one of my model boats and used a “voxellizer” to make it float properly. Here a link to the demo he posted online.
ADD (Aug 18)
Another approach might be to get the height of the vertices under the boat, compute the pitch and yaw of the underlying plane and use that to compute the pitch and yaw of the ship. For vertical motion (aka “heave”), you could keep some kind of rolling average of the above vertices and change the height based on the change in the rolling average.
That is similar to what I did with this ship where the ocean was constructed using only a few sine waves - so I cued the motion pitch, yaw and heave using only the dominant sine wave.
In a sailing ship you would take into account the direction of the wind to create additional yaw. (And you would probably want to animate the sails.) If the ship is turning, you might add or subtract some more yaw depending on whether the ship is one which leans outward or inward in a turn.
A smaller enhancement you consider adding to your program is to create a small foam outline in the direction of motion. That is something that is missing from my simulations and would enhance the feeling that the ship is plowing through the waves - even where the waves are relatively tame.
I think I could use your enhancement with my Ocean and GrdWtr modules, if I can determine the exact location of your mask within my gridmap. That is something I will have to do. Thanks for providing this option.
2 Likes
@phil_crowther excellent suggestions and ideas. This is why I love this forum so much!
Along the same lines with the foam idea, the boat might look great coupled with these ripples. I might create a codepen showcasing that a bit later this week, if I get a chance and if someone else doesn’t beat me to it.
1 Like
Looks nice. Somehow the ripples seem to be showing up on the inside of the boat. Reflections or something else?
@phil_crowther if you’re referring to the lines inside the boat, that is self-shadowing (the boat receives and casts shadow, including onto itself). Undesirable, for sure, but I didn’t have much time to think about solving this aspect. Is this what you are talking about?
Self-shadowing is okay - the seats cast shadows on the deck of the boat.
But what I am referring to is those other lines on the deck between the shadows. They are animated and sometimes distort the shadows. I thought the lines on the deck might have to do with the animated water since they are moving. But animated lines also appear on the sidewalls of the boat.
I am baffled (which seems to be my default state).
The benches are 2 separate meshes which cast shadows → looks ok, not an issue at all.
However, the rest of the boat itself is just a plane (separate 3rd mesh), shaped into the boat by programmatically modifying the vertices of a flat plane.
This is definitely self-shadowing and can be verified by commenting out this line boat.castShadow = true;
Does this help?
That sounds like a perfectly valid way to make a boat.
Commenting out the boat.castShadow makes the lines disappear. But the ripples are still there affecting what were nice clean shadows that the seats would cast on the boat.
Just to make sure we are seeing the same thing, here is a screenshot of the area between the seats:
The square rectangle shows an area where the ripples appear. They move left and right as the boat goes up and down. And they have distorted the clean shadow of the seat (to the right) on the deck.
While the your original mask seemed to block out all the water material, perhaps it is not blocking out all the effects of the animated water? Do you have the waves casting shadows?
@phil_crowther do you still experience the issue?
I fiddled with the shadow params and it looks good to my eye / on my monitor now.
I changed:
- shadow.bias,
- shadow.normalBias,
- shadow.mapSize.width and height,
- shadow.camera.zoom,
- camera.near,
- FRUSTUM_SIZE_DEFAULT,
- FRUSTUM_SIZE_ORBIT
If you find an even better config than the current values, please let me know so I can update the codepen to look as good as it is possible.
Thanks for your help!
1 Like
It is amazing to me that such little changes can have such a big effect. Shadow bias is one number that I am always struggling with since it can create all kinds of artifacts.
1 Like
2025-11-27 UPDATE #2: precise underwater cutout, vertex waves, multi-boat
NEW Demo on CodePen →
I obsessed over making the submerged hull cutout precise, not an approximation. The water now clips per‑fragment against live wave height using an underwater ortho camera and a mask render target, so only truly submerged pixels render. I also added vertex‑level surface motion and screen‑space ripples, and the multi‑boat setup gives it a playful, game‑like vibe.
Highlights:
-
Wave‑aware cutout: underwater camera renders silhouettes; water shader discards above surface. Instead of painting a mask on the CPU, I render boat silhouettes from an underwater orthographic camera and clip per-fragment in the shader against the live wave height. Only the truly submerged hull pixels survive. No approximations, no stale silhouettes.
-
Alive surface: added layered vertex waves, in addition to the ripple normals. Boat and water motion feel coherent and properly synced.
-
Game feel: Simple multi-boat selection with some very basic collision detection add “game” energy.
The Boat class can easily be modified for presets (Skiff, Canoe, Patrol, Inflatable) via hull params and dynamics. Hope you enjoy!
1 Like