cloudai-x/threejs-skills/threejs-textures

Trust tier 3
  • community
Source check pending

Checking that the SKILL.md file is still in the repository.

Three.js textures: image loading, color spaces, UV channels and transforms, texture types from canvas to HDR and KTX2, render targets, texture memory. Use when working with images on materials, UV coordinates, cubemaps, render targets or texture optimization; on WebGL or with WebGPU and TSL.

Install

npx vskill@latest install cloudai-x/threejs-skills/threejs-textures

SKILL.md

Three.js Textures

Targets three.js r186. Examples use WebGLRenderer; the WebGPU and TSL section uses WebGPURenderer.

Quick Start

javascript
import * as THREE from "three";
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
camera.position.set(0, 0, 2);
// Color images must be tagged sRGB or they render too bright and washed out
const texture = new THREE.TextureLoader().load("diffuse.jpg");
texture.colorSpace = THREE.SRGBColorSpace;
const mesh = new THREE.Mesh(
new THREE.PlaneGeometry(2, 2),
new THREE.MeshBasicMaterial({ map: texture }),
);
scene.add(mesh);
renderer.setAnimationLoop(animate);
function animate() {
mesh.rotation.z += 0.01;
renderer.render(scene, camera);
}

Loading

javascript
// load() returns the texture at once; its image arrives asynchronously
const texture = new THREE.TextureLoader().load("diffuse.jpg");
texture.colorSpace = THREE.SRGBColorSpace;
mesh.material.map = texture; // before the first render; afterwards also set mesh.material.needsUpdate = true
// When the next step needs the loaded image, await it
const colorMap = await new THREE.TextureLoader().loadAsync("diffuse.jpg");
colorMap.colorSpace = THREE.SRGBColorSpace;

TextureLoader's onProgress parameter is unsupported and never fires; a LoadingManager reports progress as each file finishes:

javascript
const manager = new THREE.LoadingManager();
manager.onProgress = (url, loaded, total) => console.log(`${loaded}/${total}: ${url}`);
const loader = new THREE.TextureLoader(manager);

Color Space

Color management is on by default and every texture starts at NoColorSpace:

  • LDR color images get SRGBColorSpace: map, emissiveMap, specularMap, sheen color and specular color maps, LDR matcaps, and canvas or video used as color.
  • Scalar data keeps NoColorSpace: normal, bump, displacement, roughness, metalness, ambient-occlusion and alpha maps.
  • HDR environments are LinearSRGBColorSpace; the HDR loaders set that themselves. GLTFLoader sets the color spaces of the textures it loads.
javascript
const colorMap = new THREE.TextureLoader().load("color.jpg");
colorMap.colorSpace = THREE.SRGBColorSpace; // authored color image
const normalMap = new THREE.TextureLoader().load("normal.jpg");
// normalMap stays at NoColorSpace: it is data, not color

Transform: Wrap, Repeat, Offset, Rotation

javascript
const texture = new THREE.TextureLoader().load("diffuse.jpg");
// Tiling requires repeat wrapping; the default clamps edge pixels
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(4, 4); // tile 4x4
texture.offset.set(0.5, 0.5); // shift in UV units
texture.center.set(0.5, 0.5); // pivot for rotation
texture.rotation = Math.PI / 4; // radians

MirroredRepeatWrapping tiles with a mirror flip on each repeat.

Filtering, Mipmaps, Anisotropy

javascript
const texture = new THREE.TextureLoader().load("diffuse.jpg");
// Minification (many texture pixels per screen pixel)
texture.minFilter = THREE.LinearMipmapLinearFilter; // default: trilinear blend between mip levels
texture.minFilter = THREE.NearestFilter; // hard pixel look, samples only the base level
// Magnification (texture magnified on screen)
texture.magFilter = THREE.LinearFilter; // default
texture.magFilter = THREE.NearestFilter; // pixelated
// Mipmaps are generated whenever generateMipmaps is true, whatever the filters.
// Turn generation off to save memory (UI sprites, data textures):
texture.generateMipmaps = false;
texture.minFilter = THREE.LinearFilter; // and do not request a mipmap filter
// Sharper at grazing angles; clamp to what the hardware supports
texture.anisotropy = renderer.capabilities.getMaxAnisotropy();

