threejs-textures

Comprehensive texture loading, configuration, and application for Three.js scenes. Covers texture types including basic, data, canvas, video, and compressed formats, with loaders for each Supports environment mapping via cube textures, HDR (RGBE/EXR), and equirectangular conversion with PMREM Includes UV mapping, texture atlasing, wrapping modes, filtering, mipmaps, and render targets for dynamic effects Provides PBR material texture setup (color, normal, roughness, metalness, AO, emissive, displacement) with proper color space handling Offers memory management patterns, texture pooling, and performance optimization guidance for web delivery

INSTALLATION
npx skills add https://github.com/cloudai-x/threejs-skills --skill threejs-textures
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$2a

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

  • 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
// 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
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card