UtkarshBirla28/threejs-geometry

Créer et façonner de la géométrie three.js : formes intégrées, texte 3D, tampons et attributs de sommets, fusion, instancing, batching, nuages de points et lignes, sur WebGL ou avec WebGPU et TSL. À utiliser lors de la construction de formes 3D ou de texte, de données de sommets personnalisées, de maillages personnalisés, de maillages instanciés ou de nuages de points.

Qu'est-ce que threejs-geometry ?

Cette compétence est un guide complet pour travailler avec la géométrie dans three.js, spécifiquement adapté à la version r186. Elle couvre un large éventail de techniques, depuis l'utilisation des formes intégrées de base (comme BoxGeometry, SphereGeometry, etc.) jusqu'à la création de géométries personnalisées avec BufferGeometry et l'optimisation des performances avec InstancedMesh et InstancedBufferGeometry. La compétence inclut également des sections dédiées à la gestion des arêtes et des filaires, aux nuages de points, aux lignes, ainsi qu'à l'utilisation avancée de WebGPU et du langage de shaders TSL. Une note importante est que la compétence contient une directive de remplacement local pour un pipeline spécifique (projet 'animated-shorts' de l'utilisateur), qui impose l'utilisation de three.js r175 et Remotion, en déconseillant les fonctionnalités introduites dans r176 et ultérieures. Cette compétence est utile pour tout développeur souhaitant maîtriser la création et la manipulation de géométries 3D avec three.js, que ce soit pour des projets web standards ou des pipelines de rendu avancés.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/UtkarshBirla28/animated-shorts-pipeline/tree/HEAD/.claude/skills/threejs-geometry

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

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 (et mixer.setTime(frame / fps) pour les clips).
  • Le post-traitement est postprocessing de 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]

Individual skills in this repo

This repo contains 1 individual skill — each has its own dedicated page.

Skills associés