Skip to content

waterFragment

const waterFragment: “#include "lygia/generative/snoise.glsl"\n\nconst vec3 shallowTint = vec3(0.55, 0.92, 0.86);\nconst vec3 sky = vec3(0.68, 0.84, 0.96);\nconst vec3 foamColour = vec3(0.94, 0.98, 1.0);\nconst float shoreWidth = 0.14;\nconst float foamWidth = 0.035;\n\nvoid main() {\n // The flat normal, from the screen-space slope of the position. It comes\n // before the discard below, since a slope is undefined once neighbouring\n // pixels have left.\n vec3 normal = normalize(cross(dFdx(vWorldPosition), dFdy(vWorldPosition)));\n\n // The shore: how far this point lies inside the plane’s edge, 0 at the\n // edge and 0.5 at the centre, or inside the circle when round.\n vec2 edge = min(vUv, 1.0 - vUv);\n float inland = mix(min(edge.x, edge.y), 0.5 - length(vUv - 0.5), uRound);\n if (inland < 0.0) discard;\n\n // Waves: two layers of noise scrolling across each other, on the world’s\n // ground so a longer river keeps the same wave, -1 to 1.\n vec2 ground = vWorldPosition.xz / max(uWaveSize, 0.01);\n float drift = uTime * uWaveSpeed;\n float waves = 0.6 * snoise(ground + vec2(0.30, 0.20) * drift)\n + 0.4 * snoise(ground * 2.3 - vec2(0.25, -0.35) * drift);\n\n // Depth: shallow at the shore and deep inland, the line moved by the waves.\n float shore = inland + waves * 0.012;\n float depth = smoothstep(0.0, shoreWidth, shore);\n\n // Colour: the deep colour in sRGB, as the constants are written, lightened\n // toward the shallow tint near the shore, with brighter crests.\n vec3 deep = sRGBTransferOETF(vec4(uColor, 1.0)).rgb;\n vec3 colour = mix(mix(deep, shallowTint, 0.55), deep, depth);\n colour += smoothstep(0.5, 0.95, waves) * 0.07;\n\n // Foam: a bright line where the water is shallowest, broken by noise.\n float breakup = 0.5 + 0.5 * snoise(ground * 4.0 - vec2(0.4, 0.1) * drift);\n float foam = (1.0 - smoothstep(0.0, foamWidth, shore)) * smoothstep(0.25, 0.6, breakup);\n foam = max(foam, 1.0 - smoothstep(0.0, 0.012, shore));\n\n // Sky: a fake reflection, stronger where the view grazes the surface.\n vec3 view = normalize(cameraPosition - vWorldPosition);\n float fresnel = pow(1.0 - abs(dot(view, normal)), 3.0);\n fresnel = clamp(fresnel + waves * 0.08, 0.0, 1.0);\n colour = mix(colour, sky, fresnel * 0.7);\n\n colour = mix(colour, foamColour, foam);\n gl_FragColor = vec4(colour, 1.0);\n}\n”

Defined in: packages/engine/src/render/water.ts:6

Stylized water, for a Shader: uColor is the deep water’s colour, uWaveSize the waves’ size in metres (1 is a pond’s ripples), uWaveSpeed how fast they drift (0 holds them) and uRound 1 to keep only the circle inside the plane, a pond, or 0 for the whole plane. One pass: no depth texture and no second render, so a phone can draw it.