| name | threejs-textures |
| description | Three.js textures - texture types, UV mapping, environment maps, texture settings. Use when working with images, UV coordinates, cubemaps, HDR environments, or texture optimization. |
| risk | unknown |
| source | community |
Three.js Textures
When to Use
- You need to load, configure, or optimize textures in Three.js.
- The task involves UV mapping, texture settings, cubemaps, environment maps, or HDR texture workflows.
- You are working on surface detail and material inputs rather than geometry or animation.
Quick Start
import * as THREE from "three";
const loader = new THREE.TextureLoader();
const texture = loader.load("texture.jpg");
const material = new THREE.MeshStandardMaterial({
map: texture,
});
Texture Loading
Basic Loading
const loader = new THREE.TextureLoader();
loader.load(
"texture.jpg",
(texture) => console.log("Loaded"),
(progress) => console.log("Progress"),
(error) => console.error("Error"),
);
const texture = loader.load("texture.jpg");
material.map = texture;
Promise Wrapper
function loadTexture(url) {
return new Promise((resolve, reject) => {
new THREE.TextureLoader().load(url, resolve, undefined, reject);
});
}
const [colorMap, normalMap, roughnessMap] = await Promise.all([
loadTexture("color.jpg"),
loadTexture("normal.jpg"),
loadTexture("roughness.jpg"),
]);
Texture Configuration
Color Space
Critical for accurate color reproduction.
colorTexture.colorSpace = THREE.SRGBColorSpace;
Wrapping Modes
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
Repeat, Offset, Rotation
texture.repeat.set(4, 4);
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.offset.set(0.5, 0.5);
texture.rotation = Math.PI / 4;
texture.center.set(0.5, 0.5);
Filtering
texture.minFilter = THREE.LinearMipmapLinearFilter;
texture.minFilter = THREE.NearestFilter;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.magFilter = THREE.NearestFilter;
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();
Generate Mipmaps
texture.generateMipmaps = true;
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter;
Texture Types
Regular Texture
const texture = new THREE.Texture(image);
texture.needsUpdate = true;
Data Texture
Create texture from raw data.
const 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;
data[index + 1] = j;
data[index + 2] = 128;
data[index + 3] = 255;
}
}
const texture = new THREE.DataTexture(data, size, size);
texture.needsUpdate = true;
Canvas Texture
const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext("2d");
ctx.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);
texture.needsUpdate = true;
Video Texture
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;
Compressed Textures
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
const loader = new THREE.CubeTextureLoader();
const cubeTexture = loader.load([
"px.jpg",
"nx.jpg",
"py.jpg",
"ny.jpg",
"pz.jpg",
"nz.jpg",
]);
scene.background = cubeTexture;
scene.environment = cubeTexture;
material.envMap = cubeTexture;
Equirectangular to Cubemap
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
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
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
scene.background = texture;
scene.backgroundBlurriness = 0.5;
scene.backgroundIntensity = 1.0;
scene.backgroundRotation.y = Math.PI;
Render Targets
Render to texture for effects.
const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
});
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null);
material.map = renderTarget.texture;
Depth Texture
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
renderTarget.depthTexture = new THREE.DepthTexture(
512,
512,
THREE.UnsignedShortType,
);
const depthTexture = renderTarget.depthTexture;
Multi-Sample Render Target
const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
samples: 4,
});
CubeCamera
Dynamic environment maps for reflections.
const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(256, {
generateMipmaps: true,
minFilter: THREE.LinearMipmapLinearFilter,
});
const cubeCamera = new THREE.CubeCamera(0.1, 1000, cubeRenderTarget);
scene.add(cubeCamera);
reflectiveMaterial.envMap = cubeRenderTarget.texture;
function animate() {
reflectiveObject.visible = false;
cubeCamera.position.copy(reflectiveObject.position);
cubeCamera.update(renderer, scene);
reflectiveObject.visible = true;
}
UV Mapping
Accessing UVs
const uvs = geometry.attributes.uv;
const u = uvs.getX(vertexIndex);
const v = uvs.getY(vertexIndex);
uvs.setXY(vertexIndex, newU, newV);
uvs.needsUpdate = true;
Second UV Channel (for AO maps)
geometry.setAttribute("uv2", geometry.attributes.uv);
const uv2 = new Float32Array(vertexCount * 2);
geometry.setAttribute("uv2", new THREE.BufferAttribute(uv2, 2));
UV Transform in Shader
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.
const atlas = loader.load("atlas.png");
atlas.wrapS = THREE.ClampToEdgeWrapping;
atlas.wrapT = THREE.ClampToEdgeWrapping;
function selectSprite(row, col, gridSize = 2) {
atlas.offset.set(col / gridSize, 1 - (row + 1) / gridSize);
atlas.repeat.set(1 / gridSize, 1 / gridSize);
}
selectSprite(0, 0);
Material Texture Maps
PBR Texture Set
const material = new THREE.MeshStandardMaterial({
map: colorTexture,
normalMap: normalTexture,
normalScale: new THREE.Vector2(1, 1),
roughnessMap: roughnessTexture,
roughness: 1,
metalnessMap: metalnessTexture,
metalness: 1,
aoMap: aoTexture,
aoMapIntensity: 1,
emissiveMap: emissiveTexture,
emissive: 0xffffff,
emissiveIntensity: 1,
displacementMap: displacementTexture,
displacementScale: 0.1,
displacementBias: 0,
alphaMap: alphaTexture,
transparent: true,
});
geometry.setAttribute("uv2", geometry.attributes.uv);
Normal Map Types
material.normalMapType = THREE.TangentSpaceNormalMap;
material.normalMapType = THREE.ObjectSpaceNormalMap;
Procedural Textures
Noise Texture
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
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
texture.dispose();
function 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
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
console.log(renderer.info.memory.textures);
const maxSize = renderer.capabilities.maxTextureSize;
const isMobile = /iPhone|iPad|Android/i.test(navigator.userAgent);
const textureSize = isMobile ? 1024 : 2048;
KTX2Loader BC3 Alpha Fix (r183)
As of r183, KTX2Loader correctly handles BC3 compressed textures with alpha channels, fixing previously incorrect alpha rendering.
ISO 21496-1 Gainmap Metadata (r183)
Three.js r183 supports ISO 21496-1 gainmap metadata in HDR textures, enabling proper tone mapping of gainmap-based HDR images (such as those produced by recent smartphone cameras).
See Also
threejs-materials - Applying textures to materials
threejs-loaders - Loading texture files
threejs-shaders - Custom texture sampling
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.