Back to UI/UX Design

threejs-textures

Three.jstextures3D graphicsWebGLUV mappingenvironment mapsasset managementperformance optimization
2.4k🕒 2026-01-19Source ↗

Install this skill

npx skills add cloudai-x/threejs-skills

Works across Claude Code, Cursor, Codex, Copilot & Antigravity

Three.js textures function as the visual skin for 3D objects, allowing developers to map 2D images onto meshes. This skill module enables the integration of standard image assets, procedurally generated pixel data, HTML5 canvas elements, and video streams into your scene. It covers the essential pipeline for loading files via TextureLoader or specialized formats like KTX2. Beyond basic application, the module addresses spatial configuration, including tiling strategies, UV offset manipulation, and rotational transforms. It also covers performance-critical adjustments such as mipmapping, anisotropic filtering, and correct color space management—specifically distinguishing between SRGB and linear data textures. Mastery of these controls ensures your materials render accurately across different lighting environments and hardware capabilities, whether you are building a simple UI element or a complex environment map.

When to Use This Skill

  • Wrapping decorative labels or UI graphics onto 3D UI panels
  • Creating dynamic mirrors or interactive displays using video streams
  • Optimizing heavy scenes with compressed KTX2 assets
  • Procedural pattern generation for custom material effects
  • Applying high-fidelity environment maps for physically based rendering

How to Invoke This Skill

Example prompts that trigger this skill in Claude Code, Cursor, or Antigravity:

  • how do I apply a texture to a Three.js mesh
  • set up repeating texture tiling in Three.js
  • load a video file as a 3D object texture
  • how to handle SRGB color space for normal maps in Three.js
  • display a pixelated texture for retro aesthetic

Pro Tips

  • 💡**Color Space Consistency**: Always ensure your color/albedo textures are set to `THREE.SRGBColorSpace` for accurate color reproduction, while non-color data (normals, roughness) should use `THREE.NoColorSpace`.
  • 💡**Mipmapping & Anisotropy**: For textures viewed at varying distances, enable mipmaps and set a suitable `anisotropy` level on your texture to prevent aliasing and improve visual quality from oblique angles.
  • 💡**Texture Packing**: For performance, consider packing multiple grayscale textures (e.g., roughness, metallic, AO) into separate channels of a single RGB image to reduce draw calls and memory footprint.

What this skill does

  • Load external images and GPU-compressed KTX2 files
  • Manage texture tiling, offset, and rotation via UV transforms
  • Apply specific filtering modes including linear, nearest, and anisotropic
  • Dynamic rendering using HTML5 Canvas and video element sources
  • Configure color spaces for albedo vs. data-driven map types

When not to use it

  • Directly manipulating vertex-based lighting effects or geometry shaders
  • Handling massive streaming datasets that require a specialized game engine texture streamer

Example workflow

  1. Initialize a TextureLoader to fetch the image file
  2. Assign the resulting texture object to the map property of a MeshStandardMaterial
  3. Set the colorSpace to SRGBColorSpace for standard color textures
  4. Configure wrapS and wrapT to RepeatWrapping if tiling is required
  5. Attach the material to a geometry and add the mesh to the scene

Prerequisites

  • Basic knowledge of the Three.js scene graph
  • Understanding of UV coordinates
  • Access to a local or remote web server for image asset loading

Pitfalls & limitations

  • !Forgetting to set needsUpdate=true when modifying canvas or custom data textures
  • !Applying SRGB color space to normal or roughness maps which requires Linear color space
  • !Performance degradation from failing to limit texture dimensions to power-of-two for older hardware compatibility

FAQ

Why does my texture look blurry?
Check your minFilter settings. Using LinearMipmapLinearFilter is the default for smooth results, but if you want sharp pixel art, switch to NearestFilter.
Do I need to set colorSpace for all textures?
No, only for color or albedo maps. Normal, metalness, and roughness maps should remain in Linear color space to ensure their data values remain accurate.
How can I rotate a texture on an object?
Modify the texture.rotation property in radians and set texture.center to (0.5, 0.5) to ensure it rotates around the center of the image.

How it compares

This skill provides a structured abstraction over raw WebGL texture units, automating memory management and state updates that would otherwise require deep boilerplate interaction with the underlying graphics API.

Source & trust

2.4k stars🕒 Updated 2026-01-19
📄 Full skill instructions — original source: cloudai-x/threejs-skills
# Three.js Textures

## Quick Start

import * as THREE from "three";

// Load texture
const loader = new THREE.TextureLoader();
const texture = loader.load("texture.jpg");

// Apply to material
const material = new THREE.MeshStandardMaterial({
map: texture,
});


## Texture Loading

### Basic Loading

const loader = new THREE.TextureLoader();

// Async with callbacks
loader.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

function loadTexture(url) {
return new Promise((resolve, reject) => {
new THREE.TextureLoader().load(url, resolve, undefined, reject);
});
}

// Usage
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.

// Color/albedo textures - use sRGB
colorTexture.colorSpace = THREE.SRGBColorSpace;

// Data textures (normal, roughness, metalness, AO) - leave as default
// Do NOT set colorSpace for data textures (NoColorSpace is default)


### Wrapping Modes

texture.wrapS = THREE.RepeatWrapping; // Horizontal
texture.wrapT = THREE.RepeatWrapping; // Vertical

// Options:
// THREE.ClampToEdgeWrapping - Stretches edge pixels (default)
// THREE.RepeatWrapping - Tiles the texture
// THREE.MirroredRepeatWrapping - Tiles with mirror flip


