Three.js-Geometrie
Ziel: three.js r186. Beispiele verwenden WebGLRenderer; der Abschnitt WebGPU und TSL verwendet WebGPURenderer.
Lokale Überschreibung (hinzugefügt am 10.11.2026 von Claude für diesen PC; kein Teil der ursprünglichen Fertigkeit). Unsere Pipeline für animierte Kurzfilme (
<ROOT>\studio, von mromidev/claude-animated-shorts) ist an three 0.175 (r175) gebunden
- Remotion 4.0.290 + @react-three/fiber 9.1.2 + pmndrs postprocessing 6.37.2. Behalte three fixiert: shade.ts/cardshade.ts patcht drei Shader-Chunks per String-Patching. Bei der Arbeit an dieser Pipeline:
- Ignoriere "Häufige Fehler" / API-Ratschläge, die r176+ benötigen (HDRLoader, Timer, PCFSoftShadowMap-Deprecation, setEffects, RenderPipeline, outputBufferType, mesh.pivot, Capsule
height, TSL/WebGPU). Überprüfe zunächst, ob die API in r175 existiert.- Verwende niemals useFrame, Clock, Timer, delta time, setAnimationLoop oder requestAnimationFrame für Animationen. Remotion rendert Bild für Bild: leite jeden Wert aus
useCurrentFrame()/ fps ab (undmixer.setTime(frame / fps)für Clips).- Post-Processing erfolgt mit pmndrs
postprocessing, nicht mit three/addons EffectComposer: Füge keinen OutputPass hinzu (doppelte sRGB-Kodierung).- Toon-Farben: Das Tone Mapping in unserem Composer ist NEUTRAL; achte auf ACES-Standardeinstellungen und
tonemapping_fragment-Tönung.- Für andere three.js-Projekte gilt die ursprüngliche r186-Anleitung unten wie beschrieben.
Kurzstart
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);
// Eine Geometrie ist die Form; viele Meshes können eine Geometrieinstanz gemeinsam nutzen
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);
}
Integrierte Geometrien
Grundlegende Formen
new THREE.BoxGeometry(1, 1, 1); // Breite, Höhe, Tiefe, BreitenSegmente, HöhenSegmente, TiefenSegmente
new THREE.SphereGeometry(1, 32, 16); // Radius, BreitenSegmente, HöhenSegmente, phiStart, phiLength, thetaStart, thetaLength
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI); // Halbkugel (phi-Schwenk, Schnitt durch die Pole)
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2); // Kuppel (Schnitt am Äquator)
new THREE.PlaneGeometry(10, 10, 1, 1); // Breite, Höhe, BreitenSegmente, HöhenSegmente
new THREE.CircleGeometry(1, 32); // Radius, Segmente, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 32); // radiusTop, radiusBottom, Höhe, radialeSegmente, HöhenSegmente, openEnded, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 6); // Sechseckprisma
new THREE.ConeGeometry(1, 2, 32); // Radius, Höhe, radialeSegmente, HöhenSegmente, openEnded, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100); // Radius, Rohr, radialeSegmente, tubuläreSegmente, arc, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100, Math.PI); // Halbbogen
new THREE.TorusKnotGeometry(1, 0.4, 100, 16, 2, 3); // Radius, Rohr, tubuläreSegmente, radialeSegmente, p, q
new THREE.RingGeometry(0.5, 1, 32, 1); // innererRadius, äußererRadius, thetaSegmente, phiSegmente, thetaStart, thetaLength
Fortgeschrittene Formen
new THREE.CapsuleGeometry(0.5, 1, 4, 8, 1); // Radius, Höhe (nur der Mittelteil), KappenSegmente, radialeSegmente, HöhenSegmente
// Radius, Detail - jede Detailebene unterteilt jede Fläche
new THREE.IcosahedronGeometry(1, 0); // 20 Flächen bei Detail 0
new THREE.DodecahedronGeometry(1, 0);
new THREE.OctahedronGeometry(1, 0);
new THREE.TetrahedronGeometry(1, 0);
// Polyeder - flaches Vertices-Array, Indices, Radius, Detail
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);
Pfadbasierte Formen
// Lathe - ein Vector2-Profil, das um die Y-Achse gedreht wird: Punkte, Segmente, 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);
// Extrude - eine 2D-Form, die entlang der Z-Achse extrudiert wird
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 - eine kreisförmige Abtastung entlang einer Kurve: Pfad, tubuläreSegmente, Radius, radialeSegmente, geschlossen
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);
Textgeometrie
import { FontLoader } from "three/addons/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
// Eine .typeface.json-Schriftart aus examples/fonts im three.js-Repository
const font = await new FontLoader().loadAsync("fonts/helvetiker_regular.typeface.json");
const geometry = new TextGeometry("Hello", {
font: font,
size: 1, // Textgröße in Welteinheiten
depth: 0.2, // Extrusionstiefe
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
direction: "ltr", // 'ltr', 'rtl' (rechts nach links) oder 'tb' (oben nach unten)
});
geometry.center(); // Glyphen am Ursprung neu zentrieren
scene.add(new THREE.Mesh(geometry, new THREE.MeshNormalMaterial()));
BufferGeometry
Die Basisklasse jeder Geometrie: typisierte Arrays von Vertexdaten plus ein optionaler Index. Attribute sind benannte Puffer mit itemSize Werten pro Vertex; position, normal und uv sind die, die Materialien standardmäßig lesen.
Benutzerdefinierte BufferGeometry
const geometry = new THREE.BufferGeometry();
// Vier Vertices, jeweils drei Floats (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));
// Indices verwenden Vertices über die beiden Dreiecke hinweg wieder
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// Normalen steuern die Beleuchtung, UVs adressieren Texturen
const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
geometry.setAttribute("normal", new THREE.BufferAttribute(normals, 3));
const uvs = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
// Optionale Vertex-Farben, gerendert mit material.vertexColors = true
const colors = new Float32Array([1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0]);
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
scene.add(new THREE.Mesh(geometry, new THREE.MeshBasicMaterial({ vertexColors: true })));
Attributtypen
const count = 4;
const geometry = new THREE.BufferGeometry();
// Float32 für Positionen, Normalen, UVs und Fließkommafarben
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(count * 3), 3));
// Uint16-Indices adressieren Vertices 0 bis 65534 (65535 ist der Primitive-Restart-Wert); setIndex([...]) mit einem einfachen Array wählt selbstständig Uint16 oder Uint32
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(count), 1));
// Uint8-Farben benötigen normalized = true (drittes Argument), damit Bytes als 0.0-1.0 zum Shader gelangen
geometry.setAttribute("color", new THREE.BufferAttribute(new Uint8Array(count * 3), 3, true));
Aktualisieren einer BufferGeometry
const geometry = new THREE.PlaneGeometry(2, 2);
const positions = geometry.attributes.position;
positions.setXYZ(0, -2, -2, 0); // Vertex 0 überschreiben
positions.getX(0); // Einzelne Komponenten mit getX / getY / getZ lesen
positions.needsUpdate = true; // Änderung hochladen; ein Array kann nach dem ersten Rendern nicht wachsen, daher das Maximum zuweisen und geometry.setDrawRange(0, n) verwenden
geometry.computeVertexNormals(); // Normalen müssen den neuen Positionen entsprechen
geometry.computeBoundingBox(); // Begrenzungsvolumen steuern Frustum-Culling und Raycasting
geometry.computeBoundingSphere();
Interleaved-Puffer
// Ein Puffer, der Position (3 Floats) und UV (2 Floats) pro Vertex hält
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, // vier Vertices: xyz + uv
]),
5, // Schrittweite: Floats pro Vertex
);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.InterleavedBufferAttribute(interleavedBuffer, 3, 0));
geometry.setAttribute("uv", new THREE.InterleavedBufferAttribute(interleavedBuffer, 2, 3));
geometry.setIndex([0, 1, 2, 0, 2, 3]); // zwei Dreiecke über die vier Vertices
// BufferGeometryUtils.interleaveAttributes([position, normal, uv]) packt vorhandene Attribute auf diese Weise
Kanten und Drahtgitter
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// Kanten, bei denen sich die Normalen der beiden Flächen um mindestens thresholdAngle (Grad; hier 15) unterscheiden, plus offene Grenzkanten
const edges = new THREE.EdgesGeometry(boxGeometry, 15);
const edgeLines = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
// Jede Dreieckskante, einschließlich Diagonalen; eine von zwei Dreiecken geteilte Kante wird einmal gezeichnet
const wireframe = new THREE.WireframeGeometry(boxGeometry);
const wireLines = new THREE.LineSegments(wireframe, new THREE.LineBasicMaterial({ color: 0xffffff }));
scene.add(edgeLines, wireLines);
Punkte
const count = 1000;
const positions = new Float32Array(count * 3).map(() => (Math.random() - 0.5) * 10);
const geometry = new THREE.BufferGeometry();
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
// sizeAttenuation (standardmäßig true) verkleinert Punkte mit der Tiefe unter einer perspektivischen Kamera: Ein Punkt erstreckt sich über size * (Canvas-CSS-Höhe / 2) / Tiefe CSS-Pixel
const material = new THREE.PointsMaterial({ size: 0.1, color: 0xffffff });
const points = new THREE.Points(geometry, material);
scene.add(points);
Unter WebGPURenderer zeichnet das WebGPU-Backend Punkte mit einem Pixel unabhängig von der Materialgröße; siehe den Abschnitt WebGPU und TSL für das instanziierte Sprite, das Größen wiederherstellt.
Linien
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 verbindet die Punkte der Reihe nach; LineLoop schließt zum ersten Punkt zurück
const line = new THREE.Line(geometry, material);
const loop = new THREE.LineLoop(geometry, material);
scene.add(line, loop);
// LineSegments zeichnet unabhängige Paare: Vertex 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 wird unter WebGPURenderer nicht unterstützt: es protokolliert einen Fehler und rendert nichts. Schließe dort eine Schleife, indem du den ersten Punkt wiederholst und Line verwendest.
InstancedMesh
Ein Draw-Call für viele Kopien einer Geometrie, jede mit ihrer eigenen Transformation.
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const material = new THREE.MeshBasicMaterial(); // multipliziert mit Instanzfarben; MeshNormalMaterial ignoriert sie
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; // nach Änderungen erforderlich, sobald das Mesh gerendert wurde; vorher harmlos
// setColorAt erstellt instanceColor bei der ersten Verwendung; nach der Schleife markieren
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);
Zur Laufzeit Instanzen wie folgt aktualisieren und raycasten:
// mesh ist hier ein InstancedMesh
const matrix = new THREE.Matrix4();
const index = 7;
mesh.getMatrixAt(index, matrix);
matrix.setPosition(0, 2, 0); // nur diese Instanz verschieben
mesh.setMatrixAt(index, matrix);
mesh.instanceMatrix.needsUpdate = true; // nach jedem Stapel von setMatrixAt-Aufrufen markieren
mesh.computeBoundingSphere(); // Instanzen verschoben: die von Frustum-Culling und Raycasting verwendeten Grenzen aktualisieren
// Raycasting meldet, welche Instanz getroffen wurde
const raycaster = new THREE.Raycaster();
const hits = raycaster.intersectObject(mesh);
if (hits.length > 0) {
const instanceId = hits[0].instanceId; // undefined für ein nicht instanziiertes Objekt
}
InstancedBufferGeometry
Pro-Instanz-Attribute über Transformation und Farbe hinaus.
const count = 1000;
const geometry = new THREE.InstancedBufferGeometry();
geometry.copy(new THREE.BoxGeometry(1, 1, 1)); // kopiert den Index und alle Attribute
geometry.instanceCount = count; // erforderlich: copy() überträgt keine Instanzanzahl
const offsets = new Float32Array(count * 3).map(() => Math.random() * 10);
geometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));
// Mit WebGLRenderer und einem ShaderMaterial deklariere `in vec3 offset;` im Vertex-
// Shader selbst und addiere es zur Position (siehe threejs-shaders). Unter WebGPURenderer
// sind Pro-Instanz-Daten ein TSL-Knoten - siehe den Abschnitt WebGPU und TSL.