threejs-textures
Three.js textures - texture types, UV mapping, environment maps, texture settings. Use when working with images, UV coordinates, cubemaps, HDR environments, or texture optimization.
By cloudai-x · 8,101 installs
npx skills add cloudai-x/threejs-skills --skill threejs-textures
Source repository · Upstream listing
Three.js Textures
Targets three.js r186. Examples use WebGLRenderer; the WebGPU and TSL section uses WebGPURenderer.
Quick Start
Loading
TextureLoader's onProgress parameter is unsupported and never fires; a LoadingManager reports progress as each file finishes:
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.
Transform: Wrap, Repeat, Offset, Rotation
MirroredRepeatWrapping tiles with a mirror flip on each repeat.
Filtering, Mipmaps, Anisotropy
Texture Types
Base class from any image element:
DataTexture from raw typed array pixels. Its defaults differ from Texture : NearestFilter for both filters, flipY false, mipmaps off:
CanvasTexture for dynamically drawn textures (sets needsUpdate on construction):
VideoTexture for a playing HTMLVideoElement (re uploads every frame, mipmaps off):
VideoFrameTexture for WebCodecs: each decoded frame is pushed by hand:
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)
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):
HDR equirectangular files — set mapping , then assign; both renderers prefilter the environment themselves:
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:
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 :
Do not run a loaded equirect file through PMREMGenerator "for better reflections" — direct assignment with mapping set produces the same prefiltering.
Render Targets
Depth buffer as a texture:
MSAA and multiple attachments:
Dynamic Reflections
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:
Texture Atlas
One image, many sprites, each selected with offset and repeat:
Standard Material Slots
Channel conventions worth knowing: aoMap reads red, roughnessMap reads green, metalnessMap reads blue (so one packed occlusion roughness metalness image works); alphaMap reads green.
alphaMap takes effect with transparent: true (blended) or alphaTest (cut out); displacementMap scales with displacementScale and displacementBias and wants a subdivided mesh; normalMapType defaults to TangentSpaceNormalMap , set ObjectSpaceNormalMap for object space normal maps.
Disposal and Memory
texture.dispose() frees the GPU upload once no other undisposed texture shares it (the atlas clones above share one); the image object remains. Dispose only textures that no other material still uses:
Share one texture instance across materials rather than loading per mesh. Non power of two sizes are fine: WebGL 2 supports mipmaps and repeat wrapping on them.
WebGPU and TSL
TSL sampling: texture(map) uses uv() plus the texture's own offset/repeat/rotation; an explicit node takes over the coordinates entirely. The classic slots ( map , normalMap , roughnessMap , aoMap ) work unchanged on node materials; colorNode is for custom sampling.
KTX2Loader.detectSupport(renderer) queries device features, so call it after await renderer.init() . For dynamic reflections use CubeRenderTarget (not WebGLCubeRenderTarget ) with the same hide update show CubeCamera pattern.
Common Mistakes
geometry.setAttribute("uv2", geometry.attributes.uv) → material.aoMap = aoTexture; : aoMap samples the attribute picked by texture.channel (default 0, uv ), so it needs no extra attribute and uv2 is read only by textures whose channel is 2; only a separate baked unwrap goes in uv1 , with aoTexture.channel = 1 .
new RGBELoader() → new HDRLoader() : renamed from RGBELoader to HDRLoader in r180; the old class still runs but logs a deprecation warning.
import { EXRLoader } from "three/examples/jsm/loaders/EXRLoader.js" → import { EXRLoader } from "three/addons/loaders/EXRLoader.js" : the browser import maps map three/addons/ to examples/jsm/ and define no three/examples/ key, so the examples path fails to resolve there.
texture.encoding = THREE.sRGBEncoding → texture.colorSpace = THREE.SRGBColorSpace : encoding and the sRGBEncoding constant were removed; colorSpace replaced them.
const texture = loader.load("color.jpg") → const texture = loader.load("color.jpg"); texture.colorSpace = THREE.SRGBColorSpace; : an LDR color image left at NoColorSpace renders too bright and washed out.
texture.minFilter = THREE.LinearFilter; // no mipmaps → texture.generateMipmaps = false; : mipmaps are generated whenever generateMipmaps is true, whatever the filter, so LinearFilter alone still uploads them.
texture.generateMipmaps = false; texture.wrapS = THREE.ClampToEdgeWrapping; // non power of two size → texture.wrapS = THREE.RepeatWrapping; : WebGL 2 supports mipmaps and repeat wrapping on non power of two textures, so nothing has to be disabled for them.
texture.anisotropy = renderer.capabilities.getMaxAnisotropy() → texture.anisotropy = renderer.getMaxAnisotropy() : WebGPURenderer has no capabilities object; this is the call under WebGPU.
material.envMapIntensity = 0.5 → scene.environmentIntensity = 0.5 : envMapIntensity scales only the material's own envMap , not scene.environment .
scene.environment = pmrem.fromEquirectangular(hdr).texture → hdr.mapping = THREE.EquirectangularReflectionMapping; scene.environment = hdr; : the renderer prefilters an equirectangular environment itself, so manual PMREM of a loaded file adds nothing.
scene.background = hdr → hdr.mapping = THREE.EquirectangularReflectionMapping; scene.background = hdr; : without mapping , an equirectangular texture draws as a flat screen space quad instead of a surround.
ktx2Loader.detectSupportAsync(renderer) → await renderer.init(); ktx2Loader.detectSupport(renderer); : the async variant was deprecated in r181, and support detection needs the initialized device.
ktx2Loader.setTranscoderPath("libs/basis/") → const ktx2Loader = new KTX2Loader(); ktx2Loader.detectSupport(renderer); : the transcoder loads from the package, so a path is only needed when self hosting, pinned to three@0.186.0.
new THREE.WebGLMultipleRenderTargets(width, height, 2) → new THREE.WebGLRenderTarget(width, height, { count: 2 }) : removed in r162; the attachments are renderTarget.textures[0..count 1] .
new THREE.WebGLCubeRenderTarget(256) → new THREE.CubeRenderTarget(256) : under WebGPURenderer use CubeRenderTarget imported from three/webgpu ; WebGLCubeRenderTarget is WebGL only.
loader.load(url, onLoad, onProgress, onError) → manager.onProgress = (url, loaded, total) = console.log(url, loaded, total) : TextureLoader's onProgress parameter is unsupported and never fires.
mesh.material.map = texture; // after the mesh has rendered → mesh.material.map = texture; mesh.material.needsUpdate = true; : WebGLRenderer builds a material's shader for the maps it has at its first render, so a map added later stays invisible until the shader is rebuilt, which needsUpdate forces.
normalMap.colorSpace = THREE.LinearSRGBColorSpace → normalMap.colorSpace = THREE.NoColorSpace : normal, roughness, metalness, ao, alpha and displacement maps hold data, not color, so they keep the default NoColorSpace .
const texture = await new Promise((resolve, reject) = loader.load(url, resolve, undefined, reject)) → const texture = await loader.loadAsync(url) : loadAsync is that promise, built into the Loader base class.
cubeCamera.update(renderer, scene) → mesh.visible = false; cubeCamera.update(renderer, scene); mesh.visible = true; : while visible, the reflective mesh is drawn into the cube map that its own envMap reads.
See Also
threejs materials — material properties, blending, and physical materials beyond texture slots.
threejs loaders — loading managers, glTF models, and the loader family generally.
threejs shaders — custom GLSL texture sampling under WebGLRenderer.
threejs lighting — environment lighting and light units.
threejs asset pipeline — encoding a model's textures to KTX2, WebP or AVIF with gltf transform.