Rendering a liquid metallic surface is a textbook shader-programming case: raymarching, normals from derivatives, specular reflection, iridescent palette. These techniques are part of Apple's and Pixar's vocabulary — yet they stay rare on the standard web because they require diving into GLSL. This article covers the Liquid Metal Shader shipped on June 3, 2026 on Effect.Labs: 200 lines of vanilla JavaScript and GLSL, no Three.js, no bundler, 60fps on Mac M1 and iPhone 12+, with a CSS fallback.
How it works
The effect combines three ingredients:
1. A noise function (Perlin / Simplex)
2D simplex noise yields a continuous value between -1 and 1 per coordinate. Stacked over 4 octaves (fractal Brownian motion, fbm), it produces a realistic surface with detail at multiple scales.
2. Normal from derivatives
For a surface to react to light, you need its local orientation. Using finite differences, you sample height at (x+ε, y) and (x, y+ε), compute the gradient, and derive the normal.
3. The Phong lighting model
With the normal known, apply the classic model: diffuse (lambertian) + specular highlight (exponent 32 for polished metal), plus an iridescence layer driven by surface height.
The minimal code
The core of the fragment shader, boiled down:
precision highp float;
uniform vec2 u_res;
uniform float u_time;
float snoise(vec2 v) { /* standard simplex noise, MIT */ }
float fbm(vec2 p) {
float v = 0.0, a = 0.5;
for (int i = 0; i < 4; i++) { v += a * snoise(p); p *= 2.0; a *= 0.5; }
return v;
}
void main() {
vec2 uv = (gl_FragCoord.xy - 0.5*u_res) / min(u_res.x, u_res.y);
float t = u_time * 0.3;
float h = fbm(uv * 2.0 + vec2(t, t*0.7)) * 0.6;
float eps = 0.01;
float hx = fbm((uv + vec2(eps,0.)) * 2.0 + vec2(t, t*0.7)) * 0.6;
float hy = fbm((uv + vec2(0.,eps)) * 2.0 + vec2(t, t*0.7)) * 0.6;
vec3 normal = normalize(vec3((h-hx)/eps, (h-hy)/eps, 1.0));
vec3 lightDir = normalize(vec3(0.7, 0.7, 0.6));
float diffuse = max(dot(normal, lightDir), 0.0);
float spec = pow(max(reflect(-lightDir, normal).z, 0.0), 32.0);
vec3 base = mix(vec3(0.1), vec3(0.85), diffuse);
gl_FragColor = vec4(base + vec3(spec * 1.2), 1.0);
}
The full code (dynamic resolution, IntersectionObserver for off-screen pause, prefers-reduced-motion, CSS fallback) is available directly on the Atmosphere catalog page — this effect is free.
Customize: color, speed, distortion
Four parameters drive the effect through uniforms:
| Parameter | Range | Effect |
|---|---|---|
u_speed | 0.1 → 3.0 | Ripple speed |
u_distortion | 0.1 → 1.5 | Wave amplitude |
u_light_angle | 0 → 360° | Light direction |
| Palette | silver / gold / iridescent | Metal color |
On Effect.Labs these are exposed in the built-in customizer: adjust the sliders, copy the ready-to-use code with your values.
Performance and compatibility
WebGL 1.0 is supported by 99.2% of browsers in 2026 (caniuse.com). With no GPU context, the code hides the canvas and falls back to a CSS gradient. On iPhone 12 and recent Android, it runs at a stable 60fps. To optimize on modest devices:
- Drop
fbmoctaves from 4 to 2-3 (~30% gain, minimal detail loss) - Cap
devicePixelRatioto 1 (~50% gain, slight pixelation) - Pause via
IntersectionObserveroff-viewport (already active)
The code honors prefers-reduced-motion: animation disabled and a static gradient shown if the user enabled reduced motion.
What ChatGPT and v0 don't do
This article opens Effect.Labs' June 2026 sprint: "5 effects ChatGPT and v0 can't write." Why does this shader resist auto-generation?
- Float precision: AI versions often copy an approximate simplex noise (octave-boundary discontinuities). Here, the correct Ashima Arts (MIT) implementation.
- devicePixelRatio: almost never handled → blurry on Retina. Our code computes the real resolution.
- Shader error handling: compilation can fail silently. We log
getShaderInfoLogand fall back. - Off-screen pause:
requestAnimationFramekeeps running while invisible. OurIntersectionObserverstops the work in the background. - prefers-reduced-motion: an accessibility rule generators routinely forget.
Not guesswork: 5 issues we hit testing 12 ChatGPT-4.5 and v0.dev prompts — none of the results covered them all.
FAQ
Can I use it with React, Vue or Svelte?
Yes: vanilla JS, framework-agnostic. In React, wrap the logic in a useEffect with cleanup; in Svelte, onMount/onDestroy. No npm dependency.
Why a shader instead of a 2D canvas?
2D canvas computes each pixel on the CPU (ceiling ~50,000 animated pixels at 60fps). A WebGL shader runs in parallel on the GPU (millions of pixels per frame): raymarching, fluid simulation, post-processing become possible.
The full, ready-to-use code
This effect is free this month. Customize it live and copy-paste the code. Access 660+ more premium effects with the subscription.
Join the founders — €9.90 / month