Command Palette

Search for a command to run...

Hectal
Course outline
Topic 6.4Intermediate ~19 min · step 4 of 7

PBR Texture Maps

In one line

A material's numbers (colour, roughness, metalness) can each be replaced by a picture. Each map is a different kind of data: colour in sRGB, everything else in plain numbers. A normal map changes how light bounces without changing the shape.

0/7 · 0%

Think of it like this

A map of a mountain. One map shows colours (forest, snow), another shows height lines, another which slopes are slippery. Each map describes the same ground, but answers a different question. A PBR material (Topic 5.3) asks each question at every point of the surface, and a texture answers it.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

PBR map
A texture that gives one material property (colour, roughness, metalness, normal, occlusion) at every point of a surface.
normal map
A picture whose colours store tiny surface tilts, so flat triangles light as if they were bumpy.
ambient occlusion (AO)
A pre-made darkening in creases and corners, where little ambient light can reach.
displacement
Really moving vertices up or down using a height picture. It changes the shape, unlike a normal map.

Step by step

01Start with the colour

Load the diffuse picture, set colorSpace = SRGBColorSpace, and assign it to map. This is the first plane of the example: a pattern, but flat.

02Add the normal map

Load nor_gl, leave its colour space alone and assign it to normalMap. The second plane's pattern now has raised diamonds that catch the low light from the left.

03Add roughness and AO

Add roughnessMap and aoMap (both data, both with their defaults). With roughness: 1 on the material, the map's green channel sets the shininess. The change is gentle under direct light.

04Flip a wrong normal map

The second example shows the same normal map with normalScale (1, 1) and (1, -1). With the sign flipped the bumps look like dents: the same look a DirectX map gives.

Try it yourself

  1. 1

    Make the normal map stronger

    In the second example set the first plane's scale to new THREE.Vector2(3, 3). The diamonds look deeper and harsher: normalScale multiplies the tilt. Values above about 2 make surfaces look rubbery and fake, so tune by eye.

  2. 2

    Light it straight on

    In the first example change the light to sun.position.set(0, 0, 5). The second plane looks almost the same as the first: with light coming straight at the surface, small tilts barely change the shading. The third and fourth planes become brighter and patchier (measured in a screenshot), which is the roughness map letting some areas shine. Low, sideways light is how you inspect a normal map; head-on light shows roughness.

Code & diagrams

Four maps, added one at a time New tab

Metal plate, Poly Haven, CC0. The light comes low from the left. The second plane is where the pattern gains faint relief. The roughness and AO steps are subtle with this light: try the head-on light from the first tryIt, and the environment of Topic 6.5.

idle

Console

Run the scene to see its console.

Expected console output

colour map only   | maps: map
+ normalMap       | maps: map, normalMap
+ roughnessMap    | maps: map, normalMap, roughnessMap
+ aoMap           | maps: map, normalMap, roughnessMap, aoMap
textures in GPU memory: 5
A normal map that looks inside-out New tab

Same picture, same light. Left: normalScale (1, 1). Right: (1, -1): the light and shadow sides of every diamond swap, so the bumps read as dents. Flip the sign for a map made for DirectX.

idle

Console

Run the scene to see its console.

Expected console output

normalScale (1, 1)   | normalScale: 1, 1
normalScale (1, -1)  | normalScale: 1, -1

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

A map the material does not read

In the first example change "new THREE.MeshStandardMaterial({ roughness: 1, metalness: 0.2, ...maps })" to "new THREE.MeshBasicMaterial({ ...maps })".

terminal
$ Run
── what you'll see ──
THREE.Material: 'normalMap' is not a property of THREE.MeshBasicMaterial.
THREE.Material: 'roughnessMap' is not a property of THREE.MeshBasicMaterial.
textures in GPU memory: 2
The warnings repeat for each plane that carries these maps (the console shows them as warnings). All four planes look the same, flat and unlit, and the texture count falls from 5 to 2: the maps were never uploaded.

Myth vs fact

Myth

A normal map makes the shape more detailed.

Fact

It changes only the lighting. Look at the silhouette of a plane with a deep normal map: it is still a flat rectangle. To move real vertices you need a displacement map and many triangles.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    The bumpiness is computed per pixel in the fragment shader, so a normal map costs one texture read plus some maths for each pixel on screen, nothing per triangle. Without tangents in the geometry, three.js builds the tilt directions from UV derivatives in the shader.

  • ▸

    When a model is made for a different engine, check the normal map convention before blaming the lighting: OpenGL (green up) is used by glTF, Blender export and Poly Haven's nor_gl; DirectX maps (nor_dx) need normalScale.y = -1.

Remember this

  1. 1

    The problem this solves: one roughness number makes the whole bike equally shiny, but a real part is smooth in one place and scratched in another. A picture per property gives each point its own value. The danger is mixing the kinds: a map of numbers must never be treated as colour (Topic 6.1).

  2. 2

    Which map does what. map: the base colour (sRGB). normalMap: tiny slope changes that make flat triangles look bumpy. roughnessMap: the green channel is roughness at each point. metalnessMap: the blue channel. aoMap: the red channel, hand-baked shadow in crevices. displacementMap: really moves vertices (needs many triangles). alphaMap: transparency (green channel), with transparent or alphaTest. emissiveMap: colour that glows (sRGB).

  3. 3

    The material number multiplies the map. The final roughness is material.roughness × the map's green. A new Standard material has roughness: 1, so the map decides. If you set roughness: 0.2 the map is scaled down to 20%. For metalness, the default is 0, so the map is ignored until you also set metalness: 1. This is the most common reason a map appears to do nothing.

  4. 4

    Normal maps store directions as colours. Red, green and blue hold the x, y and z of a tiny tilt: flat is (128, 128, 255), the pale purple-blue of every normal map. Lighting then behaves as if the surface were bumpy. The silhouette does not change, and a plane stays a plane. Tools make two flavours: OpenGL (green = up) and DirectX (green = down). three.js expects OpenGL. A DirectX map looks inside-out: bumps look like dents. The fix is material.normalScale.y = -1.

  5. 5

    **Data maps use NoColorSpace.** Only map and emissiveMap are colour. The loader's default is right for all the others; if you set SRGBColorSpace on a roughness or normal map, three.js converts numbers that were never colours, and the surface gets the wrong shininess and slope. Pack maps to save texture reads: glTF puts occlusion, roughness and metalness into the red, green and blue of one picture ("ORM"), and the same texture can be assigned to all three slots.

  6. 6

    AO affects ambient light only. Ambient occlusion darkens light that arrives from everywhere (ambient, hemisphere, environment), not a direct light, so it is subtle with only a sun. The docs say it wants a second set of UVs: in r186 it reads the UV set named by aoMap.channel (0 by default), so a map that shares the first UVs works, as here. A separately unwrapped AO map uses texture.channel = 1 and the geometry's uv1 attribute.

Explain it without notes

01

A bike part's metalness map does nothing. What do you check first?

02

Why is a normal map not enough to make a smooth tyre look like it has a real tread silhouette?

Practice

01

A downloaded normal map makes your bumps look like dents. Write the one line that fixes it, and name the cause.

02

Write the code to load a colour map and a normal map for one material with the right colour spaces.

Done when you can

  • Name the property each map controls and which channel it reads

  • Say why the material's number multiplies the map

  • Fix a normal map made for the wrong convention

  • Set the colour space correctly for each map