Three.js lightMap vs aoMap: What is the Difference?

When rendering 3D scenes in Three.js, baking lighting into textures is a highly effective way to achieve realistic visuals without the performance cost of real-time lights and shadows. Two primary texture maps used for this purpose are lightMap and aoMap (Ambient Occlusion map). While both influence how light interacts with a material, they serve distinct purposes: a lightMap stores pre-calculated direct and indirect illumination, whereas an aoMap simulates soft, localized shadows in crevices and corners where ambient light cannot reach. Understanding the technical differences between these two maps is crucial for optimizing your Three.js materials and scene performance.

What is a lightMap?

A lightMap contains baked lighting data, which includes both direct light (such as the sun or lamps) and indirect bounced light (global illumination).

What is an aoMap?

An aoMap (Ambient Occlusion map) is a grayscale texture used to simulate contact shadows in areas where geometry blocks ambient light, such as corners, creases, and joints.

Key Differences

Feature lightMap aoMap
Primary Purpose Bakes overall lighting (brightness, color, and bounced light) onto a surface. Bakes micro-shadows in crevices where ambient light is blocked.
Color Space RGB (Color) Grayscale (Monochrome)
Interaction with Lights Adds light to the material; can completely replace real-time lights. Multiplies (darkens) ambient light; does not affect direct light.
Visual Effect Warmth, cool tones, shadows, and highlights. Soft, realistic depth in corners and cracks.

The UV2 Requirement in Three.js

A critical technical detail in Three.js is that both lightMap and aoMap require a second set of UV coordinates (the uv2 attribute) on your geometry to map correctly.

By default, the standard uv attribute is used for the main texture maps (like map, roughnessMap, and normalMap), which often tile across the surface. Because baked lighting and ambient occlusion maps must map uniquely to the entire 3D object without tiling, they require a unique, non-overlapping UV layout assigned to the uv2 channel.

You can copy the primary UV channel to the secondary UV channel in JavaScript like this:

const geometry = mesh.geometry;
geometry.setAttribute('uv2', new THREE.BufferAttribute(geometry.attributes.uv.array, 2));

When to Use Which?