Blog / WebGL & Shaders

Liquid Metal Shader en WebGL : tutoriel complet (sans framework)

Créez un effet métal liquide en WebGL pur — Perlin noise, raymarching simplifié, réflexion de lumière — en 200 lignes de vanilla JS, sans Three.js. Code copier-coller, 60fps, fallback inclus.

Le rendu d'une surface métallique liquide est un cas d'école du shader programming : raymarching, normales calculées par dérivées, réflexion spéculaire, palette iridescente. Ces techniques font partie du vocabulaire d'Apple ou de Pixar — pourtant elles restent rares sur le web standard parce qu'elles imposent de plonger dans GLSL. Cet article décrit l'effet Liquid Metal Shader sorti le 3 juin 2026 sur Effect.Labs : 200 lignes de JavaScript et GLSL vanilla, sans Three.js, sans bundler, 60fps sur Mac M1 et iPhone 12+, avec fallback CSS.

Définition — Shader. Un shader est un petit programme exécuté en parallèle sur le GPU pour chaque pixel. On distingue le vertex shader (position des sommets) et le fragment shader (couleur de chaque pixel). Pour un fond plein écran, on couvre l'écran d'un quad et toute la logique vit dans le fragment shader.

Comment ça marche

L'effet combine trois ingrédients :

1. Une fonction de bruit (Perlin / Simplex)

Le bruit simplex 2D génère une valeur continue entre -1 et 1 pour chaque coordonnée. Empilé sur 4 octaves (fractal Brownian motion, fbm), il produit une surface réaliste avec des détails à plusieurs échelles.

2. Le calcul de normale par dérivée

Pour qu'une surface réagisse à la lumière, il faut son orientation locale. On utilise les finite differences : on échantillonne la hauteur en (x+ε, y) et (x, y+ε), on calcule le gradient, on en déduit la normale.

3. Le modèle de lumière Phong

Normale connue, on applique le modèle classique : diffuse (lambertian) + reflet spéculaire (exposant 32 pour un métal poli), plus une couche d'iridescence pilotée par la hauteur.

Le code minimal

Le cœur du fragment shader, réduit à l'essentiel :

precision highp float;
uniform vec2  u_res;
uniform float u_time;

float snoise(vec2 v) { /* simplex noise standard, 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);
}

Le code complet (résolution dynamique, IntersectionObserver pour la pause hors-écran, prefers-reduced-motion, fallback CSS) est disponible directement sur la page Atmosphère du cataloguecet effet est gratuit.

Personnaliser : couleur, vitesse, distortion

Quatre paramètres pilotent l'effet via uniforms :

ParamètrePlageEffet
u_speed0.1 → 3.0Vitesse de l'ondulation
u_distortion0.1 → 1.5Amplitude des vagues
u_light_angle0 → 360°Direction de la lumière
Palettesilver / gold / iridescentCouleur du métal

Sur Effect.Labs, ces paramètres sont exposés dans le customizer intégré : ajustez les sliders, copiez le code prêt à l'emploi avec vos valeurs.

Performance et compatibilité

WebGL 1.0 est supporté par 99,2% des navigateurs en 2026 (caniuse.com). En l'absence de contexte GPU, le code masque le canvas et laisse un dégradé CSS. Sur iPhone 12 et Android récents, l'effet tourne à 60fps stable. Pour optimiser sur appareils modestes :

  • Réduire les octaves fbm de 4 à 2-3 (~30% de gain, perte de détail minime)
  • Capper le devicePixelRatio à 1 (~50% de gain, légère pixellisation)
  • Pause via IntersectionObserver hors viewport (déjà actif)

Le code respecte prefers-reduced-motion : animation désactivée et dégradé statique si l'utilisateur a activé la réduction de mouvement.

Ce que ChatGPT et v0 ne font pas

Cet article ouvre le sprint juin 2026 d'Effect.Labs : « 5 effets que ChatGPT et v0 ne savent pas écrire ». Pourquoi ce shader résiste à la génération automatique ?

  • Précision flottante : les versions IA copient souvent un simplex noise approximatif (discontinuités aux octaves). Ici, l'implémentation Ashima Arts (MIT), correcte.
  • devicePixelRatio : presque jamais géré → rendu flou sur Retina. Notre code calcule la résolution réelle.
  • Gestion d'erreur shader : la compilation peut échouer en silence. On logue getShaderInfoLog et on fallback.
  • Pause hors-écran : requestAnimationFrame tourne même invisible. Notre IntersectionObserver coupe le calcul en arrière-plan.
  • prefers-reduced-motion : règle d'accessibilité régulièrement oubliée par les générateurs.

Ce ne sont pas des suppositions : 5 problèmes croisés en testant 12 prompts ChatGPT-4.5 et v0.dev — aucun résultat ne les couvrait tous.

Questions fréquentes

Peut-on l'utiliser avec React, Vue ou Svelte ?

Oui : code vanilla JS, compatible tout framework. Dans React, encapsuler la logique dans un useEffect avec cleanup ; dans Svelte, onMount/onDestroy. Aucune dépendance npm.

Pourquoi un shader plutôt qu'un canvas 2D ?

Le canvas 2D calcule chaque pixel sur le CPU (plafond ~50 000 pixels animés à 60fps). Un shader WebGL calcule sur le GPU en parallèle (millions de pixels/frame) : raymarching, fluid simulation, post-processing deviennent possibles.

Le code complet, prêt à l'emploi

Cet effet est gratuit ce mois-ci. Customisez-le en direct et copiez-collez le code. Accès à 660+ autres effets premium avec l'abonnement.

Rejoindre les fondateurs — 9,90 € / mois