wikis / Three.js / wiki / concepts / primitives.md view as markdown report a mistake
Definition
Primitives are three.js's built-in 3D shape generators — geometry classes that produce a shape at runtime from a small set of parameters, rather than requiring hand-authored vertex data or a modeling program. They are the fastest way to get a shape on screen and are heavily used for prototyping, simple procedural content (a globe, a bar chart of boxes), and learning.
How It Works
The general pattern is: construct a geometry primitive, wrap it in a Mesh with a material, and add it to the scene:
function addSolidGeometry(x, y, geometry) {
const mesh = new THREE.Mesh(geometry, createMaterial());
addObject(x, y, mesh);
}
{
const width = 8;
const height = 8;
const depth = 8;
addSolidGeometry(-2, -2, new THREE.BoxGeometry(width, height, depth));
}
Two-dimensional primitives (PlaneGeometry, ShapeGeometry) have no "inside," so their back faces disappear unless the material is drawn double-sided:
const material = new THREE.MeshPhongMaterial({
side: THREE.DoubleSide,
});
Drawing side: THREE.DoubleSide is slower than drawing one side only, so it should be reserved for materials that actually need it (2D shapes, planes) rather than applied blanket to solid shapes like boxes or spheres whose back faces are never visible anyway.
Line-based primitives. EdgesGeometry and WireframeGeometry are helpers that take another geometry as input and output line-segment geometry rather than a solid: EdgesGeometry keeps only edges where the angle between adjacent faces exceeds a threshold (removing interior triangle-diagonal lines), while WireframeGeometry generates one line segment per edge in the source geometry — useful because a wireframe: true material alone can produce missing lines depending on how the geometry's triangles share points. Both need a dedicated LineSegments object rather than Mesh:
function addLineGeometry(x, y, geometry) {
const material = new THREE.LineBasicMaterial({color: 0x000000});
const mesh = new THREE.LineSegments(geometry, material);
addObject(x, y, mesh);
}
Text is a special case. TextGeometry needs 3D font data loaded asynchronously before it can generate a mesh:
const loader = new FontLoader();
function loadFont(url) {
return new Promise((resolve, reject) => {
loader.load(url, resolve, undefined, reject);
});
}
async function doit() {
const font = await loadFont('resources/threejs/fonts/helvetiker_regular.typeface.json');
const geometry = new TextGeometry('three.js', {
font: font,
size: 3.0,
depth: .2,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.15,
bevelSize: .3,
bevelSegments: 5,
});
const mesh = new THREE.Mesh(geometry, createMaterial());
geometry.computeBoundingBox();
geometry.boundingBox.getCenter(mesh.position).multiplyScalar(-1);
const parent = new THREE.Object3D();
parent.add(mesh);
}
TextGeometry by default rotates around the left edge, not its center; computing the bounding box, negating its center into the mesh's position, and then parenting that mesh to an empty Object3D recenters rotation without disturbing the offset (a small application of the scene graph: children are drawn relative to their parent).
Points. Points (paired with PointsMaterial) draws a point per vertex of a BufferGeometry instead of triangles or lines — useful for particle-style effects:
const geometry = new THREE.SphereGeometry(radius, widthSegments, heightSegments);
const material = new THREE.PointsMaterial({
color: 'red',
size: 0.2, // in world units
});
const points = new THREE.Points(geometry, material);
scene.add(points);
Setting sizeAttenuation: false on PointsMaterial makes points a fixed pixel size (size: 3) regardless of camera distance, instead of the default world-unit sizing that shrinks with distance.
Key Parameters
- The primitive catalog covers boxes, flat circles, cones, cylinders, dodecahedra, extruded 2D shapes with optional bevel (also the basis of
TextGeometry), icosahedra, lathe shapes (a 2D silhouette spun around an axis — lamps, bowling pins, wine glasses), octahedra, parametric surfaces (a function mapping a 2D grid to 3D points), 2D planes, spheres built by projecting triangles onto a sphere, rings (disc with a hole), 2D outlines that get triangulated (ShapeGeometry), spheres, tetrahedra, 3D text, tori (donuts), torus knots, tube geometry (a circle traced down a path), plus the two derived helpersEdgesGeometryandWireframeGeometry. - Subdivision parameters (segments around/height for spheres, cylinders, etc.) trade visual smoothness for triangle count — always check the docs for each shape's specific parameter names rather than assuming they match.
side: THREE.DoubleSide— required on 2D primitives (PlaneGeometry,ShapeGeometry) to avoid disappearing back faces; costs extra render time so avoid it on solids that don't need it.
When To Use
Reach for a primitive whenever a standard shape (box, sphere, plane, cylinder, etc.) covers the need — prototyping, simple procedural scenes, data visualizations built from repeated shapes. For anything a primitive can't express, load geometry from a 3D modeling program's export (.obj, .gltf — see loading 3d models) or build a fully custom BufferGeometry (see geometry fundamentals).
Choose subdivision counts deliberately: a single detailed sphere (say, a globe) can comfortably use a high-triangle-count mesh, but the same triangle budget multiplied across a thousand instances becomes a real performance problem — see the sphere-segment cost example in performance. Planes and boxes, by contrast, usually have no visual reason to subdivide unless the mesh will be warped or deformed later.
Risks & Pitfalls
- Forgetting
side: THREE.DoubleSideon 2D primitives (planes, shape geometry) makes them invisible from the back. - Setting
side: THREE.DoubleSideon solid shapes that don't need it costs render performance for no visual benefit. - Rotating
TextGeometry(or any geometry whose origin isn't at its visual center) without recentering first causes it to spin around the wrong point — an off-center wobble rather than a clean spin. - Choosing high subdivision counts (e.g. a dense sphere) without considering instance count — the real cost is triangles-per-instance times number-of-instances, and it is easy to pick a look that is fine at one instance but catastrophic at a thousand.
- Using
wireframe: trueon a material instead ofWireframeGeometry/EdgesGeometrycan silently drop or duplicate edges depending on how the underlying triangles share vertices.
Related Concepts
Primitives produce the BufferGeometry data described in geometry fundamentals, are shaded by materials, placed via the scene graph, and become the merge/instance targets discussed in performance when used in large numbers. See also loading 3d models for shapes beyond what a primitive can express, and threejs overview.
Sources
raw/web_community-primitives.md— manual article: the full primitive catalog, theaddSolidGeometry/side: THREE.DoubleSidepattern,EdgesGeometry/WireframeGeometry/LineSegments, theTextGeometryasync-loading and recentering pattern,Points/PointsMaterial/sizeAttenuation, and the sphere-subdivision triangle-count cost discussion.
