Three.js Cheatsheet
Textures
Use this Three.js reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Loading Textures
// Basic load (async, fires callback when ready) const loader = new THREE.TextureLoader(); const texture = loader.load( '/textures/diffuse.jpg', (tex) => console.log('loaded', tex), // onLoad undefined, // onProgress (not supported for textures) (err) => console.error(err) // onError ); // Synchronous-style with async/await const texture = await new Promise((resolve, reject) => { loader.load(url, resolve, undefined, reject); });
LoadingManager
const manager = new THREE.LoadingManager( () => console.log('all loaded'), // onLoad (url, loaded, total) => console.log(`${loaded}/${total} — ${url}`), // onProgress (url) => console.error('error', url) // onError ); const loader = new THREE.TextureLoader(manager); const t1 = loader.load('/tex/a.jpg'); const t2 = loader.load('/tex/b.jpg'); // manager fires onLoad when both t1 and t2 are done
Applying Textures
const mat = new THREE.MeshStandardMaterial({ map: colorTex, normalMap: normalTex, roughnessMap: roughTex, metalnessMap: metalTex, aoMap: aoTex, emissiveMap: emissiveTex, alphaMap: alphaTex, displacementMap: dispTex, }); // Apply after creation mat.map = texture; mat.needsUpdate = true;
Texture Properties
const tex = loader.load('/tex/diffuse.jpg'); // UV repeat and offset tex.wrapS = THREE.RepeatWrapping; // U axis tex.wrapT = THREE.RepeatWrapping; // V axis tex.repeat.set(4, 4); // tile 4x4 tex.offset.set(0, 0); // UV offset (0–1) tex.center.set(0.5, 0.5); // rotation pivot tex.rotation = Math.PI / 4; // Filtering tex.magFilter = THREE.LinearFilter; // enlargement tex.minFilter = THREE.LinearMipmapLinearFilter; // reduction (default, uses mipmaps) // Color space tex.colorSpace = THREE.SRGBColorSpace; // for color/albedo maps // Leave as default LinearSRGBColorSpace for normal/roughness/metalness maps // Flip tex.flipY = false; // GLTF textures need flipY = false // Mipmaps tex.generateMipmaps = true; // default true; set false for DataTexture / RenderTarget tex.needsUpdate = true; // force GPU re-upload
Wrap Modes
| Constant | Description |
|---|---|
THREE.RepeatWrapping | Tile/repeat (most common) |
THREE.ClampToEdgeWrapping | Clamp to border pixel (default) |
THREE.MirroredRepeatWrapping | Mirror every repetition |
Filter Modes
| Filter | Notes |
|---|---|
THREE.NearestFilter | Pixel-art / crisp (no blending) |
THREE.LinearFilter | Bilinear |
THREE.NearestMipmapNearestFilter | Fast, aliased |
THREE.NearestMipmapLinearFilter | Mipmap blending, point sampling |
THREE.LinearMipmapNearestFilter | Bilinear + abrupt mip levels |
THREE.LinearMipmapLinearFilter | Trilinear (best quality, default minFilter) |
magFilteronly supportsNearestFilterorLinearFilter.
Cube Textures (Skybox)
const cubeLoader = new THREE.CubeTextureLoader(); const cubeTexture = cubeLoader.load([ '/skybox/px.jpg', '/skybox/nx.jpg', '/skybox/py.jpg', '/skybox/ny.jpg', '/skybox/pz.jpg', '/skybox/nz.jpg', // order: +x, -x, +y, -y, +z, -z ]); scene.background = cubeTexture; scene.environment = cubeTexture; // IBL for PBR
HDR / EXR Environment Maps
import { RGBELoader } from 'three/addons/loaders/RGBELoader.js'; import { EXRLoader } from 'three/addons/loaders/EXRLoader.js'; const pmrem = new THREE.PMREMGenerator(renderer); pmrem.compileEquirectangularShader(); // RGBE (.hdr) new RGBELoader().load('/env/studio.hdr', (hdrTex) => { const envMap = pmrem.fromEquirectangular(hdrTex).texture; scene.environment = envMap; scene.background = envMap; hdrTex.dispose(); pmrem.dispose(); }); // EXR new EXRLoader().load('/env/scene.exr', (exrTex) => { const envMap = pmrem.fromEquirectangular(exrTex).texture; scene.environment = envMap; exrTex.dispose(); pmrem.dispose(); });
Canvas Texture (dynamic)
const canvas = document.createElement('canvas'); canvas.width = 512; canvas.height = 512; const ctx = canvas.getContext('2d'); ctx.fillStyle = '#000'; ctx.fillRect(0, 0, 512, 512); ctx.fillStyle = '#fff'; ctx.font = '72px sans-serif'; ctx.fillText('Hello!', 50, 260); const tex = new THREE.CanvasTexture(canvas); tex.needsUpdate = true; // call again whenever canvas is redrawn // Live update in animate loop function updateCanvas() { ctx.clearRect(0, 0, 512, 512); ctx.fillText(Date.now(), 50, 260); tex.needsUpdate = true; }
Video Texture
const video = document.createElement('video'); video.src = '/video/clip.mp4'; video.loop = true; video.muted = true; video.autoplay = true; video.play(); const videoTex = new THREE.VideoTexture(video); videoTex.colorSpace = THREE.SRGBColorSpace; const mat = new THREE.MeshBasicMaterial({ map: videoTex }); // VideoTexture auto-updates each frame while video plays
DataTexture (programmatic)
const size = 256; const data = new Uint8Array(4 * size * size); // RGBA for (let i = 0; i < size * size; i++) { const stride = i * 4; data[stride] = Math.random() * 255; // R data[stride + 1] = Math.random() * 255; // G data[stride + 2] = Math.random() * 255; // B data[stride + 3] = 255; // A } const dataTex = new THREE.DataTexture(data, size, size); dataTex.needsUpdate = true; // Format defaults to THREE.RGBAFormat; use THREE.RGFormat, THREE.RedFormat etc. for single-channel
Render Targets
// Off-screen render target (render scene to texture) const rt = new THREE.WebGLRenderTarget(1024, 1024, { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, }); // Render to it renderer.setRenderTarget(rt); renderer.render(scene, camera); renderer.setRenderTarget(null); // back to screen // Use as texture material.map = rt.texture; // Dispose when done rt.dispose();
Cube Render Target (Reflections)
const cubeRT = new THREE.WebGLCubeRenderTarget(256); const cubeCamera = new THREE.CubeCamera(0.1, 100, cubeRT); scene.add(cubeCamera); // Update reflection each frame (or just once for static scenes) cubeCamera.update(renderer, scene); const mat = new THREE.MeshStandardMaterial({ envMap: cubeRT.texture, metalness: 1, roughness: 0, });
Compressed Textures
// KTX2 (Basis Universal — GPU-native compression, best option) import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'; const ktx2Loader = new KTX2Loader() .setTranscoderPath('/basis/') // path to basis_transcoder.wasm .detectSupport(renderer); ktx2Loader.load('/tex/diffuse.ktx2', (tex) => { material.map = tex; material.needsUpdate = true; });
Texture Atlas / Sprite Sheet
const sheet = loader.load('/sprites/sheet.png'); sheet.wrapS = THREE.RepeatWrapping; sheet.wrapT = THREE.RepeatWrapping; const cols = 4, rows = 4; sheet.repeat.set(1 / cols, 1 / rows); // Set frame (col 2, row 1 in a 4x4 sheet) const col = 2, row = 1; sheet.offset.set(col / cols, row / rows);
Gotchas
Always set
texture.colorSpace = THREE.SRGBColorSpaceon albedo/color textures. Leave normal, roughness, metalness, and AO textures at the defaultLinearSRGBColorSpaceto avoid incorrect shading.
Textures must be power-of-two dimensions (256, 512, 1024, 2048…) for mipmaps. Non-POT textures work but disable mipmaps and clamp wrapping automatically.
GLTF-exported textures should have
texture.flipY = false(GLTF uses top-left origin; Three.js default is bottom-left).
needsUpdate = truetriggers a full GPU re-upload — avoid calling every frame.VideoTextureandCanvasTexturehandle this automatically only when content changes.
Large textures are the most common cause of memory issues. Dispose with
texture.dispose()when no longer needed.
When using
aoMaporlightMap, the material expects UV coordinates on theuv2(oruv1in r152+) attribute. Copy UVs if geometry has only one UV set:geo.setAttribute('uv2', geo.getAttribute('uv')).