vidar.games

Stylized water layers

2026-11-01 · Discuss on X

This stylized water is 7 colours added together.
Left: base depth colour only.
Right: reflection, subsurface, foam, sun specular, sparkles and edge foam come in one at a time.

The problem

Stylized water often gets built as one big shader where every effect is tangled with the others. Change the foam and the colour shifts. This shader keeps each effect as a separate colour and adds them at the end:

color = base + reflection + sss + foam + sunSpecular + sparkle + edgeFoam

The video adds them one at a time.

How it works

Base: the scene behind the water, tinted from shallow to deep by exp(-0.5 × optical depth), then toward a far colour by distance.

Reflection: a cubemap scaled by a fresnel term, (1 + dot(n, -v))^5.

Subsurface: a colour scaled by wave height and by how much you look toward the sun.

Foam: a scrolling foam texture, its UVs pushed by the wave normal.

Sun specular: round(saturate(pow(dot(R, L), 3500))). The round makes it a hard-edged highlight, not a soft one.

Sparkles: 2 scrolling sparkle normal maps, ceil(pow(mask, 20000)). Single pixels flicker on and off.

Edge foam: round(exp(-depth / 0.25)). Anywhere shallower than about 0.17 m becomes a solid white band.

What I hit

The wave normal comes from cross(binormal, tangent), which points down. So the repo's (1 + dot(n, -v))^5 is Schlick's (1 - cos)^5 in disguise. I "fixed" it to 1 - dot on my first port and the whole lake turned white, fresnel 1 everywhere. I reverted to the repo form.

The same edge-foam rule turns a rock just under the surface into a solid white patch (right edge, around 8 s). That is the formula, not a glitch.

My build

three.js 0.170. Unity's GrabPass and depth texture became an opaque pass into a render target with a depth texture; the water draws after it with a manual depth test. The reflection probe is a CubeCamera captured once. Wave, sparkle and foam textures are my own. Values come from the repo's options figure.

Trade-offs

When to use it

Stylized lakes, rivers and coasts. Swap the cubemap for screen-space reflection if things move near the water.

New breakdowns go out on X first: @vidar_0208. If this one helped, you can buy me a coffee.

Source and credits

Technique: Stylized Water for Unity by Daniel Shervheim. BSD 3-Clause license, Copyright (c) 2019, Daniel Shervheim.
Repo: github.com/danielshervheim/unity-stylized-water

Code: I ported Stylized Water.shader and WaterUtilities.cginc to GLSL in three.js, with the parameter values from the repo's options panel. The normal maps, foam texture, island and jetty are my own; no textures, models or screenshots from the repo.

Topic inspired by @yolwoocle_'s Godot water shader breakdown. This build is three.js, not Godot.