DEV Community

Cover image for Normal Maps vs Displacement Maps in Three.js: What Beginners Get Wrong
Peter Riding
Peter Riding

Posted on

Normal Maps vs Displacement Maps in Three.js: What Beginners Get Wrong

Normal Maps vs. Displacement Maps in Three.js: What They Do and When to Use Them

So today I decided to write about another sticky bit in my Three.js journey (if you're also following Bruno Simon's course, no pun intended). Normal maps and displacement maps get mixed up constantly. They sound similar, they're both "maps," and many people assume they're interchangeable. They aren't. This guide explains clearly what each does, when to use it, and how to implement both correctly in Three.js.

  • Normal map → fakes bumps via lighting. Geometry doesn’t move. Cheap. Use for fine detail.
  • Displacement map → actually moves the mesh’s surface. Shadows, edges, and collisions follow the bumps. Heavier on performance. Best for big shapes or when the outline of the object should change.

Pro tip: Displacement for big shapes, normal maps for micro detail. Combine both for the best results.

This is what I wish someone had explained when I started learning textures in Bruno Simon's Three.js Journey course.

What Each Map Does

  • Normal map → fakes bumps by changing how light hits the surface. The shape doesn’t actually move, so edges and outline stay flat.
  • Displacement map → moves the actual surface of the mesh based on brightness (white = raised, black = flat). Shadows, edges, and intersections update naturally.

Why This Matters in Practice

  • Normal maps: Cheap, mobile-friendly, no extra vertices required. Great for fine detail like scratches, pores, fabric texture.
  • Displacement maps: Real depth, shadows, and occlusion, but require enough vertices to look good. Low-poly meshes will appear blocky. More vertices = higher GPU cost.

Quick Three.js Implementation

// Load textures
const loader = new THREE.TextureLoader();
const colorMap = loader.load('albedo.jpg');
const normalMap = loader.load('normal.jpg');
const displacementMap = loader.load('displacement.jpg');
const aoMap = loader.load('ao.jpg');

// Ensure all maps tile the same way
[colorMap, normalMap, displacementMap, aoMap].forEach(t => {
  t.wrapS = THREE.RepeatWrapping;
  t.wrapT = THREE.RepeatWrapping;
  t.repeat.set(1, 1);
  t.flipY = false; // required for glTF textures
});

// Set color map to sRGB; others stay linear
colorMap.colorSpace = THREE.sRGBColorSpace;
Enter fullscreen mode Exit fullscreen mode

Now create your geometry with enough vertices for displacement:

// Geometry — displacement needs vertices
const geometry = new THREE.PlaneGeometry(5, 5, 200, 200);

// AO maps require uv2
geometry.setAttribute('uv2', new THREE.Float32BufferAttribute(geometry.attributes.uv.array, 2));
Enter fullscreen mode Exit fullscreen mode

Then apply the material:

// Material
const mat = new THREE.MeshStandardMaterial({
  map: colorMap,
  normalMap: normalMap,
  displacementMap: displacementMap,
  displacementScale: 0.3,
  displacementBias: -0.15,
  aoMap: aoMap,
  aoMapIntensity: 1.0,
});

const mesh = new THREE.Mesh(geometry, mat);
scene.add(mesh);
Enter fullscreen mode Exit fullscreen mode

Key Controls: displacementScale & displacementBias

displacementScale

  • Think of this as how tall or deep the bumps are.
  • Small value → tiny bumps, almost flat.
  • Large value → big bumps, very pronounced.

displacementBias

  • Think of this as moving the whole mesh up or down.
  • Negative → lowers the mesh.
  • Positive → raises the mesh.
  • Useful so the mesh doesn’t “sink” below the floor when using a displacement map.

Tip: Use a GUI (e.g., lil-gui) to tweak these interactively while watching shadows.

Common Mistakes & Fixes

  • Displacement looks blocky: Increase segments or use LOD.
  • Normal map looks inverted: Check normalScale and orientation.
  • Mesh shifts after displacement: Remember vertices actually move; anything parented or baked will follow.

When to Use What

  • Mobile / low-power: Normal map only.
  • Close-ups with silhouette changes: Displacement + normal.
  • Large terrain / world: Displacement is fine but use LOD or multiple mesh resolutions to reduce GPU load.

Top comments (0)