WEBGL : Vertex Shader dans page html
Particles WebGL2 — cours en 10 étapes (présentation)
1) Structure HTML minimale
Une page fond noir avec un unique <canvas> plein écran : c’est la surface de rendu.
<style>
html,body{margin:0;height:100%;background:#000;overflow:hidden}
canvas{position:fixed;inset:0;width:100%;height:100%;display:block}
</style>
<canvas id="gl"></canvas>
2) Canvas + DPR (haute résolution)
On utilise le devicePixelRatio (DPR) pour un rendu net et on ajuste le viewport au resize.
const canvas = document.getElementById('gl');
const gl = canvas.getContext('webgl2', {alpha:false});
let DPR=1, W=0, H=0;
function resize(){
DPR = Math.max(1, Math.min(3, window.devicePixelRatio||1));
W = Math.floor(innerWidth * DPR);
H = Math.floor(innerHeight * DPR);
canvas.width = W; canvas.height = H;
gl.viewport(0,0,W,H);
}
addEventListener('resize', resize, {passive:true});
resize();
3) Contexte WebGL2 + shaders minimaux
Smoke test : on compile un vertex/fragment tout simple et on dessine 1 point au centre.
// Vertex (NDC au centre)
const vs = `#version 300 es
precision highp float;
void main(){
gl_PointSize = 8.0;
gl_Position = vec4(0.0,0.0,0.0,1.0);
}`;
const fs = `#version 300 es
precision mediump float;
out vec4 outColor;
void main(){ outColor = vec4(1.0); }`;
// (compile + link) → gl.useProgram(program); gl.drawArrays(gl.POINTS, 0, 1);
4) Grille via gl_VertexID (sans VBO)
Sans buffer : chaque sommet déduit sa position dans une grille across×across en NDC.
uniform int uAcross;
void main(){
int id = gl_VertexID;
int x = int(mod(float(id), float(uAcross)));
int y = id / uAcross;
float u = float(x)/float(uAcross-1);
float v = float(y)/float(uAcross-1);
vec2 p = vec2(u*2.0-1.0, v*2.0-1.0);
gl_PointSize = 6.0;
gl_Position = vec4(p,0.0,1.0);
}
5) Taille selon DPR + uBaseSize
Taille en pixels “device” avec uDPR et uBaseSize. Légèrement plus gros en bas (mix sur v).
uniform float uDPR, uBaseSize; // px float base = mix(1.0, 1.6, 1.0 - v); // plus gros en bas gl_PointSize = uBaseSize * base * uDPR;
6) Ondes sinusoïdales
Deux offsets sin/cos dépendants du temps cassent la rigidité et animent la grille.
uniform float uTime; float offx = sin(uTime*0.8 + float(y)*0.21) * 0.08; float offy = cos(uTime*0.6 + float(x)*0.17) * 0.10; p += vec2(offx, offy);
7) Value-noise → champ de flux
Un value-noise 3D (x,y,t) fournit un angle et donc une direction de flux à ajouter.
float valueNoise(vec3 p){ /* trilinear value-noise */ }
uniform float uFlowScale, uFlowStr;
float ang = valueNoise(vec3(p*uFlowScale*2.0, uTime*0.15)) * 6.28318530718;
vec2 flow = vec2(cos(ang), sin(ang)) * uFlowStr;
p += flow;
8) Montée + traînées (voile noir)
La fumée monte et reboucle verticalement. Les traînées viennent d’un voile noir alpha rendu avant les points.
uniform float uRise; p.y += (uTime * uRise); if (p.y > 1.2) p.y -= 2.4; // Pass "voile" (fragment) uniform float uFade; // 0.06–0.1 out vec4 outColor; outColor = vec4(0.0,0.0,0.0, uFade);
9) Halo doux + filaire (F) + couleurs HSV
Remplace le disque plein par un halo radial (gl_PointCoord). Option anneau + teintes HSV animées.
in vec4 vColor; in float vAlpha; uniform bool uWireframe; vec2 uv = gl_PointCoord - 0.5; float r = length(uv)*2.0; float soft = smoothstep(1.0, 0.0, r); float ring = smoothstep(1.0, 0.90, r) * smoothstep(0.75, 0.9, r); float a = (uWireframe ? ring : soft) * vAlpha; if (a < 0.01) discard; outColor = vec4(vColor.rgb, a);
10) Souffle souris + burst (B) + additif
Poussée radiale autour de la souris (NDC). Burst = impulsion courte. Blend additif pour l’effet glow.
uniform vec2 uMouseNDC; uniform float uMousePush, uBurst; vec2 d = p - uMouseNDC; float r = length(d); float push = smoothstep(0.7, 0.0, r) * uMousePush; if (r > 0.0001) p += normalize(d) * 0.35 * push; // dessin : gl.blendFunc(gl.SRC_ALPHA, gl.ONE); // additif
Code final complet:
Commentaires
Enregistrer un commentaire