Listen up. If you're building modern applications, understanding Geometries in Three.js 3D WebGL is non-negotiable. This is where simple logic turns into intelligent behavior.
1What a Geometry Actually Is
Welcome to Geometries! A geometry defines the shape of a 3D object. It consists of vertices (points) and faces (triangles connecting those points).
Every mesh you render needs exactly one geometry paired with a material ā the geometry answers 'what shape', the material answers 'what does the surface look like'. THREE.BoxGeometry(1, 1, 1) creates a 1x1x1 cube's worth of vertices and triangle faces, ready to be handed to a mesh.
const geometry = new THREE.BoxGeometry(1, 1, 1);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
2Built-in Primitive Geometries
Three.js comes with many built-in geometries so you don't have to calculate vertices manually. Box, Sphere, Cone, Cylinder, and Torus are the most common, each with its own constructor arguments for size and detail level.
SphereGeometry's constructor takes a radius plus widthSegments and heightSegments ā these segment counts control how many triangles make up the sphere's surface, directly trading visual smoothness for rendering cost.
new THREE.SphereGeometry(radius, widthSegments, heightSegments);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
3The Torus: A Donut-Shaped Geometry
A Torus is mathematically a donut shape ā a ring formed by sweeping a small circle (the 'tube') around a larger circular path. THREE.TorusGeometry(1, 0.4, 16, 100) takes a radius, a tube radius, and two segment counts for the ring and the tube cross-section.
Tori are common for UI elements like loading spinners and progress rings in 3D interfaces, since the shape naturally reads as a closed loop without needing a custom mesh.
const geom = new THREE.TorusGeometry(1, 0.4, 16, 100);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
4Geometries as JSX Tags in R3F
In React Three Fiber, we use lowercase tags for geometries, and pass their constructor parameters via the args array prop ā boxGeometry args={[width, height, depth]} is equivalent to new THREE.BoxGeometry(width, height, depth).
This follows the same convention covered earlier for materials and lights: any Three.js class becomes a lowercase JSX tag, and its constructor arguments become the args array in the exact order the constructor expects them.
<boxGeometry args={[width, height, depth]} />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
5TorusKnot: A Complex Parametric Shape
Let's see a TorusKnot! A TorusKnot is a highly complex mathematical shape that looks like a tangled pretzel ā it's generated by a parametric curve winding repeatedly around a torus rather than a simple sweep.
Despite the visual complexity, THREE.TorusKnotGeometry still takes just 4 simple arguments (radius, tube, tubularSegments, radialSegments) ā the intricate look comes entirely from the underlying math of the knot curve, not from manually placed vertices.
const App = () => {
const meshRef = React.useRef();
useFrame((state, delta) => {
meshRef.current.rotation.y += delta;
meshRef.current.rotation.x += delta * 0.5;
});
return (
<Canvas camera={{ position: [0, 0, 5] }}>
<ambientLight intensity={1} />
<mesh ref={meshRef}>
{/* radius, tube, tubularSegments, radialSegments */}
<torusKnotGeometry args={[1, 0.3, 128, 16]} />
<meshNormalMaterial wireframe />
</mesh>
</Canvas>
);
};
render(<App />);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
6Segments: The Smoothness/Performance Tradeoff
Notice the 'segments' arguments. Geometries are made of triangles. More segments mean a smoother shape, but it costs more performance ā every additional segment multiplies the triangle count the GPU has to process each frame.
A sphereGeometry with 32x32 segments looks convincingly round, while the same sphere at 8x8 segments looks visibly faceted and blocky. Choosing the right segment count is a direct tradeoff between visual fidelity and frame rate, especially when many instances of the same geometry appear on screen at once.
<sphereGeometry args={[1, 32, 32]} /> // Smooth
<sphereGeometry args={[1, 8, 8]} /> // Blocky/Low-poly3D Scene rendered. Objects: 4, Draw Calls: Optimized.
7Low-Poly Style in Practice
Let's see what a 'low-poly' sphere looks like. By reducing the width and height segments to 8, we get a retro, blocky style ā this isn't a rendering limitation, it's a deliberate stylistic choice many games and apps use intentionally.
Low-poly geometry paired with a flat-shading material (like meshNormalMaterial without smoothing) is a popular aesthetic precisely because it's cheap to render while still looking intentional rather than broken, unlike a high-poly sphere rendered at too few segments by accident.
const App = () => {
const meshRef = React.useRef();
useFrame((state, delta) => {
meshRef.current.rotation.y += delta;
});
return (
<Canvas camera={{ position: [0, 0, 3] }}>
<ambientLight intensity={1} />
<mesh ref={meshRef}>
<sphereGeometry args={[1, 8, 8]} />
<meshNormalMaterial wireframe />
</mesh>
</Canvas>
);
};
render(<App />);3D Scene rendered. Objects: 4, Draw Calls: Optimized.
8Choosing the Right Segment Count
If you want a perfectly smooth sphere, you should increase the segment count ā more segments mean more triangles approximating the true curved surface, reducing the visible faceting.
In practice, values above roughly 32 segments per axis produce diminishing visual returns for a typical on-screen sphere size, while still doubling or tripling the triangle count. Profiling your actual scene, rather than defaulting to the highest setting everywhere, keeps performance in check.
<sphereGeometry args={[1, ???, ???]} />3D Scene rendered. Objects: 4, Draw Calls: Optimized.
9Under the Hood: BufferGeometry
Behind the scenes, geometries use a BufferGeometry which stores vertices, colors, and UVs in highly optimized flat arrays (Float32Array) ā every primitive geometry you've used so far (Box, Sphere, Torus) is actually just a pre-built BufferGeometry with the math already worked out for you.
You can build entirely custom shapes by constructing a BufferGeometry directly and setting a 'position' attribute from your own Float32Array of vertex coordinates ā this is the escape hatch used when none of the built-in primitives match what you need, such as procedurally generated terrain.
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([ ... ]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));3D Scene rendered. Objects: 4, Draw Calls: Optimized.
10From Shapes to Surfaces
Great! You now understand shapes and vertices. But right now they are all wireframes or plain colors. Time to learn about Materials! ā geometry alone only defines the 3D form; it says nothing about color, shininess, or how light interacts with the surface.
Every mesh needs both pieces together: the geometry you've just learned to build, paired with a material that determines its final visual appearance. The next lesson picks up exactly where this one leaves off.
// š Geometries unlocked!3D Scene rendered. Objects: 4, Draw Calls: Optimized.
11Step-by-Step Breakdown
Welcome to Geometries! A geometry defines the shape of a 3D object. It consists of vertices (points) and faces (triangles connecting those points).
Three.js comes with many built-in geometries so you don't have to calculate vertices manually. Box, Sphere, Cone, Cylinder, and Torus are the most common.
What mathematical shape is a 'Torus'?
- āA donut
- āA pyramid
- āA cylinder
In React Three Fiber, we use lowercase tags for geometries, and pass their constructor parameters via the args array prop.
Let's see a TorusKnot! A TorusKnot is a highly complex mathematical shape that looks like a tangled pretzel. Watch the 3D preview.
Notice the 'segments' arguments. Geometries are made of triangles. More segments mean a smoother shape, but it costs more performance.
Let's see what a 'low-poly' sphere looks like. By reducing the width and height segments to 8, we get a retro, blocky style.
If you want a perfectly smooth sphere, should you increase or decrease the segment count?
- āIncrease
- āDecrease
Behind the scenes, geometries use a BufferGeometry which stores vertices, colors, and UVs in highly optimized flat arrays (Float32Array).
Which typed array does Three.js use internally to store vertex coordinates for maximum performance?
- āArray
- āFloat32Array
- āInt16Array
Great! You now understand shapes and vertices. But right now they are all wireframes or plain colors. Time to learn about Materials!
Level Up š
Advanced cheat sheets, SEO tricks, and interview prep for this topic.
Browser Support
Fully supported.
Fully supported.
Fully supported.
Fully supported.
Accessibility (A11y)
1Don't Rely on Geometry Shape Alone to Convey Meaning
A torus vs. a sphere vs. a cube is a purely visual distinction with no equivalent in the accessibility tree ā if a geometry's shape encodes meaningful information (like a warning icon shape), pair it with a text label or aria-describedby so the meaning isn't lost to non-sighted users.
<mesh aria-label="Warning indicator (triangle shape)">...</mesh>SEO Implications
- 1
Geometry Constructor Details Are Runtime-Only, Not Crawlable
Segment counts and shape parameters only exist as WebGL buffer data once JavaScript executes in the browser ā a page's SEO value for geometry topics comes from the written explanation of vertices, faces, and segment tradeoffs, not from anything a crawler could observe in the rendered canvas.
Best Practices
Reuse Geometry Instances Across Multiple Meshes Instead of Creating Duplicates
If you need many copies of the same shape (like a forest of identical trees), create one BoxGeometry or SphereGeometry instance and reuse it across every mesh ā geometries are safe to share since they don't hold per-object state like position.
Match Segment Count to Actual On-Screen Size
A sphere that will only ever appear as a small distant object doesn't need 64 segments ā the extra triangles are wasted GPU work the viewer will never perceive. Reserve high segment counts for geometries the camera gets close to.
Frequent Bugs
Creating a brand-new geometry instance inside a component's render function or a useFrame callback instead of once outside it, causing constant reallocation and garbage collection pressure.
Instantiate geometries once ā either as a module-level constant, inside useMemo in React, or declaratively as a JSX tag that R3F manages ā never inside a loop or a function that runs every frame.
Real-World Examples
Data Visualization with Instanced Geometry
3D bar charts and scatter plots commonly reuse a single BoxGeometry or SphereGeometry across thousands of data points via THREE.InstancedMesh, since creating a unique geometry object per data point would be far too expensive for real-time rendering.
const geometry = new THREE.SphereGeometry(0.1, 8, 8);
const instancedMesh = new THREE.InstancedMesh(geometry, material, dataPoints.length);