Capítulo 848 de 859

Chapter 848: Textures (Manual)

Core Idea

Loading and using textures in three.js — from a single image on material.map, to per-face material arrays, to the memory realities of GPU texture storage — where uncompressed size (not file size) is what actually matters for memory, and where filtering/mipmaps control how a texture looks at small or oblique sizes.

Key Concepts

  • Basic loading: TextureLoader.load(url) returns a Texture immediately (transparent until the image finishes loading asynchronously); waiting for completion needs either the loader's callback or a shared LoadingManager with onLoad/onProgress.
  • Per-face materials: some geometries accept a material array — BoxGeometry up to 6 (one per face), ConeGeometry 2, CylinderGeometry 3 — but this is less common/performant than a texture atlas (multiple images packed into one texture, selected per-triangle via texture coordinates).
  • Cross-origin images: loading a texture from another domain requires that server to send CORS headers permitting it (e.g. imgur, GitHub); without them the image can't be used and three.js will error.
  • Memory cost, not file size: a texture uses roughly width * height * 4 * 1.33 bytes of GPU memory regardless of how well the source file compresses — a 157KB but 3024×3761 JPEG can consume around 60MB once uploaded, since the GPU generally needs uncompressed data.
  • JPG vs. PNG: JPG is lossy (smaller download), PNG is lossless and supports transparency and is the appropriate choice for non-color data like normal maps — but neither format choice changes the uncompressed GPU memory cost.
  • Filtering and mipmaps: texture.magFilter/minFilter control how pixels are chosen when a texture is drawn larger or smaller than its native size; NearestFilter gives a blocky/pixelated look (e.g. Minecraft-style), LinearFilter interpolates; mipmaps (progressively half-sized, pre-blended copies down to 1×1) let the GPU cheaply pick an appropriately-sized version instead of averaging many source pixels per draw.

Code Examples

function loadColorTexture(path) {
  const texture = loader.load(path);
  texture.colorSpace = THREE.SRGBColorSpace;
  return texture;
}

const cube = new THREE.Mesh(geometry, [
  new THREE.MeshBasicMaterial({ map: loadColorTexture("resources/images/flower-1.jpg") }),
  new THREE.MeshBasicMaterial({ map: loadColorTexture("resources/images/flower-2.jpg") }),
  // ...4 more, one per BoxGeometry face
]);
  • What it demonstrates: assigning a different texture to each of a BoxGeometry's 6 faces via a material array.

Key Takeaways

  1. TextureLoader.load() returns immediately; use its callback or a LoadingManager if you need to know when the image has actually arrived.
  2. A texture's real GPU memory cost is roughly width * height * 4 * 1.33 bytes — driven by pixel dimensions, not file compression or format.
  3. Keep texture dimensions as small as still looks good; a small file size does not mean small memory usage.
  4. Filtering (NearestFilter/LinearFilter) and mipmaps control appearance at minified/magnified sizes — mipmaps in particular avoid expensive per-pixel averaging when a texture is drawn much smaller than its native resolution.

Connects To

  • TextureLoader: the loader this entire chapter is built around.
  • BoxGeometry / ConeGeometry / CylinderGeometry: the geometries that support per-face material arrays.