UI Skill
threejs-geometry
"Creating and shaping three.js geometry: built-in shapes, 3D text, vertex buffers and attributes, merging, instancing, batching, point clouds and lines, on WebGL or with WebGPU and TSL. Use when building 3D shapes or text, custom vertex data, custom meshes, instanced meshes, or point clouds."
目录
- Quick Start
- Built-in Geometries
- Basic Shapes
- Advanced Shapes
- Path-Based Shapes
- Text Geometry
- BufferGeometry
- Custom BufferGeometry
- Attribute Types
- Updating a BufferGeometry
- Interleaved Buffers
- Edges and Wireframes
- Points
- Lines
- InstancedMesh
- InstancedBufferGeometry
- BatchedMesh
- Geometry Utilities
- Transforming Geometry
- Morph Targets
- Performance and Disposal
- WebGPU and TSL
- Common Mistakes
- See Also
Three.js Geometry
Targets three.js r186. Examples use WebGLRenderer; the WebGPU and TSL section uses WebGPURenderer.
Quick Start
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);
// A geometry is the shape; many meshes can share one geometry instance
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);
}
Built-in Geometries
Basic Shapes
new THREE.BoxGeometry(1, 1, 1); // width, height, depth, widthSegments, heightSegments, depthSegments
new THREE.SphereGeometry(1, 32, 16); // radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI); // half sphere (phi sweep, cut through the poles)
new THREE.SphereGeometry(1, 32, 16, 0, Math.PI * 2, 0, Math.PI / 2); // dome (cut at the equator)
new THREE.PlaneGeometry(10, 10, 1, 1); // width, height, widthSegments, heightSegments
new THREE.CircleGeometry(1, 32); // radius, segments, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 32); // radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength
new THREE.CylinderGeometry(1, 1, 2, 6); // hexagonal prism
new THREE.ConeGeometry(1, 2, 32); // radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100); // radius, tube, radialSegments, tubularSegments, arc, thetaStart, thetaLength
new THREE.TorusGeometry(1, 0.4, 16, 100, Math.PI); // half arc
new THREE.TorusKnotGeometry(1, 0.4, 100, 16, 2, 3); // radius, tube, tubularSegments, radialSegments, p, q
new THREE.RingGeometry(0.5, 1, 32, 1); // innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength
Advanced Shapes
new THREE.CapsuleGeometry(0.5, 1, 4, 8, 1); // radius, height (the middle section only), capSegments, radialSegments, heightSegments
// radius, detail - each detail level subdivides every face
new THREE.IcosahedronGeometry(1, 0); // 20 faces at detail 0
new THREE.DodecahedronGeometry(1, 0);
new THREE.OctahedronGeometry(1, 0);
new THREE.TetrahedronGeometry(1, 0);
// Polyhedron - flat 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);
Path-Based Shapes
// Lathe - a Vector2 profile rotated around the Y axis: 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);
// Extrude - a 2D Shape pushed along 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 - a circular sweep along a curve: path, tubularSegments, radius, radialSegments, closed
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);
Text Geometry
import { FontLoader } from "three/addons/loaders/FontLoader.js";
import { TextGeometry } from "three/addons/geometries/TextGeometry.js";
// A .typeface.json font, from examples/fonts in the three.js repository
const font = await new FontLoader().loadAsync("fonts/helvetiker_regular.typeface.json");
const geometry = new TextGeometry("Hello", {
font: font,
size: 1, // text size in world units
depth: 0.2, // extrusion depth
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.03,
bevelSize: 0.02,
direction: "ltr", // 'ltr', 'rtl' (right to left) or 'tb' (top to bottom)
});
geometry.center(); // recentre the glyphs on the origin
scene.add(new THREE.Mesh(geometry, new THREE.MeshNormalMaterial()));
BufferGeometry
The base class of every geometry: typed arrays of vertex data plus an optional index. Attributes are named buffers of itemSize values per vertex; position, normal and uv are the ones materials read by default.
Custom BufferGeometry
const geometry = new THREE.BufferGeometry();
// Four vertices, three floats each (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 reuse vertices across the two triangles
const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// Normals drive the lighting, UVs address textures
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));
// Optional per-vertex colors, rendered with 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 })));
Attribute Types
const count = 4;
const geometry = new THREE.BufferGeometry();
// Float32 for positions, normals, UVs and floating-point colors
geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(count * 3), 3));
// Uint16 indices address vertices 0 to 65534 (65535 is the primitive-restart value); setIndex([...]) with a plain array picks Uint16 or Uint32 itself
geometry.setIndex(new THREE.BufferAttribute(new Uint16Array(count), 1));
// Uint8 colors need normalized = true (third argument) so bytes reach the shader as 0.0-1.0
geometry.setAttribute("color", new THREE.BufferAttribute(new Uint8Array(count * 3), 3, true));
Updating a BufferGeometry
const geometry = new THREE.PlaneGeometry(2, 2);
const positions = geometry.attributes.position;
positions.setXYZ(0, -2, -2, 0); // overwrite vertex 0
positions.getX(0); // read single components with getX / getY / getZ
positions.needsUpdate = true; // upload the change; an array cannot grow after its first render, so allocate the maximum and use geometry.setDrawRange(0, n)
geometry.computeVertexNormals(); // normals must match the new positions
geometry.computeBoundingBox(); // bounding volumes drive frustum culling and raycasting
geometry.computeBoundingSphere();
Interleaved Buffers
// One buffer holding position (3 floats) and uv (2 floats) per vertex
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, // four vertices: xyz + uv
]),
5, // stride: floats per 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]); // two triangles over the four vertices
// BufferGeometryUtils.interleaveAttributes([position, normal, uv]) packs existing attributes this way
Edges and Wireframes
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// Edges where the two faces' normals differ by at least thresholdAngle (degrees; 15 here), plus open boundary edges
const edges = new THREE.EdgesGeometry(boxGeometry, 15);
const edgeLines = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0xffffff }));
// Every triangle edge, diagonals included; an edge shared by two triangles is drawn once
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();
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
// sizeAttenuation (default true) shrinks points with depth under a perspective camera: a point spans size * (canvas CSS height / 2) / depth CSS pixels
const material = new THREE.PointsMaterial({ size: 0.1, color: 0xffffff });
const points = new THREE.Points(geometry, material);
scene.add(points);
Under WebGPURenderer the WebGPU backend draws points at one pixel whatever the material size; see the WebGPU and TSL section for the instanced Sprite that restores sizes.
Lines
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 connects the points in order; LineLoop closes back to the first point
const line = new THREE.Line(geometry, material);
const loop = new THREE.LineLoop(geometry, material);
scene.add(line, loop);
// LineSegments draws independent pairs: 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 is not supported under WebGPURenderer: it logs an error and renders nothing. Close a loop there by repeating the first point and using Line.
InstancedMesh
One draw call for many copies of one geometry, each with its own transform.
const geometry = new THREE.BoxGeometry(0.2, 0.2, 0.2);
const material = new THREE.MeshBasicMaterial(); // multiplies by instance colors; MeshNormalMaterial ignores them
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; // needed after changes once the mesh has rendered; harmless before
// setColorAt creates instanceColor on first use; flag it after the loop
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);
At runtime, update and raycast instances like this:
// mesh here is an InstancedMesh
const matrix = new THREE.Matrix4();
const index = 7;
mesh.getMatrixAt(index, matrix);
matrix.setPosition(0, 2, 0); // move just this instance
mesh.setMatrixAt(index, matrix);
mesh.instanceMatrix.needsUpdate = true; // flag after any batch of setMatrixAt calls
mesh.computeBoundingSphere(); // instances moved: refresh the bounds used by frustum culling and raycasting
// Raycasting reports which instance was hit
const raycaster = new THREE.Raycaster();
const hits = raycaster.intersectObject(mesh);
if (hits.length > 0) {
const instanceId = hits[0].instanceId; // undefined for a non-instanced object
}
InstancedBufferGeometry
Per-instance attributes beyond transform and color.
const count = 1000;
const geometry = new THREE.InstancedBufferGeometry();
geometry.copy(new THREE.BoxGeometry(1, 1, 1)); // copies the index and all attributes
geometry.instanceCount = count; // required: copy() carries no instance count
const offsets = new Float32Array(count * 3).map(() => Math.random() * 10);
geometry.setAttribute("offset", new THREE.InstancedBufferAttribute(offsets, 3));
// With WebGLRenderer and a ShaderMaterial, declare `in vec3 offset;` in the vertex
// shader yourself and add it to position (see threejs-shaders). Under WebGPURenderer,
// per-instance data is a TSL node - see the WebGPU and TSL section.
BatchedMesh
Many different geometries sharing one material, each instance with its own transform. WebGLRenderer draws them with one multi-draw call where WEBGL_multi_draw is available; the WebGPU backend issues one draw per instance.
const material = new THREE.MeshNormalMaterial();
const maxInstanceCount = 1000;
const maxVertexCount = 20000;
const maxIndexCount = 20000;
const batchedMesh = new THREE.BatchedMesh(maxInstanceCount, maxVertexCount, maxIndexCount, material);
const boxGeometryId = batchedMesh.addGeometry(new THREE.BoxGeometry(1, 1, 1));
const sphereGeometryId = batchedMesh.addGeometry(new THREE.SphereGeometry(0.5, 16, 16));
// A geometry renders only through its instances; added geometries need matching attributes and index state
const boxInstanceId = batchedMesh.addInstance(boxGeometryId);
batchedMesh.setMatrixAt(boxInstanceId, new THREE.Matrix4().setPosition(-2, 0, 0));
const sphereInstanceId = batchedMesh.addInstance(sphereGeometryId);
batchedMesh.setMatrixAt(sphereInstanceId, new THREE.Matrix4().setPosition(2, 0, 0));
scene.add(batchedMesh);
Geometry Utilities
import * as BufferGeometryUtils from "three/addons/utils/BufferGeometryUtils.js";
import * as MikkTSpace from "three/addons/libs/mikktspace.module.js";
const geo1 = new THREE.BoxGeometry(1, 1, 1);
const geo2 = new THREE.SphereGeometry(0.5, 16, 16);
// Merge static geometries; inputs need the same attributes and must all be indexed or all non-indexed (else null).
// useGroups = true adds one group per input, for a material array such as [boxMaterial, sphereMaterial].
const merged = BufferGeometryUtils.mergeGeometries([geo1, geo2], true);
// Weld duplicate vertices; also generates an index for a non-indexed geometry
const welded = BufferGeometryUtils.mergeVertices(geo2);
// Tangents for normal mapping - the core method needs an index plus position, normal and uv
welded.computeTangents();
// MikkTSpace tangents (the convention glTF normal maps use) come from the addon; this makes merged non-indexed in place
await MikkTSpace.ready;
BufferGeometryUtils.computeMikkTSpaceTangents(merged, MikkTSpace);
Transforming Geometry
const geometry = new THREE.TorusKnotGeometry(0.5, 0.15, 100, 16);
geometry.center(); // move the vertices so the bounding-box center lands on the origin
geometry.computeBoundingBox(); // refresh after vertex changes
const size = new THREE.Vector3();
geometry.boundingBox.getSize(size);
const maxDim = Math.max(size.x, size.y, size.z);
geometry.scale(1 / maxDim, 1 / maxDim, 1 / maxDim); // fit inside a unit cube
const clone = geometry.clone(); // clone first: these methods mutate the geometry
clone.rotateX(Math.PI / 2);
clone.translate(0, 1, 0);
To rotate or scale an object around a point without touching vertex data, set a pivot instead of re-centering the geometry:
// Rotation and scale normally use the object origin; pivot is a point in the object's own coordinates
mesh.pivot = new THREE.Vector3(1, 0, 0); // default null
mesh.rotation.y += 0.05; // now swings around the local point (1, 0, 0)
Morph Targets
const geometry = new THREE.BoxGeometry(1, 1, 1, 4, 4, 4);
const morphPositions = geometry.attributes.position.array.slice();
for (let i = 0; i < morphPositions.length; i += 3) {
morphPositions[i] *= 2; // stretch X
morphPositions[i + 1] *= 0.5; // squash Y
}
geometry.morphAttributes.position = [new THREE.BufferAttribute(morphPositions, 3)];
// Build the mesh after assigning morphAttributes so it picks up the targets
const mesh = new THREE.Mesh(geometry, new THREE.MeshNormalMaterial());
mesh.morphTargetInfluences[0] = 0.5; // 50% blend between base and target
scene.add(mesh);
Performance and Disposal
- Cut draw calls: mergeGeometries for static meshes, InstancedMesh for repeated geometry, BatchedMesh for many different geometries (one multi-draw call on WebGLRenderer where WEBGL_multi_draw is available).
- Segment counts trade smoothness for vertex count; the sphere default of 32 by 16 suits most uses, go higher only for close-ups.
scene.remove(mesh);
mesh.dispose(); // frees renderer state kept for this object only, not children or shared resources
mesh.geometry.dispose(); // frees the GPU buffers at once, even while another mesh shares them (that mesh uploads them again)
mesh.material.dispose(); // its textures need their own texture.dispose()
To free a whole subtree, traverse it as in Common Mistakes. InstancedMesh.dispose() additionally frees its morph texture.
WebGPU and TSL
The geometry classes, InstancedMesh and BatchedMesh keep the API shown above under WebGPURenderer. What differs: the WebGPU backend draws Points at one pixel and issues one draw per BatchedMesh instance, LineLoop is skipped with an error, and per-instance shader data is a TSL node.
import * as THREE from "three/webgpu";
const renderer = new THREE.WebGPURenderer({ antialias: true });
await renderer.init(); // before any other use of the renderer
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111); // the WebGPU canvas is transparent by default
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 5);
renderer.setAnimationLoop(animate);
function animate() {
renderer.render(scene, camera);
}
The WebGPU backend draws points at one pixel whatever the material size. Larger points render as a single Sprite drawn once per position:
import * as THREE from "three/webgpu";
import { instancedBufferAttribute } from "three/tsl";
const count = 1000;
const positions = new Float32Array(count * 3).map(() => (Math.random() - 0.5) * 10);
const material = new THREE.PointsNodeMaterial({
positionNode: instancedBufferAttribute(new THREE.InstancedBufferAttribute(positions, 3)),
size: 0.5, // depth-scaled like PointsMaterial; CSS pixels with sizeAttenuation: false; sizeNode for a node
});
const points = new THREE.Sprite(material);
points.count = count; // how many instances the sprite draws
points.frustumCulled = false; // culling tests one 0.7-unit sphere at the sprite origin, not the positions
scene.add(points);
Custom per-instance attributes are TSL nodes, not GLSL declarations. Build displacement on positionLocal, which already carries the instance transforms:
import * as THREE from "three/webgpu";
import { instancedBufferAttribute, positionLocal } from "three/tsl";
const count = 1000;
const jitter = new THREE.InstancedBufferAttribute(new Float32Array(count * 3).map(() => Math.random() - 0.5), 3);
const material = new THREE.MeshBasicNodeMaterial();
material.positionNode = positionLocal.add(instancedBufferAttribute(jitter).mul(0.5)); // per-instance data, no GLSL
const mesh = new THREE.InstancedMesh(new THREE.BoxGeometry(0.2, 0.2, 0.2), material, count);
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
mesh.setMatrixAt(i, matrix.setPosition(Math.random() * 8 - 4, Math.random() * 8 - 4, Math.random() * 8 - 4));
}
scene.add(mesh);
Common Mistakes
BufferGeometryUtils.computeTangents(geometry)→geometry.computeTangents(): BufferGeometryUtils exports no computeTangents; the core method needs an index plus position, normal and uv attributes.new THREE.BufferAttribute(new Uint8Array(rgbBytes), 3)→new THREE.BufferAttribute(new Uint8Array(rgbBytes), 3, true): unnormalized bytes reach the shader as 0.0 to 255.0 and the colors blow out.geometry.copy(new THREE.BoxGeometry(1, 1, 1));→geometry.copy(new THREE.BoxGeometry(1, 1, 1)); geometry.instanceCount = count;: InstancedBufferGeometry.copy() overwrites instanceCount with the source's undefined, and WebGLRenderer then draws no instances.batchedMesh.addGeometry(boxGeometry)→batchedMesh.addInstance(batchedMesh.addGeometry(boxGeometry)): a geometry added to a BatchedMesh renders only through an instance created with addInstance (required since r166).new THREE.LineLoop(geometry, material)→new THREE.Line(new THREE.BufferGeometry().setFromPoints([...linePoints, linePoints[0]]), material): WebGPURenderer does not support LineLoop; it logs an error and skips the object, so repeat the first point to close a Line.import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js"→import { TextGeometry } from "three/addons/geometries/TextGeometry.js": the examples/jsm specifier does not resolve under the documented import maps, which map three/addons/ but not three/examples/.capsule.parameters.length→capsule.parameters.height: the CapsuleGeometry parameter was renamed fromlengthtoheightin r176, and a fifth parameterheightSegmentswas added.{ font: font, size: 1, height: 0.2 }→{ font: font, size: 1, depth: 0.2 }: the TextGeometry extrusion option was renamed fromheighttodepthin r163; an old height is ignored and the text extrudes the default 50 units.new THREE.Points(cloud, new THREE.PointsMaterial({ size: 10 }))→new THREE.Sprite(new THREE.PointsNodeMaterial({ positionNode: instancedBufferAttribute(attr) })): the WebGPU backend draws Points at one pixel whatever the size, so larger points need the instanced Sprite form.new THREE.ShaderMaterial({ vertexShader: offsetVertexShader })→material.positionNode = positionLocal.add(instancedBufferAttribute(offsetAttribute)): WebGPURenderer has no node version of ShaderMaterial; it logs that the material is not compatible and falls back to a plain NodeMaterial, so per-instance data there is a TSL node.material.positionNode = positionGeometry.add(offset)→material.positionNode = positionLocal.add(offset): positionLocal already carries skinning, morph targets, batching and instancing; positionGeometry is the raw attribute.mesh.morphTargetInfluences[0] = 0.5→mesh.updateMorphTargets(); mesh.morphTargetInfluences[0] = 0.5;: influences exist only for morph attributes the mesh saw at construction, so refresh them after adding targets to an existing mesh.gltf.scene.dispose()→gltf.scene.traverse((child) => { child.geometry?.dispose(); for (const material of [child.material ?? []].flat()) { for (const value of Object.values(material)) if (value?.isTexture) value.dispose(); material.dispose(); } child.dispose(); }): Object3D.dispose() frees renderer state for that one object only; it does not visit children or dispose geometries, materials or textures.new THREE.BoxBufferGeometry(1, 1, 1)→new THREE.BoxGeometry(1, 1, 1): the*BufferGeometryaliases of the built-in shapes no longer exist; every built-in geometry is already a BufferGeometry.BufferGeometryUtils.mergeBufferGeometries([geo1, geo2])→BufferGeometryUtils.mergeGeometries([geo1, geo2]): the old name is not exported, so the call throws a TypeError.geometry.setAttribute("uv2", geometry.attributes.uv)→material.aoMap = aoTexture;: an aoMap readsuv(channel 0) by default; only a separate baked unwrap goes inuv1, withaoTexture.channel = 1.new THREE.LineBasicMaterial({ linewidth: 4 })→new Line2(new LineGeometry().setPositions(xyzArray), new LineMaterial({ linewidth: 4 })): WebGL and WebGPU draw line primitives one pixel wide whatever linewidth says; Line2, LineGeometry and LineMaterial from three/addons/lines/ draw thick lines on WebGLRenderer, and under WebGPURenderer the Line2 from three/addons/lines/webgpu/Line2.js takes a Line2NodeMaterial from three/webgpu.
See Also
threejs-fundamentals- scene setup, the render loop, Object3D transforms and disposal.threejs-materials- material types and settings for the meshes built here.threejs-shaders- ShaderMaterial vertex and fragment code for WebGLRenderer.threejs-animation- animating morph target influences and transforms over time.threejs-loaders- glTF and other formats that bring their own geometry.threejs-performance- choosing between merging, instancing, batching and LOD by measured draw calls.
