Skip to content

Three.js - Textures

A texture is an image that is mapped to the surface of a 3D model to add color, detail, and realism. In modern 3D graphics, especially with Physically Based Rendering (PBR), textures do more than just add color—they define properties like roughness, metalness, and surface height. This chapter covers how to load and apply these powerful maps.

The foundation of texturing is loading an image file. Three.js provides TextureLoader for this purpose. The loaded texture can then be applied to a material’s map property, which corresponds to the base color.

import * as THREE from 'three';
// 1. Create a TextureLoader instance
const textureLoader = new THREE.TextureLoader();
// 2. Load an image file
const colorTexture = textureLoader.load('/textures/door/color.jpg');
// 3. Apply it to a material
const material = new THREE.MeshStandardMaterial({ map: colorTexture });

Loading is asynchronous. The texture might not be visible immediately. Three.js handles this in the background, but for more complex scenarios, you can use the loader’s callback functions to know when loading is complete.

Configuring Textures: Tiling and Filtering

Section titled “Configuring Textures: Tiling and Filtering”

You can control how a texture repeats (tiles) and how it looks when scaled up or down.

  • Wrapping (Tiling): Use texture.wrapS (horizontal) and texture.wrapT (vertical) with THREE.RepeatWrapping to make a texture tile across a surface. You can then set how many times it repeats with texture.repeat.set(x, y).
  • Filtering: When a texture is stretched over a large area (magFilter) or shrunk onto a small area (minFilter), filtering determines how pixels are rendered. THREE.LinearFilter provides a smooth look, while THREE.NearestFilter gives a sharp, pixelated look. For best quality with minFilter, use one of the Mipmap options like THREE.LinearMipmapLinearFilter.
// Make the texture repeat 4 times horizontally and 2 times vertically
colorTexture.wrapS = THREE.RepeatWrapping;
colorTexture.wrapT = THREE.RepeatWrapping;
colorTexture.repeat.set(4, 2);
// For pixel art or sharp textures
colorTexture.magFilter = THREE.NearestFilter;
// Set colorspace for PBR materials
colorTexture.colorSpace = THREE.SRGBColorSpace;

Important: For PBR materials like MeshStandardMaterial, it is crucial to set the correct colorSpace for textures that contain color information (like the map). THREE.SRGBColorSpace is the correct choice for most color textures.

MeshStandardMaterial simulates realistic lighting by using several types of texture maps. These are typically grayscale or special-purpose images that control specific physical properties of the surface.

  • Normal Map (.normalMap): Adds fine surface detail like bumps, scratches, and grooves without adding more polygons. It fakes detail by manipulating how light reflects off the surface. This is more advanced than a bump map.
  • Ambient Occlusion (AO) Map (.aoMap): A grayscale map that adds fake shadows in crevices and corners where light is blocked. It makes objects look more grounded and less flat. This requires a second set of UV coordinates (uv2).
  • Roughness Map (.roughnessMap): A grayscale map where white means a very rough surface (diffuse reflection, like chalk) and black means a very smooth surface (sharp reflection, like a mirror).
  • Metalness Map (.metalnessMap): A grayscale map where white indicates the surface is 100% metallic and black indicates it is non-metallic (dielectric).
  • Displacement Map (.displacementMap): Unlike a normal map which only fakes detail, a displacement map actually moves the vertices of the geometry. This can create true geometric detail but is computationally expensive and requires a highly subdivided mesh.

This example demonstrates applying a full set of PBR textures to a sphere. It assumes you have a set of textures (color, normal, roughness, etc.) in a /textures/ folder in your public directory. You can find free PBR texture sets on sites like AmbientCG.

import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// Base Setup (Scene, Camera, Renderer, Controls as in previous examples)
// ...
/**
* Textures
*/
const textureLoader = new THREE.TextureLoader();
// Load all the required PBR maps
const colorTexture = textureLoader.load('/textures/metal_plate/color.jpg');
const aoTexture = textureLoader.load('/textures/metal_plate/ambientOcclusion.jpg');
const heightTexture = textureLoader.load('/textures/metal_plate/height.png');
const normalTexture = textureLoader.load('/textures/metal_plate/normal.jpg');
const roughnessTexture = textureLoader.load('/textures/metal_plate/roughness.jpg');
const metalnessTexture = textureLoader.load('/textures/metal_plate/metalness.jpg');
// Set the correct color space for the color texture
colorTexture.colorSpace = THREE.SRGBColorSpace;
/**
* Objects
*/
// Use a highly detailed geometry to see the effects of displacement
const geometry = new THREE.SphereGeometry(1, 64, 64);
// Add the aoMap's UV attribute. It shares the same UVs as the color map.
geometry.setAttribute('uv2', new THREE.BufferAttribute(geometry.attributes.uv.array, 2));
const material = new THREE.MeshStandardMaterial({
map: colorTexture,
aoMap: aoTexture,
aoMapIntensity: 1,
normalMap: normalTexture,
roughnessMap: roughnessTexture,
metalnessMap: metalnessTexture,
displacementMap: heightTexture,
displacementScale: 0.05 // Adjust this value carefully
});
const sphere = new THREE.Mesh(geometry, material);
scene.add(sphere);
/**
* Lights
*/
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const pointLight = new THREE.PointLight(0xffffff, 20);
pointLight.position.set(2, 3, 4);
scene.add(pointLight);
// ... (Animation loop and responsiveness as in previous examples)

Textures are often the largest part of a 3D application’s file size. Optimizing them is critical for fast loading times.

  • File Format: Use .jpg for color and other maps without transparency. Use .png only when you need an alpha channel.
  • Resolution: Use the smallest resolution that still looks good. Keep dimensions to a power of two (e.g., 512x512, 1024x1024) for broad compatibility and mipmapping efficiency.
  • Texture Compression: For production, use tools like Basis Universal to create super-compressed .basis or .ktx2 files that can be decoded efficiently by the GPU, significantly reducing memory usage and load times.