Super Soldier Battle Dev Log
THREE.JS FLOATING OBJECTS ON WATER
ON SUPER SOLDIER BATTLE'S OCEAN
My ocean already has Gerstner waves. Now I want crates, barrels and rafts riding them, and the trick is running the water's math twice.
By Shane Brumback ·
Short answer: To make floating objects on water in Three.js, animate the water with a wave function (usually Gerstner waves) in the vertex shader, then run the exact same function in JavaScript at a few points under each object. The average height places the object and the height differences set its tilt, so crates, barrels and rafts bob with the waves.
Three.js floating objects on water are one of those small details that make a game world feel real. A crate drifting past the beach, a barrel rolling over a swell, a raft tipping on a wave. In this post I look at how the ocean in my browser shooter Super Soldier Battle works today, why nothing floats on it yet, and the technique I'm using to fix that: run the water's wave math on the GPU for the surface and again on the CPU for everything sitting on it.
If you just want a pretty reflective ocean, my older Three.js water shader tutorial with the Water and Sky addons covers that. This one is about what happens when things have to ride the water.
How Does the Water in Super Soldier Battle Work Today?
The game's water is a class called GerstnerWater. It starts from the Three.js Water addon (the one with real-time reflections and a scrolling normal map) and patches its vertex shader with onBeforeCompile so the surface actually moves. Here's what the code does right now:
| Setting | Value in the game |
|---|---|
| Water plane | one 10,000 x 10,000 plane, 256 x 256 segments |
| Height | y = -7, lowered from -5 so inland dips don't show the ocean through them |
| Waves | 3 Gerstner waves, directions 0, 30 and 60 degrees, wavelengths 100, 50 and 25 |
| Steepness | 0.012 per wave (it was 0.12, then 0.06, then 0.03) |
| Look | alpha 0.88 and distortion 3 to soften the shoreline |
| Toggle | water animation can be switched off in the F1 overlay to measure its FPS cost |
That steepness history tells the story. The whole map shares one water plane, so the little ponds and low spots inland use the same surface as the open sea. With real waves the puddles rolled like the ocean, so I flattened the waves step by step until the water was almost still.
Players can also wade. When the ground under you is below the waterline, the game flags you as in the water, holds your view at chest height, slows you down and blocks jumping until you reach land.
Why Doesn't Anything Float Yet?
Two reasons, both visible in the code.
First, the waves are nearly flat on purpose, so there's not much motion to ride.
Second, nothing reads the water height. The GerstnerWater class already has a getWaveInfo() method that computes the wave position and normal in JavaScript, the CPU twin of the shader. But no code in the game calls it. The wading check uses a fixed surface height instead. So the water moves on the GPU, and the gameplay side has no idea where the surface is at any moment.
What Are Gerstner Waves, in Plain Terms?
Gerstner waves move each vertex of the water in a small circle instead of just up and down. Points slide toward the crest as it rises, so peaks get sharp and troughs get wide, which is how real swells look. Stack three to six of them with different directions and wavelengths and you get a convincing sea.
The important part for floating objects: the waves are a plain math function of position and time. Anything that can run that function knows exactly where the surface is.
How Do You Make Objects Float on Three.js Water?
This is the approach I prototyped for the paired tutorial:
- Keep one wave list. Direction, steepness and wavelength live in one JavaScript array. The shader gets it as uniforms, and the CPU reads the same array.
- Share one clock. The same
timevalue drives the shader uniform and the CPU sampler every frame. If they drift, objects sink or hover. - Sample a few points per object. Four corners for a crate, two ends and two sides for a barrel, six points along a boat.
- Height from the average. The object's height is the average water height under its points, minus how deep it sits.
- Tilt from the differences. If the front points are higher than the back, the object pitches; left versus right gives roll.
- Smooth it. A small spring makes a heavy raft lag behind the water while a light crate snaps to it.
There's one catch with Gerstner waves. Because vertices also move sideways, the water point that ends up above a spot didn't start there. A few quick correction steps find the right starting point. In my tests four steps were enough to keep the error to about a tenth of a unit even in the stormiest setting.
Water Addon vs Custom Water Shader: Which Should You Use?
| Water addon (my older tutorial) | Game today | New prototype | |
|---|---|---|---|
| Surface shape | flat plane, normal map ripples | Gerstner waves, almost flat | Gerstner waves, adjustable sea state |
| Reflections | real reflection render | real reflection render | sky color by fresnel, no extra render |
| Lighting normals | from the normal map | from the normal map | computed from the waves |
| Floating objects | no | no (CPU function exists, unused) | yes, bob and tilt |
The reflection render target in the addon draws the scene a second time, which looks great but costs a whole extra render. The prototype drops it for a sky-colored fresnel and puts the budget into wave motion and floating objects.
What's Planned for the Game?
These are plans, not shipped features:
- Floating crates, barrels and boats on the coast, using the shared wave function.
- Real wave heights at sea without rolling puddles, by fading the waves out near the shore or giving ponds their own calm surface.
- Wading that follows the waves instead of a fixed surface height.
If you want the full working code, the Three.js Gerstner waves and buoyancy tutorial walks through it step by step, from the water shader to the buoyancy spring. For other effects from the same game, see my Three.js smoke effect and Three.js terrain LOD posts, or the bunkers and beaches dev log.
Want to see the current water in action? Play Super Soldier Battle and wade into the sea off the beach. And if you need a Three.js developer for water, effects or a full browser game, I'm available for freelance and contract work, so contact me.
Frequently Asked Questions
How do you make objects float on water in Three.js?
Run the same wave function on the CPU that the water shader runs on the GPU. Sample the water height at a few points under each object, set the object's height from the average, and its pitch and roll from the differences. Smooth the result with a spring so heavy objects lag a little. The shader and the sampler must share one wave list.
What are Gerstner waves?
Gerstner waves are a classic way to fake ocean waves. Each wave moves a vertex in a small circle, up and down and also sideways toward the crest, so peaks get sharp and troughs get wide like real water. You add three to six of them with different directions and wavelengths to get a natural-looking sea.
Can the Three.js Water addon do buoyancy?
Not on its own. The Water addon gives you reflections and a scrolling normal map, but the surface itself stays flat, so there is no height to float things on. You either displace its vertices with your own waves, as Super Soldier Battle does, or write a custom water shader.
Why do floating objects sink into or hover above my waves?
Usually the CPU and GPU math don't match. A different time value, a missing wave, a flipped direction or a different steepness all put the object at the wrong height. Gerstner waves also move vertices sideways, so you need to correct for that offset before reading the height, or objects sit slightly off on steep seas.
Do floating objects slow down a Three.js game?
Hardly. Each sample is a few sines and cosines per wave. In my demo, six objects with 26 sample points cost a tiny fraction of a millisecond per frame on the CPU. The water mesh itself is the real cost.
Does Super Soldier Battle have floating objects?
Not yet. The game's ocean already uses Gerstner waves on the Water addon, and the water class even has a CPU wave function, but nothing calls it today, and the waves are kept almost flat. Floating crates, barrels and boats are planned.
Play Super Soldier Battle
Play Super Soldier Battle free in your browser and wade into the sea off the beach to see the Gerstner ocean up close. Then read the Gerstner waves and buoyancy tutorial to build the floating prototype yourself.
Building a Three.js game and need water, effects or performance help? I'm available for freelance and contract work: contact me.
Play Super Soldier Battle free →