### Repeat, Offset, Rotation

// Tile texture 4x4
texture.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 pivot


### Filtering

// Minification (texture larger than screen pixels)
texture.minFilter = THREE.LinearMipmapLinearFilter; // Default, smooth
texture.minFilter = THREE.NearestFilter; // Pixelated
texture.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

// Usually true by default
texture.generateMipmaps = true;

// Disable for non-power-of-2 textures or data textures
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.

// Create gradient texture
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; // 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

const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext("2d");

// Draw on canvas
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);

// Update when canvas changes
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;

// No need to set needsUpdate - auto-updates


### 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", // +X, -X
"py.jpg",
"ny.jpg", // +Y, -Y
"pz.jpg",
"nz.jpg", // +Z, -Z
]);

// As background
scene.background = cubeTexture;

// As environment map
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; // 0-1, blur background
scene.backgroundIntensity = 1.0; // Brightness
scene.backgroundRotation.y = Math.PI; // Rotate background


## Render Targets

Render to texture for effects.

// Create render target
const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
minFilter: THREE.LinearFilter,
magFilter: THREE.LinearFilter,
format: THREE.RGBAFormat,
});

// Render scene to target
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null); // Back to screen

// Use as texture
material.map = renderTarget.texture;


### Depth Texture

const renderTarget = new THREE.WebGLRenderTarget(512, 512);
renderTarget.depthTexture = new THREE.DepthTexture(
512,
512,
THREE.UnsignedShortType,
);

// Access depth
const depthTexture = renderTarget.depthTexture;


### Multi-Sample Render Target

const renderTarget = new THREE.WebGLRenderTarget(512, 512, {
samples: 4, // MSAA
});


## 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);

// Apply to reflective material
reflectiveMaterial.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

const uvs = geometry.attributes.uv;

// Read UV
const u = uvs.getX(vertexIndex);
const v = uvs.getY(vertexIndex);

// Modify UV
uvs.setXY(vertexIndex, newU, newV);
uvs.needsUpdate = true;


### Second UV Channel (for AO maps)

// Required for aoMap
geometry.setAttribute("uv2", geometry.attributes.uv);

// Or create custom second UV
const uv2 = new Float32Array(vertexCount * 2);
// ... fill uv2 data
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.

// 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/scale
function selectSprite(row, col, gridSize = 2) {
atlas.offset.set(col / gridSize, 1 - (row + 1) / gridSize);
atlas.repeat.set(1 / gridSize, 1 / gridSize);
}

// Select top-left sprite
selectSprite(0, 0);


## Material Texture Maps

### PBR Texture Set

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 AO
geometry.setAttribute("uv2", geometry.attributes.uv);


### Normal Map Types

// OpenGL style normals (default)
material.normalMapType = THREE.TangentSpaceNormalMap;

// Object space normals
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

// Single texture
texture.dispose();

// Material textures
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

1. **Use power-of-2 dimensions**: 256, 512, 1024, 2048
2. **Compress textures**: KTX2/Basis for web delivery
3. **Use texture atlases**: Reduce texture switches
4. **Enable mipmaps**: For distant objects
5. **Limit texture size**: 2048 usually sufficient for web
6. **Reuse textures**: Same texture = better batching

// Check texture memory
console.log(renderer.info.memory.textures);

// Optimize for mobile
const 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 materials
- threejs-loaders - Loading texture files
- threejs-shaders - Custom texture sampling

How to Use This Skill Unit

Option A: Project-Specific (Recommended)

  1. Click "Download" above
  2. In your project, create the directory: .agent/skills/threejs-textures/
  3. Save the file as SKILL.md
  4. The agent will automatically discover the skill based on its description.

Option B: Global Installation (All Agents)

Save the file to these locations to make it available across all projects:

  • Claude Code: ~/.claude/skills/cloudai-x/threejs-skills/threejs-textures/SKILL.md
  • Cursor: ~/.cursor/skills/cloudai-x/threejs-skills/threejs-textures/SKILL.md
  • Antigravity: ~/.gemini/antigravity/skills/cloudai-x/threejs-skills/threejs-textures/SKILL.md

🚀 Install with CLI:
npx skills add cloudai-x/threejs-skills

Read the Master Guide: Mastering Agent Skills

Related Skill Units

Recommended Rules

View more rules

Recommended Workflows

View more workflows

Recommended MCP Servers

View more MCP servers

Take It Further

Maximize your productivity with these powerful resources

📋

Define Your Standards

Set up coding standards to ensure this workflow produces consistent, high-quality results.

Browse Rules Library
📖

Master Workflows

Learn how to create custom workflows, use Turbo Mode, and build your automation library.

Complete Guide

How to use this Skill in Claude Code & Cursor

For Claude Code (CLI)

To use this skill in Claude Code, copy the rule content into your project's custom instructions or follow our Add-Skill CLI guide. This ensures Claude follows your standards during every code generation.

For Cursor & Windsurf

For Cursor or Windsurf, individual skills are best used in the "Rules for AI" section. This specific unit helps the agent avoid ui/ux design issues, leading to cleaner, more efficient code.

Why the skill format matters: the standardized Agent Skills format lets your AI agent load detailed instructions only when they are relevant, keeping your prompt clean while improving results.

Source & attribution

This skill is categorized under UI/UX Design and is published by CloudAI-X, maintained in cloudai-x/threejs-skills.

← Browse All Agent Skills
Sponsored AI assistant. Recommendations may be paid.