Texture Types

Base class from any image element:

javascript
const image = new Image();
image.src = "sprite.png";
const imageTexture = new THREE.Texture(image);
imageTexture.colorSpace = THREE.SRGBColorSpace;
image.onload = () => (imageTexture.needsUpdate = true); // upload once the pixels exist

DataTexture from raw typed-array pixels. Its defaults differ from Texture: NearestFilter for both filters, flipY false, mipmaps off:

javascript
const size = 256;
const data = new Uint8Array(size * size * 4); // RGBA, one byte per channel
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 dataTexture = new THREE.DataTexture(data, size, size);
dataTexture.needsUpdate = true;

CanvasTexture for dynamically drawn textures (sets needsUpdate on construction):

javascript
const canvas = document.createElement("canvas");
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#404040";
ctx.fillRect(0, 0, 256, 256);
ctx.fillStyle = "#ffffff";
ctx.font = "48px sans-serif";
ctx.fillText("Hello", 40, 150);
const canvasTexture = new THREE.CanvasTexture(canvas);
canvasTexture.colorSpace = THREE.SRGBColorSpace; // canvas colors are sRGB
// after each redraw of the canvas:
canvasTexture.needsUpdate = true;

VideoTexture for a playing HTMLVideoElement (re-uploads every frame, mipmaps off):

javascript
const video = document.createElement("video");
video.src = "video.mp4";
video.loop = true;
video.muted = true;
video.play();
const videoTexture = new THREE.VideoTexture(video);
videoTexture.colorSpace = THREE.SRGBColorSpace;

VideoFrameTexture for WebCodecs: each decoded frame is pushed by hand:

javascript
const frameTexture = new THREE.VideoFrameTexture();
frameTexture.colorSpace = THREE.SRGBColorSpace;
const decoder = new VideoDecoder({
output(frame) {
if (frameTexture.image instanceof VideoFrame) frameTexture.image.close(); // free the replaced frame, or decoding stalls
frameTexture.setFrame(frame);
},
error: (e) => console.error(e),
});

HTMLTexture draws an HTML element through the browser's HTML-in-Canvas API: the renderer moves the element into its own canvas and re-uploads it on paint events; where the browser lacks that API the texture stays empty.

Compressed Textures (KTX2)

javascript
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
const ktx2Loader = new KTX2Loader();
// No setTranscoderPath call: the transcoder loads from the three package.
// Set one only when self-hosting the decoders, pinned to this release.
ktx2Loader.detectSupport(renderer); // required before loading; works with both renderers
const compressedMap = await ktx2Loader.loadAsync("diffuse.ktx2");
// colorSpace comes from the file's transfer function; nothing to set by hand

Basis Universal KTX2 transcodes to a compressed format the GPU supports: smaller uploads and less memory than PNG or JPEG, which upload uncompressed (where the GPU supports none of the targets, KTX2Loader also falls back to uncompressed RGBA). Compressed textures cannot be flipped or mipmapped at upload: flipY stays false, so author the file in the orientation your UVs expect, and mipmaps must be embedded in the file.

Environment Maps

Cube maps (the loader tags the result sRGB itself):

javascript
const cubeTexture = new THREE.CubeTextureLoader().load([
"px.jpg", "nx.jpg", // +X, -X
"py.jpg", "ny.jpg", // +Y, -Y
"pz.jpg", "nz.jpg", // +Z, -Z
]);
scene.background = cubeTexture;
scene.environment = cubeTexture; // lights Standard and Physical materials (WebGLRenderer also Lambert and Phong)

HDR equirectangular files — set mapping, then assign; both renderers prefilter the environment themselves:

javascript
import { HDRLoader } from "three/addons/loaders/HDRLoader.js";
const env = await new HDRLoader().loadAsync("environment.hdr");
env.mapping = THREE.EquirectangularReflectionMapping; // without it the background draws as a flat screen quad
scene.environment = env;
scene.background = env;

