Three.js Textures
Quick Start
javascript
import * as THREE from "three";
// Load textureconst loader = new THREE.TextureLoader();const texture = loader.load("texture.jpg");
// Apply to materialconst material = new THREE.MeshStandardMaterial({ map: texture,});Texture Loading
Basic Loading
javascript
const loader = new THREE.TextureLoader();
// Async with callbacksloader.load( "texture.jpg", (texture) => console.log("Loaded"), (progress) => console.log("Progress"), (error) => console.error("Error"),);
// Synchronous style (loads async internally)const texture = loader.load("texture.jpg");material.map = texture;Promise Wrapper
javascript
function loadTexture(url) { return new Promise((resolve, reject) => { new THREE.TextureLoader().load(url, resolve, undefined, reject); });}
// Usageconst [colorMap, normalMap, roughnessMap] = await Promise.all([ loadTexture("color.jpg"), loadTexture("normal.jpg"), loadTexture("roughness.jpg"),]);Texture Configuration
Color Space
Critical for accurate color reproduction.
javascript
// Color/albedo textures - use sRGBcolorTexture.colorSpace = THREE.SRGBColorSpace;
// Data textures (normal, roughness, metalness, AO) - leave as default// Do NOT set colorSpace for data textures (NoColorSpace is default)Wrapping Modes
javascript
texture.wrapS = THREE.RepeatWrapping; // Horizontaltexture.wrapT = THREE.RepeatWrapping; // Vertical
// Options:// THREE.ClampToEdgeWrapping - Stretches edge pixels (default)// THREE.RepeatWrapping - Tiles the texture// THREE.MirroredRepeatWrapping - Tiles with mirror flipRepeat, Offset, Rotation
javascript
// Tile texture 4x4texture.repeat.set(4, 4);texture.wrapS = THREE.RepeatWrapping;texture.wrapT = THREE.RepeatWrapping;
// Offset (0-1 range)texture.offset.set(0.5, 0.5);
// Rotation (radians, around center)texture.rotation = Math.PI / 4;texture.center.set(0.5, 0.5); // Rotation pivotFiltering
javascript
// Minification (texture larger than screen pixels)texture.minFilter = THREE.LinearMipmapLinearFilter; // Default, smoothtexture.minFilter = THREE.NearestFilter; // Pixelatedtexture.minFilter = THREE.LinearFilter; // Smooth, no mipmaps
// Magnification (texture smaller than screen pixels)texture.magFilter = THREE.LinearFilter; // Smooth (default)texture.magFilter = THREE.NearestFilter; // Pixelated (retro games)
// Anisotropic filtering (sharper at angles)texture.anisotropy = renderer.capabilities.getMaxAnisotropy();Generate Mipmaps
javascript
// Usually true by defaulttexture.generateMipmaps = true;
// Disable for non-power-of-2 textures or data texturestexture.generateMipmaps = false;texture.minFilter = THREE.LinearFilter;Texture Types
Regular Texture
javascript
const texture = new THREE.Texture(image);texture.needsUpdate = true;Data Texture
Create texture from raw data.
javascript
// Create gradient textureconst size = 256;const data = new Uint8Array(size * size * 4);
for (let i = 0; i < size; i++) { for (let j = 0; j < size; j++) { const index = (i * size + j) * 4; data[index] = i; // R data[index + 1] = j; // G data[index + 2] = 128; // B data[index + 3] = 255; // A }}
const texture = new THREE.DataTexture(data, size, size);texture.needsUpdate = true;Canvas Texture
javascript
const canvas = document.createElement("canvas");canvas.width = 256;canvas.height = 256;const ctx = canvas.getContext("2d");
// Draw on canvasctx.fillStyle = "red";ctx.fillRect(0, 0, 256, 256);ctx.fillStyle = "white";ctx.font = "48px Arial";ctx.fillText("Hello", 50, 150);
const texture = new THREE.CanvasTexture(canvas);
// Update when canvas changestexture.needsUpdate = true;Video Texture
javascript
const video = document.createElement("video");video.src = "video.mp4";video.loop = true;video.muted = true;video.play();
const texture = new THREE.VideoTexture(video);texture.colorSpace = THREE.SRGBColorSpace;
// No need to set needsUpdate - auto-updatesCompressed Textures
javascript
import { KTX2Loader } from "three/examples/jsm/loaders/KTX2Loader.js";
const ktx2Loader = new KTX2Loader();ktx2Loader.setTranscoderPath("path/to/basis/");ktx2Loader.detectSupport(renderer);
ktx2Loader.load("texture.ktx2", (texture) => { material.map = texture;});Cube Textures
For environment maps and skyboxes.
CubeTextureLoader
javascript
const loader = new THREE.CubeTextureLoader();const cubeTexture = loader.load([ "px.jpg", "nx.jpg", // +X, -X "py.jpg", "ny.jpg", // +Y, -Y "pz.jpg", "nz.jpg", // +Z, -Z]);
// As backgroundscene.background = cubeTexture;
// As environment mapscene.environment = cubeTexture;material.envMap = cubeTexture;Equirectangular to Cubemap
javascript
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
const pmremGenerator = new THREE.PMREMGenerator(renderer);pmremGenerator.compileEquirectangularShader();
new RGBELoader().load("environment.hdr", (texture) => { const envMap = pmremGenerator.fromEquirectangular(texture).texture; scene.environment = envMap; scene.background = envMap;
texture.dispose(); pmremGenerator.dispose();});HDR Textures
RGBELoader
javascript
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";
const loader = new RGBELoader();loader.load("environment.hdr", (texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; scene.environment = texture; scene.background = texture;});EXRLoader
javascript
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js";
const loader = new EXRLoader();loader.load("environment.exr", (texture) => { texture.mapping = THREE.EquirectangularReflectionMapping; scene.environment = texture;});Background Options
javascript
scene.background = texture;scene.backgroundBlurriness = 0.5; // 0-1, blur backgroundscene.backgroundIntensity = 1.0; // Brightnessscene.backgroundRotation.y = Math.PI; // Rotate backgroundRender Targets
Render to texture for effects.
javascript
// Create render targetconst renderTarget = new THREE.WebGLRenderTarget(512, 512, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat,});
// Render scene to targetrenderer.setRenderTarget(renderTarget);renderer.render(scene, camera);renderer.setRenderTarget(null); // Back to screen
// Use as texturematerial.map = renderTarget.texture;Depth Texture
javascript
const renderTarget = new THREE.WebGLRenderTarget(512, 512);renderTarget.depthTexture = new THREE.DepthTexture( 512, 512, THREE.UnsignedShortType,);
// Access depthconst depthTexture = renderTarget.depthTexture;Multi-Sample Render Target
javascript
const renderTarget = new THREE.WebGLRenderTarget(512, 512, { samples: 4, // MSAA});CubeCamera
Dynamic environment maps for reflections.
javascript
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, { generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter,});
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);scene.add(cubeCamera);
// Apply to reflective materialreflectiveMaterial.envMap = cubeRenderTarget.texture;
// Update in animation loop (expensive!)function animate() { // Hide reflective object, update env map, show again reflectiveObject.visible = false; cubeCamera.position.copy(reflectiveObject.position); cubeCamera.update(renderer, scene); reflectiveObject.visible = true;}UV Mapping
Accessing UVs
javascript
const uvs = geometry.attributes.uv;
// Read UVconst u = uvs.getX(vertexIndex);const v = uvs.getY(vertexIndex);
// Modify UVuvs.setXY(vertexIndex, newU, newV);uvs.needsUpdate = true;Second UV Channel (for AO maps)
javascript
// Required for aoMapgeometry.setAttribute("uv2", geometry.attributes.uv);
// Or create custom second UVconst uv2 = new Float32Array(vertexCount * 2);// ... fill uv2 datageometry.setAttribute("uv2", new THREE.BufferAttribute(uv2, 2));UV Transform in Shader
javascript
const material = new THREE.ShaderMaterial({ uniforms: { map: { value: texture }, uvOffset: { value: new THREE.Vector2(0, 0) }, uvScale: { value: new THREE.Vector2(1, 1) }, }, vertexShader: ` varying vec2 vUv; uniform vec2 uvOffset; uniform vec2 uvScale;
void main() { vUv = uv * uvScale + uvOffset; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` varying vec2 vUv; uniform sampler2D map;
void main() { gl_FragColor = texture2D(map, vUv); } `,});Texture Atlas
Multiple images in one texture.
javascript
// Atlas with 4 sprites (2x2 grid)const atlas = loader.load("atlas.png");atlas.wrapS = THREE.ClampToEdgeWrapping;atlas.wrapT = THREE.ClampToEdgeWrapping;
// Select sprite by UV offset/scalefunction selectSprite(row, col, gridSize = 2) { atlas.offset.set(col / gridSize, 1 - (row + 1) / gridSize); atlas.repeat.set(1 / gridSize, 1 / gridSize);}
// Select top-left spriteselectSprite(0, 0);Material Texture Maps
PBR Texture Set
javascript
const material = new THREE.MeshStandardMaterial({ // Base color (sRGB) map: colorTexture,
// Surface detail (Linear) normalMap: normalTexture, normalScale: new THREE.Vector2(1, 1),
// Roughness (Linear, grayscale) roughnessMap: roughnessTexture, roughness: 1, // Multiplier
// Metalness (Linear, grayscale) metalnessMap: metalnessTexture, metalness: 1, // Multiplier
// Ambient occlusion (Linear, uses uv2) aoMap: aoTexture, aoMapIntensity: 1,
// Self-illumination (sRGB) emissiveMap: emissiveTexture, emissive: 0xffffff, emissiveIntensity: 1,
// Vertex displacement (Linear) displacementMap: displacementTexture, displacementScale: 0.1, displacementBias: 0,
// Alpha (Linear) alphaMap: alphaTexture, transparent: true,});
// Don't forget UV2 for AOgeometry.setAttribute("uv2", geometry.attributes.uv);Normal Map Types
javascript
// OpenGL style normals (default)material.normalMapType = THREE.TangentSpaceNormalMap;
// Object space normalsmaterial.normalMapType = THREE.ObjectSpaceNormalMap;Procedural Textures
Noise Texture
javascript
function generateNoiseTexture(size = 256) { const data = new Uint8Array(size * size * 4);
for (let i = 0; i < size * size; i++) { const value = Math.random() * 255; data[i * 4] = value; data[i * 4 + 1] = value; data[i * 4 + 2] = value; data[i * 4 + 3] = 255; }
const texture = new THREE.DataTexture(data, size, size); texture.needsUpdate = true; return texture;}Gradient Texture
javascript
function generateGradientTexture(color1, color2, size = 256) { const canvas = document.createElement("canvas"); canvas.width = size; canvas.height = 1; const ctx = canvas.getContext("2d");
const gradient = ctx.createLinearGradient(0, 0, size, 0); gradient.addColorStop(0, color1); gradient.addColorStop(1, color2);
ctx.fillStyle = gradient; ctx.fillRect(0, 0, size, 1);
return new THREE.CanvasTexture(canvas);}Texture Memory Management
Dispose Textures
javascript
// Single texturetexture.dispose();
// Material texturesfunction disposeMaterial(material) { const maps = [ "map", "normalMap", "roughnessMap", "metalnessMap", "aoMap", "emissiveMap", "displacementMap", "alphaMap", "envMap", "lightMap", "bumpMap", "specularMap", ];
maps.forEach((mapName) => { if (material[mapName]) { material[mapName].dispose(); } });
material.dispose();}Texture Pooling
javascript
class TexturePool { constructor() { this.textures = new Map(); this.loader = new THREE.TextureLoader(); }
async get(url) { if (this.textures.has(url)) { return this.textures.get(url); }
const texture = await new Promise((resolve, reject) => { this.loader.load(url, resolve, undefined, reject); });
this.textures.set(url, texture); return texture; }
dispose(url) { const texture = this.textures.get(url); if (texture) { texture.dispose(); this.textures.delete(url); } }
disposeAll() { this.textures.forEach((t) => t.dispose()); this.textures.clear(); }}Performance Tips
- Use power-of-2 dimensions: 256, 512, 1024, 2048
- Compress textures: KTX2/Basis for web delivery
- Use texture atlases: Reduce texture switches
- Enable mipmaps: For distant objects
- Limit texture size: 2048 usually sufficient for web
- Reuse textures: Same texture = better batching
javascript
// Check texture memoryconsole.log(renderer.info.memory.textures);
// Optimize for mobileconst maxSize = renderer.capabilities.maxTextureSize;const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);const textureSize = isMobile ? 1024 : 2048;See Also
threejs-materials- Applying textures to materialsthreejs-loaders- Loading texture filesthreejs-shaders- Custom texture sampling


