Three.js Geometries & Materials Explained — Complete Guide to Shapes, PBR & Textures
Three.js geometries define the shape of your 3D objects (the skeleton), while materials define their appearance (the skin). Together they form a Mesh — the fundamental building block of any Three.js scene.
What You’ll Learn
By the end of this guide, you’ll understand all built-in geometries, the differences between material types (Basic, Standard, Physical, Toon), how to apply textures with UV mapping, create custom BufferGeometry from scratch, and build a complete geometry showcase.
Why Geometries & Materials Matter
The choice of geometry and material determines both visual quality and performance. Using too many polygons kills frame rate; using the wrong material creates flat, lifeless scenes. At DodaTech’s Durga Antivirus Pro, the 3D threat visualization uses custom BufferGeometry to render real-time network node connections and MeshPhysicalMaterial for realistic glass-like threat indicators that glow based on severity.
flowchart LR
A[Mesh] --> B[Geometry = Shape]
A --> C[Material = Appearance]
B --> D[Built-in: Box, Sphere, Torus...]
B --> E[Custom: BufferGeometry]
C --> F[BasicMaterial: Unlit, flat]
C --> G[StandardMaterial: PBR, needs lights]
C --> H[PhysicalMaterial: Glass, clearcoat]
C --> I[ToonMaterial: Cartoon style]
D --> J[Texture: Surface detail]
E --> J
J --> K[Geometry Showcase Project]
style G fill:#44aa88,color:#fff,stroke:none
Geometry = Shape (Think Clay)
A geometry defines the vertex positions — the 3D points that form a shape. Think of it like clay before it’s painted. Three.js provides many ready-to-use geometries.
Built-in Geometries Reference
// Box / Cube — width, height, depth, (optional segments)
new THREE.BoxGeometry(1, 1, 1);
// Sphere — radius, widthSegments, heightSegments
// More segments = smoother but more polygons
new THREE.SphereGeometry(1, 32, 32);
// Cylinder — radiusTop, radiusBottom, height, radialSegments
new THREE.CylinderGeometry(0.8, 0.8, 1.5, 32);
// Cone — radius, height, radialSegments
new THREE.ConeGeometry(1, 1.5, 32);
// Torus (donut) — radius, tube (thickness), radialSegments, tubularSegments
new THREE.TorusGeometry(1, 0.4, 16, 48);
// Torus Knot — like a torus but twisted
new THREE.TorusKnotGeometry(0.8, 0.3, 64, 16);
// Plane — width, height, (optional segments)
new THREE.PlaneGeometry(5, 5);
// Ring — innerRadius, outerRadius, thetaSegments
new THREE.RingGeometry(0.3, 1, 32);
// Circle — radius, segments
new THREE.CircleGeometry(1, 32);
// Tube — follows a 3D curve path
new THREE.TubeGeometry(path, tubularSegments, radius, radialSegments);
// Polyhedron variants
new THREE.DodecahedronGeometry(radius, detail);
new THREE.IcosahedronGeometry(radius, detail);
new THREE.OctahedronGeometry(radius, detail);
new THREE.TetrahedronGeometry(radius, detail);
// Text — requires a font JSON file
new THREE.TextGeometry("Hello", { font: font, size: 1, height: 0.2 });Why segments matter: A sphere with widthSegments: 3 is a crude octahedron. With 64 it’s a smooth ball. Each segment doubles/triples the vertex count. For real-time rendering, 16-32 segments is usually enough — let normal maps fake the detail.
Custom BufferGeometry — Building Shapes from Scratch
When built-in geometries aren’t enough (e.g., data visualization, custom meshes), you create a BufferGeometry by specifying vertices manually.
var geometry = new THREE.BufferGeometry();
// Each vertex needs 3 floats: X, Y, Z
var vertices = new Float32Array([
-1, -1, 0, // vertex 0 (bottom-left)
1, -1, 0, // vertex 1 (bottom-right)
1, 1, 0, // vertex 2 (top-right)
-1, 1, 0 // vertex 3 (top-left)
]);
// Indices define which vertices form triangles (3 indices per triangle)
var indices = [
0, 1, 2, // Triangle 1: bottom-left, bottom-right, top-right
0, 2, 3 // Triangle 2: bottom-left, top-right, top-left
];
// UV coordinates map a 2D texture onto the surface
var uvs = new Float32Array([
0, 0, // vertex 0 → bottom-left of texture
1, 0, // vertex 1 → bottom-right of texture
1, 1, // vertex 2 → top-right of texture
0, 1 // vertex 3 → top-left of texture
]);
geometry.setAttribute("position", new THREE.BufferAttribute(vertices, 3));
geometry.setIndex(indices);
geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
geometry.computeVertexNormals(); // Calculates lighting direction per vertex
Why UV coordinates? UV mapping is how a 2D texture wraps onto a 3D surface. The U and V values (0 to 1) tell Three.js which part of the texture maps to each vertex. Without UVs, textures won’t show up on custom geometry.
Material = Appearance (Think Paint)
The material determines how light interacts with the surface. Three.js offers several types, each with a different purpose.
MeshBasicMaterial — Unlit (No Lights Needed)
The simplest material. It renders a flat color or texture without any lighting calculation. Objects are always fully visible regardless of scene lights.
new THREE.MeshBasicMaterial({
color: 0xff0000, // Red (hex color)
wireframe: false, // Show triangle edges
transparent: false,
opacity: 1.0,
side: THREE.FrontSide, // Render front faces only
map: texture // Optional texture
});When to use: UI elements, wireframe overlays, debug views, or any object that shouldn’t be affected by lighting.
MeshStandardMaterial — PBR (Needs Lights)
Physically-Based Rendering — simulates how real-world materials interact with light. This is your go-to material for realistic scenes. Must have lights in the scene.
new THREE.MeshStandardMaterial({
color: 0x44aa88,
roughness: 0.4, // 0 = mirror smooth, 1 = completely rough
metalness: 0.1, // 0 = non-metal (dielectric), 1 = metal
map: texture, // Albedo/diffuse texture
normalMap: normalTexture, // Fakes surface bumps/dents
roughnessMap: roughnessTexture,
metalnessMap: metalnessTexture,
aoMap: aoTexture, // Ambient occlusion — shadows in crevices
emissive: 0x000000, // Self-illumination color
emissiveIntensity: 1,
envMap: envTexture, // Environment map for reflections
wireframe: false,
transparent: false,
opacity: 1.0,
side: THREE.FrontSide,
flatShading: false // True = faceted look (low-poly style)
});Why roughness and metalness? These two values describe almost every real-world surface:
- Wood: roughness=0.9, metalness=0
- Polished chrome: roughness=0.1, metalness=1
- Plastic: roughness=0.4, metalness=0
- Gold: roughness=0.2, metalness=1
MeshPhysicalMaterial — Extended PBR (More Realistic)
Extends MeshStandardMaterial with advanced features. More expensive but stunning.
new THREE.MeshPhysicalMaterial({
clearcoat: 0.5, // Clear coat layer (car paint)
clearcoatRoughness: 0.3,
reflectivity: 0.5,
ior: 1.5, // Index of refraction (glass = 1.5)
thickness: 0.5, // For transmission effects
transmission: 0.9, // Glass-like transparency with refraction
sheen: 0.3, // Fabric sheen (velvet, satin)
sheenColor: new THREE.Color(0xffffff)
});When to use: Glass, water, car paint, gemstones, fabrics — any surface where light interacts in complex ways.
MeshToonMaterial — Cartoon / Cel-Shaded
Renders with hard color bands instead of smooth gradients — the “anime” or “comic” look.
new THREE.MeshToonMaterial({
color: 0x44aa88,
gradientMap: gradientTexture // Custom ramp texture (3-5 color bands)
});Comparison Table
| Material | Needs Lights | Performance | Use Case |
|---|---|---|---|
| MeshBasicMaterial | No | Fastest | UI, wireframes, debug |
| MeshStandardMaterial | Yes | Moderate | Most 3D objects |
| MeshPhysicalMaterial | Yes | Slower | Glass, water, car paint |
| MeshToonMaterial | Yes | Moderate | Cartoon/cel-shaded style |
| MeshMatcapMaterial | No | Fast | Fixed lighting look |
Textures — Surface Detail
A texture is an image (or video) mapped onto a 3D surface. Without textures, everything looks like smooth plastic.
Loading Image Textures
var loader = new THREE.TextureLoader();
var texture = loader.load("texture.jpg");
texture.wrapS = THREE.RepeatWrapping; // Repeat horizontally
texture.wrapT = THREE.RepeatWrapping; // Repeat vertically
texture.repeat.set(2, 2); // Repeat 2x in both directions
texture.rotation = Math.PI / 4; // Rotate 45 degrees
texture.offset.set(0.5, 0.5); // Shift the texture
texture.anisotropy = 16; // Sharper at angles (0-16, higher = better but slower)
var material = new THREE.MeshStandardMaterial({ map: texture });Why anisotropy matters: When you view a texture at a shallow angle (like a floor receding into the distance), it becomes blurry. Anisotropic filtering reduces this blur. Values up to 16 are supported but 4-8 is usually enough.
Multiple Map Types
A realistic material uses several textures working together:
var loader = new THREE.TextureLoader();
var material = new THREE.MeshStandardMaterial({
map: loader.load("diffuse.jpg"), // Base color (albedo)
normalMap: loader.load("normal.jpg"), // Surface bumps (purple-tinted)
roughnessMap: loader.load("roughness.jpg"), // Roughness variations
metalnessMap: loader.load("metalness.jpg"), // Metal areas
aoMap: loader.load("ao.jpg"), // Ambient occlusion
emissiveMap: loader.load("emissive.jpg"), // Glowing areas
alphaMap: loader.load("alpha.jpg") // Transparency mask
});Why normal maps? A normal map fakes surface detail (bumps, scratches, dents) without adding geometry. A brick wall texture with a normal map looks 3D but only has 2 triangles. Without it, the same wall would need millions of polygons.
Material Utility
// Clone a material (shares the same texture references)
var material2 = material.clone();
// Dispose when no longer needed — frees GPU memory
material.dispose();
geometry.dispose();
texture.dispose();
// Color manipulation
var color = new THREE.Color(0xff0000);
color.setHSL(0.5, 1, 0.5); // HSL: hue (0-1), saturation, lightness
color.multiplyScalar(0.5); // Darken by half
material.color = color;Common Mistakes
1. Texture not showing (no UV coordinates)
Custom BufferGeometry doesn’t automatically have UV coordinates. Textures need UVs to map onto surfaces. Always add geometry.setAttribute("uv", ...) or use a built-in geometry.
2. MeshStandardMaterial renders black
PBR materials need lights. Without at least an AmbientLight, they render black. Add lights or use MeshBasicMaterial for testing.
3. High-poly geometry with too many segments
A sphere with 64×64 segments = 4,096 vertices. Several of these in a scene will kill performance. Use 16-32 segments and normal maps for detail.
4. Texture CORS errors with local files
Opening your HTML via file:// blocks texture loading due to browser security. Use a local server: npx serve . or Python’s python -m http.server.
5. Not disposing old materials/geometries
When replacing objects, old materials/geometries stay in GPU memory, causing memory leaks. Always call .dispose() on replaced materials and geometries.
6. Wrong texture wrapping mode
The default ClampToEdgeWrapping stretches the texture’s edge pixels. If you want a repeating pattern (like tiles), set wrapS and wrapT to RepeatWrapping.
Practice Questions
Q1: What is the difference between MeshStandardMaterial and MeshPhysicalMaterial? A: MeshPhysicalMaterial extends StandardMaterial with clearcoat, transmission (glass), sheen (fabric), and other advanced PBR features. It’s more realistic but slower.
Q2: What are UV coordinates and why do they matter? A: UV coordinates map 2D texture positions to 3D vertices. Each vertex gets a U (horizontal) and V (vertical) value from 0 to 1, defining which part of the texture appears at that vertex. Without UVs, textures don’t appear.
Q3: Why does roughness=0 and metalness=1 create a mirror? A: Roughness=0 means the surface is perfectly smooth (no light scattering). Metalness=1 means all light reflects (no diffuse color). Together, the surface acts as a perfect mirror.
Q4: What’s the performance impact of normal maps vs actual geometry? A: Normal maps are much cheaper. A flat plane with a normal map can look as detailed as a million-polygon surface. The GPU just reads the normal direction from a texture instead of interpolating between vertices.
Q5: How do you create transparent materials?
A: Set transparent: true and opacity: 0.5 on the material. For proper rendering, transparent objects should be rendered last — set renderOrder if needed.
Challenge: Create a custom BufferGeometry that forms a pyramid shape (4 triangular faces + 1 square base). Apply a MeshStandardMaterial with a procedural canvas texture. Hint: a pyramid has 5 vertices and 6 triangles total.
FAQ
Try It Yourself
Copy this complete HTML file, save as geometry-showcase.html, and open in a browser.
<!DOCTYPE html>
<html>
<head>
<title>Geometry Showcase — Three.js</title>
<style>
body { margin: 0; overflow: hidden; }
#controls { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%);
display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; z-index: 10; }
#controls button { padding: 8px 16px; border: none; border-radius: 4px;
background: rgba(255,255,255,0.2); color: white; cursor: pointer;
font-size: 13px; backdrop-filter: blur(4px); }
#controls button:hover { background: rgba(255,255,255,0.4); }
#info { position: absolute; top: 10px; left: 10px; color: white;
font-family: sans-serif; font-size: 14px; background: rgba(0,0,0,0.5);
padding: 8px 12px; border-radius: 4px; z-index: 10; }
</style>
</head>
<body>
<div id="info">Click a button to switch geometry</div>
<div id="controls">
<button onclick="showGeo('box')">Box</button>
<button onclick="showGeo('sphere')">Sphere</button>
<button onclick="showGeo('cylinder')">Cylinder</button>
<button onclick="showGeo('cone')">Cone</button>
<button onclick="showGeo('torus')">Torus</button>
<button onclick="showGeo('knot')">Torus Knot</button>
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
var scene = new THREE.Scene();
scene.background = new THREE.Color(0x1a1a2e);
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(4, 3, 6);
var renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
new OrbitControls(camera, renderer.domElement);
var ambient = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambient);
var light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 7);
scene.add(light);
var light2 = new THREE.DirectionalLight(0x4488ff, 0.3);
light2.position.set(-5, 0, 5);
scene.add(light2);
var mesh;
function createGeometry(type) {
if (mesh) { scene.remove(mesh); mesh.geometry.dispose(); mesh.material.dispose(); }
var geo;
switch (type) {
case "box": geo = new THREE.BoxGeometry(1.5, 1.5, 1.5); break;
case "sphere": geo = new THREE.SphereGeometry(1, 32, 32); break;
case "cylinder": geo = new THREE.CylinderGeometry(0.8, 0.8, 1.5, 32); break;
case "cone": geo = new THREE.ConeGeometry(1, 1.5, 32); break;
case "torus": geo = new THREE.TorusGeometry(1, 0.4, 16, 48); break;
case "knot": geo = new THREE.TorusKnotGeometry(0.8, 0.3, 64, 16); break;
default: geo = new THREE.BoxGeometry(1, 1, 1);
}
var mat = new THREE.MeshStandardMaterial({ color: 0x44aa88, roughness: 0.3, metalness: 0.2 });
mesh = new THREE.Mesh(geo, mat);
scene.add(mesh);
}
createGeometry("torus");
window.showGeo = function(type) {
createGeometry(type);
document.getElementById("info").textContent = "Geometry: " + type.charAt(0).toUpperCase() + type.slice(1);
};
function animate() {
requestAnimationFrame(animate);
if (mesh) { mesh.rotation.x += 0.005; mesh.rotation.y += 0.01; }
renderer.render(scene, camera);
}
animate();
window.addEventListener("resize", function() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
</script>
</body>
</html>What’s Next
| Topic | Description | Link |
|---|---|---|
| Lights & Shadows | Light types, shadow maps, and 3-point lighting | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-lights-shadows/ |
| Animation | Clocks, easing, TWEEN.js, and GSAP | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-animation/ |
| Cameras & Controls | Camera types and control schemes | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-cameras-controls/ |
| Particles | Particle systems and effects | https://tutorials.dodatech.com/frontend/libraries/threejs/threejs-particles/ |
| WebGL | The low-level graphics API | WebGL |
| JavaScript | Language fundamentals | JavaScript |
| HTML | Document structure basics | HTML |
Geometries and materials are the building blocks of every Three.js scene. You now know how to choose the right shape, the right material type, and how to apply textures for realistic surfaces. The same PBR material system powers the threat indicator nodes in Durga Antivirus Pro’s 3D dashboard.
What’s Next
Congratulations on completing this Threejs Geometries Materials tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro