Custom Vertices and Faces in Three.js BufferGeometry

This article provides a practical guide on how to create custom 3D shapes in Three.js using the BufferGeometry class. You will learn how to define custom vertex coordinates, group them into triangular faces using indices, and apply these attributes to render a custom mesh in your 3D scene.

To define custom geometry in modern Three.js, you must use BufferGeometry. Unlike the deprecated Geometry class, BufferGeometry stores data in raw binary arrays called BufferAttributes, which are highly optimized for the GPU.

Step 1: Define the Vertices

Vertices are points in 3D space defined by X, Y, and Z coordinates. To define vertices, create a flat Float32Array where every three consecutive numbers represent the X, Y, and Z coordinates of a single point.

import * as THREE from 'three';

const geometry = new THREE.BufferGeometry();

// Define 4 vertices for a flat square (quad)
const vertices = new Float32Array([
    -1.0, -1.0,  0.0, // Vertex 0 (bottom-left)
     1.0, -1.0,  0.0, // Vertex 1 (bottom-right)
     1.0,  1.0,  0.0, // Vertex 2 (top-right)
    -1.0,  1.0,  0.0  // Vertex 3 (top-left)
]);

// Add the vertices to the geometry as the 'position' attribute
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

The second argument of THREE.BufferAttribute (in this case, 3) specifies how many values in the array represent a single vertex.

Step 2: Define the Faces (Indices)

Faces in Three.js are always built using triangles. Instead of duplicating vertex coordinates for shared edges, you define face “indices.” An index array references the vertex positions by their order of definition (starting at index 0).

To draw a quad, you need two triangles: one using vertices 0, 1, and 2, and another using vertices 0, 2, and 3.

// Define the index array to connect vertices into triangles
const indices = new Uint16Array([
    0, 1, 2, // First triangle
    0, 2, 3  // Second triangle
]);

// Set the index of the geometry
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

Using setIndex tells Three.js how to connect the vertices to form the geometric faces.

Step 3: Compute Normals for Lighting

If your custom mesh interacts with light sources, you need to define face normals. Normals tell the renderer which direction a face is pointing. Instead of calculating these manually, Three.js can compute them automatically:

geometry.computeVertexNormals();

Step 4: Create and Render the Mesh

With the geometry defined, you can now combine it with a material and add it to your scene.

const material = new THREE.MeshStandardMaterial({ color: 0xff0000, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);

scene.add(mesh);

By managing vertex positions and face index arrays directly, you can programmatically construct any 2D or 3D shape within Three.js.