Géométrie Three.js
Cible three.js r186. Les exemples utilisent WebGLRenderer ; la section WebGPU et TSL utilise WebGPURenderer.
Remplacement local (ajouté le 2026-11-10 par Claude pour ce PC ; ne fait pas partie de la compétence d'origine). Notre pipeline de courts métrages animés (
<ROOT>\studio, provenant de mromidev/claude-animated-shorts) est verrouillé sur three 0.175 (r175)
- Remotion 4.0.290 + @react-three/fiber 9.1.2 + pmndrs postprocessing 6.37.2. Gardez three verrouillé : shade.ts/cardshade.ts corrige les chunks de shader three par des chaînes de caractères. Lorsque vous travaillez sur ce pipeline :
- Ignorez les « Erreurs courantes » / conseils d'API qui nécessitent r176+ (HDRLoader, Timer, dépréciation de PCFSoftShadowMap, setEffects, RenderPipeline, outputBufferType, mesh.pivot, hauteur de Capsule
height, TSL/WebGPU). Vérifiez d'abord que l'API existe dans r175.- N'animez jamais avec useFrame, Clock, Timer, delta time, setAnimationLoop ou requestAnimationFrame. Remotion effectue le rendu image par image : dérivez chaque valeur de
useCurrentFrame()/ fps (etmixer.setTime(frame / fps)pour les clips).- Le post-traitement est
postprocessingde pmndrs, pas EffectComposer de three/addons : n'ajoutez pas OutputPass (double encodage sRGB).- Couleurs Toon : le mappage des tons est NEUTRAL dans notre compositeur ; méfiez-vous des valeurs par défaut ACES et de la teinte
tonemapping_fragment.- Pour les autres projets three.js, les conseils originaux pour r186 ci-dessous s'appliquent tels quels.
Démarrage Rapide
import * as THREE from "three";
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
// Une géométrie est la forme ; plusieurs maillages peuvent partager une seule instance de géométrie
const mesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshNormalMaterial());
scene.add(mesh);
renderer.setAnimationLoop(animate);
function animate() {
mesh.rotation.y += 0.01;
renderer.render(scene, camera);
}
Géométries Intégrées
Formes de Base
new THREE.BoxGeometry(1, 1, 1); // largeur, hauteur, profondeur, segmentsLargeur, segmentsHauteur, segmentsProfondeur
new THREE.SphereGeometry(1, 32, 16); // rayon, segmentsLargeur, segmentsHauteur, phiStart, phiLength, thetaStart, thetaLength
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI); // demi-sphère (balayage phi, coupé à travers les pôles)
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2); // dôme (coupé à l'équateur)
new THREE.PlaneGeometry(10, 10, 1, 1); // largeur, hauteur, segmentsLargeur, segmentsHauteur
new THREE.CircleGeometry(1, 32); // rayon, segments, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 32); // rayonHaut, rayonBas, hauteur, segmentsRadiaux, segmentsHauteur, ouvertExtrémités, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 6); // prisme hexagonal
new THREE.ConeGeometry(1, 2, 32); // rayon, hauteur, segmentsRadiaux, segmentsHauteur, ouvertExtrémités, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100); // rayon, tube, segmentsRadiaux, segmentsTubulaires, arc, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100, Math.PI); // demi-arc
new THREE.TorusKnotGeometry(1, 0.4, 100, 16, 2, 3); // rayon, tube, segmentsTubulaires, segmentsRadiaux, p, q
new THREE.RingGeometry(0.5, 1, 32, 1); // rayonIntérieur, rayonExtérieur, segmentsTheta, segmentsPhi, thetaStart, thetaLength
Formes Avancées
new THREE.CapsuleGeometry(0.5, 1, 4, 8, 1); // rayon, hauteur (uniquement la section du milieu), segmentsCapsule, segmentsRadiaux, segmentsHauteur
// rayon, détail - chaque niveau de détail subdivise chaque face
new THREE.IcosahedronGeometry(1, 0); // 20 faces au niveau de détail 0
new THREE.DodecahedronGeometry(1, 0);
new THREE.OctahedronGeometry(1, 0);
new THREE.TetrahedronGeometry(1, 0);
// Polyèdre - tableau de sommets plat, indices, rayon, détail
const vertices = [1, 1, 1, -1, -1, 1, -1, 1, -1, 1, -1, -1];
const indices = [2, 1, 0, 0, 3, 2, 1, 3, 0, 2, 3, 1];
new THREE.PolyhedronGeometry(vertices, indices, 1, 0);
Formes Basées sur des Chemins
// Lathe - un profil Vector2 tourné autour de l'axe Y : points, segments, phiStart, phiLength
const profile = [
new THREE.Vector2(0, 0), new THREE.Vector2(0.5, 0), new THREE.Vector2(0.5, 1), new THREE.Vector2(0, 1),
];
new THREE.LatheGeometry(profile, 32);
// Extrusion - une forme 2D poussée le long de Z
const shape = new THREE.Shape();
shape.moveTo(0, 0);
shape.lineTo(1, 0);
shape.lineTo(1, 1);
shape.lineTo(0, 1);
new THREE.ExtrudeGeometry(shape, {
steps: 1, depth: 1, bevelEnabled: true,
bevelThickness: 0.1, bevelSize: 0.1, bevelSegments: 3,
});
// Tube - un balayage circulaire le long d'une courbe : chemin, segmentsTubulaires, rayon, segmentsRadiaux, fermé
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(1, 0, 0),
]);
new THREE.TubeGeometry(curve, 64, 0.2, 8, false);
Géométrie de Texte
import { FontLoader } from "three/addons/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
// Une police .typeface.json, provenant des exemples/fonts du dépôt three.js
const font = await new FontLoader().loadAsync("fonts/helvetiker_regular.typeface.json");
const geometry = new TextGeometry("Hello", {
font: font,
size: 1, // taille du texte en unités du monde
depth: 0.2, // profondeur d'extrusion
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
direction: "ltr", // 'ltr', 'rtl' (droite à gauche) ou 'tb' (haut en bas)
});
gometry.center(); // recentrer les glyphes sur l'origine
scene.add(new THREE.Mesh(geometry, new THREE.MeshNormalMaterial()));
BufferGeometry
La classe de base de chaque géométrie : des tableaux typés de données de sommets plus un index optionnel. Les attributs sont des tampons nommés de valeurs itemSize par sommet ; position, normal et uv sont ceux que les matériaux lisent par défaut.
BufferGeometry Personnalisée
const geometry = new THREE.BufferGeometry();
// Quatre sommets, trois flottants chacun (x, y, z)
const vertices = new Float32Array([-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, 1, 0]);
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
// Les indices réutilisent les sommets entre les deux triangles
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// Les normales pilotent l'éclairage, les UV adressent les textures
const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
gometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
gometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
// Couleurs optionnelles par sommet, rendues avec material.vertexColors = true
const colors = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0]);
gometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
scene.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ vertexColors: true })));
Types d'Attributs
const count = 4;
const geometry = new THREE.BufferGeometry();
// Float32 pour les positions, normales, UV et couleurs à virgule flottante
gometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(count * 3), 3));
// Les indices Uint16 adressent les sommets 0 à 65534 (65535 est la valeur de redémarrage primitif) ; setIndex([...]) avec un tableau simple choisit lui-même Uint16 ou Uint32
gometry.setIndex(new THREE.BufferAttribute(new Uint16Array(count), 1));
// Les couleurs Uint8 ont besoin de normalized = true (troisième argument) pour que les octets atteignent le shader sous forme de 0.0-1.0
gometry.setAttribute("color", new THREE.BufferAttribute(new Uint8Array(count * 3), 3, true));
Mise à Jour d'une BufferGeometry
const geometry = new THREE.PlaneGeometry(2, 2);
const positions = geometry.attributes.position;
positions.setXYZ(0, -2, -2, 0); // écraser le sommet 0
positions.getX(0); // lire des composants individuels avec getX / getY / getZ
positions.needsUpdate = true; // télécharger le changement ; un tableau ne peut pas croître après son premier rendu, allouez donc le maximum et utilisez geometry.setDrawRange(0, n)
gometry.computeVertexNormals(); // les normales doivent correspondre aux nouvelles positions
gometry.computeBoundingBox(); // les volumes de délimitation pilotent l'élimination du frustum et le lancer de rayons
gometry.computeBoundingSphere();
Tampons Entrelacés
// Un tampon contenant la position (3 flottants) et l'uv (2 flottants) par sommet
const interleavedBuffer = new THREE.InterleavedBuffer(
new Float32Array([
-1, -1, 0, 0, 0, 1, -1, 0, 1, 0, 1, 1, 0, 1, 1, -1, 1, 0, 0, 1, // quatre sommets : xyz + uv
]),
5, // stride : flottants par sommet
);
const geometry = new THREE.BufferGeometry();
gometry.setAttribute("position", new THREE.InterleavedBufferAttribute(interleavedBuffer, 3, 0));
gometry.setAttribute("uv", new THREE.InterleavedBufferAttribute(interleavedBuffer, 2, 3));
gometry.setIndex([0, 1, 2, 0, 2, 3]); // deux triangles sur les quatre sommets
// BufferGeometryUtils.interleaveAttributes([position, normal, uv]) empaquette les attributs existants de cette façon
Arêtes et Filaires
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// Arêtes où les normales des deux faces diffèrent d'au moins thresholdAngle (degrés ; 15 ici), plus les arêtes de limite ouvertes
const edges = new THREE.EdgesGeometry(boxGeometry, 15);
const edgeLines = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
// Chaque arête de triangle, diagonales incluses ; une arête partagée par deux triangles est dessinée une fois
const wireframe = new THREE.WireframeGeometry(boxGeometry);
const wireLines = new THREE.LineSegments(wireframe, new THREE.LineBasicMaterial({ color: 0xffffff }));
scene.add(edgeLines, wireLines);
Points
const count = 1000;
const positions = new Float32Array(count * 3).map(() => (Math.random() - 0.5) * 10);
const geometry = new THREE.BufferGeometry();
gometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
// sizeAttenuation (vrai par défaut) réduit les points avec la profondeur sous une caméra perspective : un point couvre size * (hauteur CSS du canvas / 2) / profondeur pixels CSS
const material = new THREE.PointsMaterial({ size: 0.1, color: 0xffffff });
const points = new THREE.Points(geometry, material);
scene.add(points);
Sous WebGPURenderer, le backend WebGPU dessine les points à un pixel quelle que soit la taille du matériau ; voir la section WebGPU et TSL pour le Sprite instancié qui restaure les tailles.
Lignes
const linePoints = [
new THREE.Vector3(-1, 0, 0), new THREE.Vector3(0, 1, 0), new THREE.Vector3(1, 0, 0),
];
const geometry = new THREE.BufferGeometry().setFromPoints(linePoints);
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
// Line relie les points dans l'ordre ; LineLoop se referme sur le premier point
const line = new THREE.Line(geometry, material);
const loop = new THREE.LineLoop(geometry, material);
scene.add(line, loop);
// LineSegments dessine des paires indépendantes : sommet 0-1, 2-3, ...
const segmentsGeometry = new THREE.BufferGeometry().setFromPoints([linePoints[0], linePoints[1], linePoints[1], linePoints[2]]);
const segments = new THREE.LineSegments(segmentsGeometry, material);
scene.add(segments);
LineLoop n'est pas pris en charge sous WebGPURenderer : il enregistre une erreur et n'affiche rien. Pour fermer une boucle, répétez le premier point et utilisez Line.
InstancedMesh
Un appel de rendu pour plusieurs copies d'une même géométrie, chacune avec sa propre transformation.
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const material = new THREE.MeshBasicMaterial(); // multiplie par les couleurs d'instance ; MeshNormalMaterial les ignore
const count = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
dummy.position.set((Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20, (Math.random() - 0.5) * 20);
dummy.rotation.set(Math.random() * Math.PI, Math.random() * Math.PI, 0);
dummy.scale.setScalar(0.5 + Math.random());
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
instancedMesh.instanceMatrix.needsUpdate = true; // nécessaire après les changements une fois que le maillage a été rendu ; inoffensif avant
// setColorAt crée instanceColor lors de la première utilisation ; signalez-le après la boucle
for (let i = 0; i < count; i++) {
instancedMesh.setColorAt(i, new THREE.Color(Math.random(), Math.random(), Math.random()));
}
instancedMesh.instanceColor.needsUpdate = true;
scene.add(instancedMesh);
Au moment de l'exécution, mettez à jour et effectuez un lancer de rayons sur les instances comme ceci :
// mesh ici est un InstancedMesh
const matrix = new THREE.Matrix4();
const index = 7;
mesh.getMatrixAt(index, matrix);
matrix.setPosition(0, 2, 0); // déplacer uniquement cette instance
mesh.setMatrixAt(index, matrix);
mesh.instanceMatrix.needsUpdate = true; // signaler après tout lot d'appels setMatrixAt
mesh.computeBoundingSphere(); // instances déplacées : rafraîchir les limites utilisées par l'élimination du frustum et le lancer de rayons
// Le lancer de rayons indique quelle instance a été touchée
const raycaster = new THREE.Raycaster();
const hits = raycaster.intersectObject(mesh);
if (hits.length > 0) {
const instanceId = hits[0].instanceId; // indéfini pour un objet non instancié
}
InstancedBufferGeometry
Attributs par instance au-delà de la transformation et de la couleur.
const count = 1000;
const geometry = new THREE.InstancedBufferGeometry();
gometry.copy(new THREE.BoxGeometry(1, 1, 1)); // copie l'index et tous les attributs
gometry.instanceCount = count; // requis : copy() ne transporte pas le nombre d'instances
const offsets = new Float32Array(count * 3).map(() => Math.random() * 10);
gometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));
// Avec WebGLRenderer et un ShaderMaterial, déclarez `in vec3 offset;` dans le sommet
// shader vous-même et ajoutez-le à la position (voir threejs-shaders). Sous WebGPURenderer,
// les données par instance sont un nœud TSL - voir la section WebGPU et TSL
…
[tronqué pour la traduction]