EXRLoader (three/addons/loaders/EXRLoader.js) loads .exr files the same way and UltraHDRLoader loads Ultra HDR JPEGs; both set their own linear color space.

Scene-level controls:

javascript
scene.environmentIntensity = 0.5; // dim the image-based lighting
scene.environmentRotation.y = Math.PI / 4;
scene.backgroundBlurriness = 0.5; // 0-1
scene.backgroundIntensity = 1.0;
scene.backgroundRotation.y = Math.PI;

Both renderers convert an equirectangular environment into a cube map with a larger texture internally (better sampling, more memory); when memory matters, ship a lower-resolution equirect file.

PMREMGenerator is for generated environments — a scene such as RoomEnvironment captured with fromScene:

javascript
import { RoomEnvironment } from "three/addons/environments/RoomEnvironment.js";
const pmrem = new THREE.PMREMGenerator(renderer);
const room = new RoomEnvironment();
scene.environment = pmrem.fromScene(room, 0.04).texture;
room.dispose(); // frees the room's geometries and materials
pmrem.dispose();

Do not run a loaded equirect file through PMREMGenerator "for better reflections" — direct assignment with mapping set produces the same prefiltering.

Render Targets

javascript
const renderTarget = new THREE.WebGLRenderTarget(512, 512);
const rtScene = new THREE.Scene(); // must not contain the mesh that samples the target
rtScene.add(new THREE.Mesh(new THREE.BoxGeometry(), new THREE.MeshNormalMaterial())); // unlit, visible without lights
renderer.setRenderTarget(renderTarget);
renderer.render(rtScene, camera);
renderer.setRenderTarget(null); // back to the canvas
// the output is a texture like any other
mesh.material.map = renderTarget.texture;

Depth buffer as a texture:

javascript
const depthTarget = new THREE.WebGLRenderTarget(512, 512);
depthTarget.depthTexture = new THREE.DepthTexture(512, 512); // default type UnsignedIntType

MSAA and multiple attachments:

javascript
// MSAA: resolved into .texture automatically after rendering
const msaaTarget = new THREE.WebGLRenderTarget(512, 512, { samples: 4 });
// MRT: N color attachments, read back through .textures
const mrtTarget = new THREE.WebGLRenderTarget(512, 512, { count: 2 });
const firstAttachment = mrtTarget.textures[0];
const secondAttachment = mrtTarget.textures[1];

Dynamic 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);
mesh.material.envMap = cubeRenderTarget.texture;
// each frame: hide the reflective mesh so it does not see itself
mesh.visible = false;
cubeCamera.position.copy(mesh.position);
cubeCamera.update(renderer, scene);
mesh.visible = true;

UV Channels

Every UV-mapped material texture samples the UV attribute chosen by texture.channel: 0 reads uv (the default), 1 reads uv1, 2 reads uv2, 3 reads uv3. So aoMap and lightMap work on the default uv set; a separately unwrapped set goes in uv1 with the texture pointed at it:

javascript
const aoTexture = new THREE.TextureLoader().load("ao.jpg");
const geometry = new THREE.BoxGeometry(1, 1, 1);
const uvAttribute = geometry.attributes.uv;
const aoUvs = new Float32Array(uvAttribute.count * 2); // fill with the second unwrap
geometry.setAttribute("uv1", new THREE.BufferAttribute(aoUvs, 2));
const material = new THREE.MeshStandardMaterial({ aoMap: aoTexture });
material.aoMap.channel = 1; // sample "uv1" instead of "uv"
const aoMesh = new THREE.Mesh(geometry, material);
scene.add(aoMesh);

Texture Atlas

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 100 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

Repository stars
2,158
Repository forks
247
7-day trend score
38

Stars and forks are for the whole GitHub repository, not this skill alone. Refreshed 7 May 2026, 5 months ago.

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/cloudai-x/threejs-skills/threejs-textures/badge)](https://verified-skill.com/skills/cloudai-x/threejs-skills/threejs-textures)

Something wrong with this skill or its page? Tell us.