A Texture wraps an image source plus Textures's sampler state: wrapS and wrapT, repeat, offset and rotation for the uv transform, magFilter and minFilter, anisotropy (up to renderer.capabilities.getMaxAnisotropy(), 16 on the GTX 1650), generateMipmaps, flipY and colorSpace. Subclasses change where the pixels come from:
| Class | Source | Typical use |
|---|---|---|
| Texture | An image or ImageBitmap | Photos, cover art |
| CanvasTexture | A 2D canvas (Canvas 2D) | Generated labels (Generated Covers) |
| VideoTexture | A <video> element | Trailers (Video Textures) |
| DataTexture | A typed array | Lookup tables, noise |
| CompressedTexture | KTX2, DDS or KTX data | GPU-compressed images (KTX2Loader) |
| CubeTexture | Six images | Skyboxes and reflections |
| DepthTexture | A render target's depth | Shadows, post-processing (EffectComposer) |
Only color data is sRGB: set colorSpace = THREE.SRGBColorSpace on map, emissiveMap and sheen or specular colors, and leave normal, roughness, metalness and ambient-occlusion maps as NoColorSpace (Color Management). After changing a texture's pixels, as a canvas or data texture does, set texture.needsUpdate = true.
<!doctype html>
<style>
body { margin: 0; background: #f5f1e8; font: 12px system-ui, sans-serif; color: #263238; }
canvas { display: block; max-width: 100%; }
.labels { display: flex; width: 600px; max-width: 100%; text-align: center; }
.labels div { flex: 1; }
.ui { padding: 4px 8px; }
</style>
<script type="importmap">
{ "imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.186.1/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.186.1/examples/jsm/" } }
</script>
<div class="labels"><div>CanvasTexture (sRGB)</div><div>DataTexture, NearestFilter</div><div>repeat + rotation</div></div>
<div class="ui">repeat <input type="range" id="rep" min="1" max="6" value="3"> rotation <input type="range" id="rot" min="0" max="90" value="20"></div>
<script type="module">
import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(600, 220);
document.body.prepend(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color('#fbf8f2');
const camera = new THREE.OrthographicCamera(-3, 3, 1.1, -1.1, 0, 10);
camera.position.z = 5;
// 1. CanvasTexture: generated cover art, a color map, so sRGB
const canvas = Object.assign(document.createElement('canvas'), { width: 256, height: 360 });
const g = canvas.getContext('2d');
g.fillStyle = '#e09a10'; g.fillRect(0, 0, 256, 360);
g.fillStyle = '#fff3e0'; g.font = 'bold 40px Georgia';
['Salt', 'and', 'Saffron'].forEach((w, i) => g.fillText(w, 22, 70 + i * 48));
g.font = 'italic 22px Georgia'; g.fillText('Leila Haddad', 22, 330);
const cover = new THREE.CanvasTexture(canvas);
cover.colorSpace = THREE.SRGBColorSpace;
cover.anisotropy = renderer.capabilities.getMaxAnisotropy();
// 2. DataTexture: 8×8 RGBA bytes written in code; NearestFilter keeps the texels square
const size = 8, data = new Uint8Array(size * size * 4);
for (let i = 0; i < size * size; i++) {
const x = i % size, y = Math.floor(i / size), v = (x + y) % 2 ? 255 : 40 + ((x * y * 37) % 160);
data.set([v, v * 0.8, 120, 255], i * 4);
}
const noise = new THREE.DataTexture(data, size, size);
noise.magFilter = THREE.NearestFilter;
noise.colorSpace = THREE.SRGBColorSpace;
noise.needsUpdate = true; // upload the typed array
// 3. A tile with RepeatWrapping, repeat and rotation (the uv transform)
const tileCanvas = Object.assign(document.createElement('canvas'), { width: 64, height: 64 });
const t = tileCanvas.getContext('2d');
t.fillStyle = '#1f5f8b'; t.fillRect(0, 0, 64, 64);
t.fillStyle = '#fff3e0'; t.beginPath(); t.arc(32, 32, 14, 0, 7); t.fill();
const tile = new THREE.CanvasTexture(tileCanvas);
tile.colorSpace = THREE.SRGBColorSpace;
tile.wrapS = tile.wrapT = THREE.RepeatWrapping;
tile.center.set(0.5, 0.5); // rotate about the middle
[cover, noise, tile].forEach((map, i) => {
const plane = new THREE.Mesh(new THREE.PlaneGeometry(1.4, 1.96), new THREE.MeshBasicMaterial({ map }));
plane.position.x = (i - 1) * 2;
scene.add(plane);
});
function update() {
const r = +document.getElementById('rep').value;
tile.repeat.set(r, r);
tile.rotation = THREE.MathUtils.degToRad(+document.getElementById('rot').value);
renderer.render(scene, camera); // uv transforms need no needsUpdate
}
['rep', 'rot'].forEach((id) => document.getElementById(id).addEventListener('input', update));
update();
</script>