How to Add a Secondary UV Map in Three.js

This article explains how to correctly apply a secondary UV map (uv2) to a BufferGeometry in Three.js. You will learn how to define the uv2 attribute by either cloning existing UV coordinates or assigning custom ones, which is a necessary step for rendering ambient occlusion (aoMap) and lightmaps on your 3D models.

Why a Secondary UV Map is Needed

In Three.js, materials that utilize lightmaps (lightMap) or ambient occlusion maps (aoMap) require a second set of UV coordinates. While the primary uv attribute defines how color textures wrap around a 3D model, the uv2 attribute determines how light and shadow maps are mapped across the geometry.

Step 1: Clone Existing UVs (Quick Method)

If your lightmap or AO map uses the same layout as your default texture, you can quickly create the uv2 attribute by cloning the primary uv attribute.

// Assuming you have a mesh with BufferGeometry
const geometry = mesh.geometry;

// Check if the primary uv attribute exists
if (geometry.attributes.uv) {
    // Clone the 'uv' attribute and assign it to 'uv2'
    geometry.setAttribute('uv2', geometry.attributes.uv.clone());
}

Step 2: Define Custom UV2 Coordinates

If your secondary map has a unique layout (for example, a non-overlapping lightmap bake from Blender), you must define custom coordinates. You can do this by creating a new Float32Array and assigning it to the geometry as a BufferAttribute.

// Create an array of 2D coordinates (U, V) for each vertex
const uv2Array = new Float32Array([
    0.0, 0.0,  // Vertex 1
    1.0, 0.0,  // Vertex 2
    0.0, 1.0,  // Vertex 3
    // Add coordinates for all vertices...
]);

// Assign the array to the 'uv2' attribute (item size of 2 for U and V)
geometry.setAttribute('uv2', new THREE.BufferAttribute(uv2Array, 2));

Step 3: Apply the Texture to the Material

Once the uv2 attribute is added to the geometry, Three.js will automatically detect it when you apply an aoMap or lightMap to your material.

const textureLoader = new THREE.TextureLoader();
const aoTexture = textureLoader.load('path/to/ambient_occlusion.jpg');

const material = new THREE.MeshStandardMaterial({
    color: 0xffffff,
    aoMap: aoTexture,
    aoMapIntensity: 1.0 // Adjust the intensity of the shadows
});

const mesh = new THREE.Mesh(geometry, material);

By ensuring your BufferGeometry contains the uv2 attribute, Three.js will correctly render complex shadow and light details on your 3D meshes.