iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
iducationducation
IndexArticlesFormationsProfilOutilsBibliothech
N°014 — 2026
Navigation
01Index02Articles03Formations04Profil05Outils06Bibliothech
N°014 — 2026
Formations
JavaScript · Intermédiaire

Three.js

Créer de la 3D dans le navigateur de zéro : scène, modèles STL/GLTF, animation au scroll, shaders, post-processing et portfolio 3D animé complet.

Three.jsJavaScriptGSAPGLSLWebGL
01Introduction à Three.js02Géométries, matériaux et lumières03Charger des modèles 3D — STL et GLTF04Animer au scroll — Three.js + GSAP05Post-processing et effets visuels06Shaders GLSL et systèmes de particules07Portfolio 3D animé — projet complet
Chapitre 6·30 min

Shaders GLSL et systèmes de particules

Les matériaux Three.js (Standard, Physical, Normal) sont puissants mais figés — ce sont des shaders GLSL précompilés avec des paramètres exposés. Dès qu'on veut un effet vraiment custom — un dégradé animé, une distorsion de surface, un effet de bruit procédural — on écrit son propre shader.

C'est quoi un shader

Un shader est un programme qui s'exécute sur le GPU, en parallèle, pour chaque vertex ou chaque pixel.

Il y en a deux types, toujours en tandem :

Vertex shader — s'exécute pour chaque vertex (point 3D). Son rôle : positionner le vertex en 2D sur l'écran (gl_Position). Peut aussi déformer la géométrie.

Fragment shader — s'exécute pour chaque pixel à l'intérieur des triangles. Son rôle : définir la couleur finale (gl_FragColor).

// Vertex shader minimal
void main() {
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// Fragment shader minimal
void main() {
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);  // rouge, plein
}

projectionMatrix * modelViewMatrix — la chaîne de transformations qui passe des coordonnées 3D du monde aux coordonnées 2D de l'écran. Three.js injecte ces matrices automatiquement.

ShaderMaterial

const material = new THREE.ShaderMaterial({
  vertexShader: `
    void main() {
      gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
  `,
  fragmentShader: `
    void main() {
      gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0);
    }
  `,
})

En pratique, on externalise les shaders dans des fichiers .glsl ou on utilise des template strings avec un syntax highlighter VSCode (extension glsl-canvas ou Shader languages support).

Uniforms — passer des données au shader

Les uniforms sont des variables qu'on envoie depuis JavaScript au shader. Elles restent constantes pendant un frame (d'où "uniform").

const material = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0 },
    uColor: { value: new THREE.Color('#cdff4f') },
    uResolution: { value: new THREE.Vector2(innerWidth, innerHeight) },
    uMouse: { value: new THREE.Vector2(0, 0) },
  },
  vertexShader: /* glsl */`
    uniform float uTime;
 
    void main() {
      vec3 pos = position;
      pos.y += sin(pos.x * 2.0 + uTime) * 0.2;  // ondulation sur Y
      gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
    }
  `,
  fragmentShader: /* glsl */`
    uniform vec3 uColor;
    uniform float uTime;
 
    void main() {
      // Couleur qui pulse légèrement
      float pulse = sin(uTime * 2.0) * 0.1 + 0.9;
      gl_FragColor = vec4(uColor * pulse, 1.0);
    }
  `,
})
 
// Mettre à jour uTime dans la render loop
const clock = new THREE.Clock()
function animate() {
  requestAnimationFrame(animate)
  material.uniforms.uTime.value = clock.getElapsedTime()
  renderer.render(scene, camera)
}

Varyings — vertex → fragment

Les varyings passent des données du vertex shader au fragment shader. La valeur est interpolée entre les vertices.

// Vertex shader
varying vec2 vUv;        // déclarer la varying
varying vec3 vPosition;
 
void main() {
  vUv = uv;              // uv = coordonnées de texture (0,0 → 1,1)
  vPosition = position;
  gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
// Fragment shader
varying vec2 vUv;
varying vec3 vPosition;
 
void main() {
  // Dégradé de gauche à droite
  vec3 color = mix(vec3(0.0), vec3(0.8, 1.0, 0.3), vUv.x);
  gl_FragColor = vec4(color, 1.0);
}

mix(a, b, t) interpole linéairement entre a et b selon t (0 = a, 1 = b). C'est la fonction GLSL la plus utilisée.

Noise — bruit procédural

La base de la plupart des effets de surface organiques. GLSL n'a pas de fonction noise() native — on l'ajoute manuellement ou on utilise une bibliothèque.

// Fonction hash simple (pseudo-aléatoire)
float random(vec2 st) {
  return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}
 
// Noise de valeur 2D interpolé
float noise(vec2 st) {
  vec2 i = floor(st);
  vec2 f = fract(st);
  vec2 u = f * f * (3.0 - 2.0 * f);  // smoothstep
 
  return mix(
    mix(random(i + vec2(0.0, 0.0)), random(i + vec2(1.0, 0.0)), u.x),
    mix(random(i + vec2(0.0, 1.0)), random(i + vec2(1.0, 1.0)), u.x),
    u.y
  );
}
// Fragment shader : surface organique animée
uniform float uTime;
varying vec2 vUv;
 
void main() {
  vec2 uv = vUv * 3.0;
  float n = noise(uv + uTime * 0.3);
  n += noise(uv * 2.0 - uTime * 0.1) * 0.5;  // FBM (Fractional Brownian Motion)
 
  vec3 color = mix(vec3(0.05), vec3(0.8, 1.0, 0.3), n);
  gl_FragColor = vec4(color, 1.0);
}

La librairie glslify permet d'importer des modules GLSL (noise, simplex, etc.) comme des modules npm.

Systèmes de particules

Points — la base

Points utilise une géométrie dont chaque vertex est rendu comme un sprite carré.

const count = 5000
 
// Créer les positions
const positions = new Float32Array(count * 3)
for (let i = 0; i < count; i++) {
  positions[i * 3 + 0] = (Math.random() - 0.5) * 10  // x
  positions[i * 3 + 1] = (Math.random() - 0.5) * 10  // y
  positions[i * 3 + 2] = (Math.random() - 0.5) * 10  // z
}
 
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3))
 
