---
title: "Primitives"
type: concept
tags: [threejs, geometry, primitives, beginner, foundational]
created: 2026-08-02
updated: 2026-08-02
sources: ["raw/web_community-primitives.md"]
confidence: high
threejs_docs_snapshot: "2026-08-02"
---

## 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:

```js
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:

```js
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`:

```js
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:

```js
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 [[concepts/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:

```js
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 helpers `EdgesGeometry` and `WireframeGeometry`.
- **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 [[concepts/loading-3d-models]]) or build a fully custom `BufferGeometry` (see [[concepts/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 [[concepts/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.DoubleSide` on 2D primitives (planes, shape geometry) makes them invisible from the back.
- Setting `side: THREE.DoubleSide` on 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: true` on a material instead of `WireframeGeometry`/`EdgesGeometry` can silently drop or duplicate edges depending on how the underlying triangles share vertices.

## Related Concepts

Primitives produce the `BufferGeometry` data described in [[concepts/geometry-fundamentals]], are shaded by [[concepts/materials]], placed via the [[concepts/scene-graph]], and become the merge/instance targets discussed in [[concepts/performance]] when used in large numbers. See also [[concepts/loading-3d-models]] for shapes beyond what a primitive can express, and [[concepts/threejs-overview]].

## Sources

- `raw/web_community-primitives.md` — manual article: the full primitive catalog, the `addSolidGeometry`/`side: THREE.DoubleSide` pattern, `EdgesGeometry`/`WireframeGeometry`/`LineSegments`, the `TextGeometry` async-loading and recentering pattern, `Points`/`PointsMaterial`/`sizeAttenuation`, and the sphere-subdivision triangle-count cost discussion.
