How to Map UV Coordinates in Three.js
This article explains the fundamental concept of UV coordinates in 3D graphics and demonstrates how they are applied to geometries in Three.js. You will learn what the U and V axes represent, how Three.js processes these coordinates through vertex attributes, and how to define custom UV mapping for your 3D models and textures.
What Are UV Coordinates?
UV coordinates are two-dimensional coordinates used to map a 2D texture onto the surface of a 3D object. While 3D space uses the X, Y, and Z axes, the 2D texture space uses the U (horizontal) and V (vertical) axes to prevent confusion.
UV values range from 0.0 to 1.0: *
(0, 0) typically represents the bottom-left corner of
the texture image. * (1, 1) represents the top-right
corner of the texture image.
Every vertex in a 3D geometry is assigned a corresponding UV coordinate. When a renderer draws a face between vertices, it interpolates these UV coordinates across the pixels of the face, stretching or wrapping the 2D texture image onto the 3D surface accordingly.
How UVs Work in Three.js
In Three.js, all standard geometries (such as
BoxGeometry, SphereGeometry, and
PlaneGeometry) come with pre-calculated UV coordinates.
When you assign a texture to a material and apply it to one of these
built-in geometries, Three.js automatically maps the texture
correctly.
Under the hood, Three.js stores these coordinates as a vertex
attribute inside the geometry’s BufferGeometry. This
attribute is named 'uv' and is structured as a flat array
of floating-point numbers, where every pair of values represents the U
and V coordinates for a single vertex.
Mapping Custom UV Coordinates
If you are creating custom geometries using
BufferGeometry, you must define the UV coordinates
manually.
To map a 2D texture onto a custom 3D plane made of two triangles (four vertices), you define the vertices in 3D space and assign their corresponding 2D coordinates in the UV array.
Here is how to set up UV coordinates manually in Three.js:
import * as THREE from 'three';
// 1. Create a custom BufferGeometry
const geometry = new THREE.BufferGeometry();
// 2. Define the vertices in 3D space (X, Y, Z)
const vertices = new Float32Array([
-1.0, -1.0, 0.0, // Bottom-left vertex (Index 0)
1.0, -1.0, 0.0, // Bottom-right vertex (Index 1)
1.0, 1.0, 0.0, // Top-right vertex (Index 2)
-1.0, 1.0, 0.0 // Top-left vertex (Index 3)
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// 3. Define the index to draw two triangles
const indices = [
0, 1, 2, // First triangle
0, 2, 3 // Second triangle
];
geometry.setIndex(indices);
// 4. Map UV coordinates (U, V) to match each vertex
const uvs = new Float32Array([
0.0, 0.0, // Connects Bottom-left vertex to Bottom-left texture corner
1.0, 0.0, // Connects Bottom-right vertex to Bottom-right texture corner
1.0, 1.0, // Connects Top-right vertex to Top-right texture corner
0.0, 1.0 // Connects Top-left vertex to Top-left texture corner
]);
// 5. Add the UV attribute to the geometry (dimension size of 2 for U and V)
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
// 6. Apply a texture and render
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('path/to/your/texture.jpg');
const material = new THREE.MeshBasicMaterial({ map: texture });
const mesh = new THREE.Mesh(geometry, material);Modifying UVs in Real-Time
You can access and modify UV coordinates dynamically after a geometry has been created. If you need to shift, scale, or distort how a texture sits on a mesh, you can update the UV buffer attribute directly.
To update the UV attribute of an existing geometry:
const uvAttribute = geometry.attributes.uv;
// Loop through each vertex to scale the UV coordinates by half
for (let i = 0; i < uvAttribute.count; i++) {
let u = uvAttribute.getX(i);
let v = uvAttribute.getY(i);
// Modify the coordinate values
uvAttribute.setXY(i, u * 0.5, v * 0.5);
}
// Tell Three.js to upload the updated UVs to the GPU
uvAttribute.needsUpdate = true;By understanding and manipulation of the uv attribute,
you gain complete control over texture placement, allowing for custom
mapping, tiling behaviors, and dynamic graphical effects on any Three.js
geometry.