Unlit water shader with foam

I’d like to share a water shader that might be useful when rendering scenes with NPR-techniques. The great thing about this code is the ability to detect obstacles in the water and then render a nice foam around them. I’ve seen this effect in Unity some time ago and I though it would be useful to have this in three.js.

The shader should work with perspective and orthographic cameras, it supports output encoding, inline tonemapping and fog and is much faster than the (more realistic) water shaders of the official repo.

Live demo: https://gojcn.csb.app/

Editable code at Codesandbox.


Looks very nice, the illusion is broken when you move the camera around, you really need a depth buffer perpendicular to water plane to figure out where the edges are, I have a much less good looking shader in Might is Right that simulates the shore-line (foam, whatever) - I use the inverse of terrain height-map to figure out where the shore is.

I get that that’s extra processing in general, so maybe not suitable for everyone.

Looks very lovely :slight_smile:

Really like it! Feel there are many more uses for it other than just water, after tweaking your settings a bit I could see it being useful for other effects like heat waves coming off of an object.

Very cool stuff, really enjoy the cartoon look too!

1 Like

This is really nice, but I don’t think your initial settings do it justice. This looks much more like water to me.

1 Like

Of course it’s a matter of taste. I’ve just implemented the style of the Untiy showcase where the foam is white and only visible at the intersection points:


Also a nice reference for water in cel shading:


Updated the water effect with the waterfall from the article:

Live demo: https://x4fl4.csb.app/

Editable code at Codesandbox .

The particle effect on the waterfall’s bottom and a bloom pass are still missing.


Next level achieved! The waterfall has now a nice particle effect based on THREE.InstancedMesh and MeshBasicMaterial enhanced with a simple dissolve shader.

Live demo: https://7rkse.csb.app/

Editable code at Codesandbox.

Um, a generic particle system based on THREE.InstancedMesh could be usable for much more interesting effects :thinking:.


Looks sweet! :grin:
Will definitely try it out with a bloom pass when I have some time.

Arent sprite billboards way more popular as particles? I feel like most p systems make great uses of alpha textures.
It obviously depends on the case, but what do you think? What are the adventages of using THREE.InstancedMesh for particles? Or rather which effects would be better suited for it, instead of billboards?

A typical particle system usually renders points similar to the following example:


The waterfall’s particle system uses spheres (THREE.SphereBufferGeometry) for its foam. So a geometry rendered with gl.TRIANGLES instead of gl.POINTS. You can’t achieve the same volumetric effect with a 2D primitive. Check out the volumetric particles here:

It would be obviously bad to render each particle with a single draw call. That’s not an issue when rendering points but when rendering meshes. Using instanced rendering solves this issue.


I guess the 3D shape is the obvious point (pun unintended :cowboy_hat_face:). I was just trying to come up with different uses for gl.TRIANGLES type particles and Im failing miserably :thinking:

My job has twitter blocked :sweat_smile: but I’ll make sure to check it out later. I’ll try to look up some more volumetric effects as well.
Thanks for the resources! :+1:

1 Like

Wow this is progressing! Looks awesome!

1 Like

@Mugen87 for webgl2 the example could use THREE.DepthTexture() too right?
renderer.extensions.get(“WEBGL_depth_texture”) returns false on webgl2

Yes, you can always use depth textures with WebGL 2. The extension test will return false since WEBGL_depth_texture is only available with WebGL 1.

1 Like

thx this helps a lot,
still have some problems with the DepthMap if objects are moving

the idea was only use the Terrain object3d for the target Render pass like
const terrain = scene.getObjectByName(“Terrain”)
renderer.setRenderTarget( this.target );
renderer.render( terrain, camera );
renderer.setRenderTarget( null );

this works, but slow because i cannot set the overrideMaterial ?

any suggestions are welcome

looks like found the solution

create a new Scene() and add the object3D cloned
terrain = new THREE.Scene().add(scene.getObjectByName(“Terrain”).clone())