const material = new THREE.PointsMaterial({
  size: 0.05,
  color: '#cdff4f',
  transparent: true,
  opacity: 0.8,
  sizeAttenuation: true,  // les particules lointaines sont plus petites
})
 
const particles = new THREE.Points(geometry, material)
scene.add(particles)
 
// Animer dans la render loop
function animate() {
  particles.rotation.y += 0.0005
  // ...
}

Particules avec ShaderMaterial

Pour un contrôle total : taille, couleur, opacité par particule.

const count = 10000
const positions = new Float32Array(count * 3)
const sizes = new Float32Array(count)
const colors = new Float32Array(count * 3)
 
for (let i = 0; i < count; i++) {
  // Positions en sphère
  const phi = Math.acos(2 * Math.random() - 1)
  const theta = 2 * Math.PI * Math.random()
  const r = Math.random() * 5
 
  positions[i * 3]     = r * Math.sin(phi) * Math.cos(theta)
  positions[i * 3 + 1] = r * Math.sin(phi) * Math.sin(theta)
  positions[i * 3 + 2] = r * Math.cos(phi)
 
  sizes[i] = Math.random() * 2 + 0.5
  colors[i * 3]     = Math.random()
  colors[i * 3 + 1] = Math.random()
  colors[i * 3 + 2] = Math.random()
}
 
const geo = new THREE.BufferGeometry()
geo.setAttribute('position', new THREE.BufferAttribute(positions, 3))
geo.setAttribute('aSize', new THREE.BufferAttribute(sizes, 1))
geo.setAttribute('aColor', new THREE.BufferAttribute(colors, 3))
 
const mat = new THREE.ShaderMaterial({
  uniforms: {
    uTime: { value: 0 },
    uPixelRatio: { value: Math.min(devicePixelRatio, 2) },
  },
  vertexShader: /* glsl */`
    attribute float aSize;
    attribute vec3 aColor;
    uniform float uTime;
    uniform float uPixelRatio;
    varying vec3 vColor;
 
    void main() {
      vColor = aColor;
      vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
 
      // Flottement individuel par particule
      float offset = sin(position.x * 2.0 + uTime) * 0.1;
      mvPosition.y += offset;
 
      gl_Position = projectionMatrix * mvPosition;
      gl_PointSize = aSize * uPixelRatio * (300.0 / -mvPosition.z);
    }
  `,
  fragmentShader: /* glsl */`
    varying vec3 vColor;
 
    void main() {
      // Particule ronde (par défaut c'est un carré)
      float dist = length(gl_PointCoord - vec2(0.5));
      if (dist > 0.5) discard;
 
      // Bords doux
      float alpha = 1.0 - smoothstep(0.3, 0.5, dist);
      gl_FragColor = vec4(vColor, alpha);
    }
  `,
  transparent: true,
  depthWrite: false,  // éviter les artefacts de tri avec les particules transparentes
})
 
const particles = new THREE.Points(geo, mat)
scene.add(particles)

gl_PointCoord est une varying automatique de (0,0) à (1,1) à l'intérieur du sprite. discard rejette le pixel — c'est comme ça qu'on fait des particules rondes.

Particules qui forment un modèle STL

Effet spectaculaire : des particules qui forment la silhouette d'un modèle 3D.

loader.load('/models/piece.stl', (geo) => {
  // Extraire les positions du modèle comme positions de particules
  const modelPositions = geo.attributes.position.array
  const count = Math.floor(modelPositions.length / 3)
 
  // Sous-échantillonner si trop de vertices
  const maxParticles = 5000
  const step = Math.max(1, Math.floor(count / maxParticles))
 
  const particlePositions = []
  for (let i = 0; i < count; i += step) {
    particlePositions.push(
      modelPositions[i * 3],
      modelPositions[i * 3 + 1],
      modelPositions[i * 3 + 2]
    )
  }
 
  const particleGeo = new THREE.BufferGeometry()
  particleGeo.setAttribute(
    'position',
    new THREE.BufferAttribute(new Float32Array(particlePositions), 3)
  )
 
  const particles = new THREE.Points(particleGeo, mat)
  scene.add(particles)
})

Combiné avec GSAP pour animer la transition "particules dispersées → forment le modèle" est un effet d'entrée très impressionnant.


Shaders et particules ouvrent des possibilités visuelles infinies. Le dernier chapitre assemble tout dans un portfolio 3D complet — scène, modèles, caméra animée au scroll, sections interactives.

Précédent
Post-processing et effets visuels
Suivant
Portfolio 3D animé — projet complet

Développeur fullstack passionné. J'apprends en construisant et je documente tout — front, back, outils. Le code s'apprend mieux en public.

Naviguer

IndexTous les articlesFormationsProfilOutilsBibliothech

Ailleurs

GitHub RSS

Newsletter

Les articles, libs et découvertes. Une fois par semaine, pas plus.

© 2026 William LoreeConçu & codé à la main