Course outline
Loading Textures
In one line
A texture is a picture the GPU reads. TextureLoader fetches it, but the picture arrives later, it can fail, and a colour picture needs one extra line (colorSpace) or it looks washed out.
Think of it like this
You order wallpaper for a model house. The roll comes in the post, so there is a wait, and sometimes the parcel never arrives or holds the wrong thing. When it does arrive, you must say whether it is a photo (colours made for eyes) or a map of numbers. The builder reads them in different ways.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- texture
- A picture the GPU reads to decide the colour (or another value) at each point of a surface.
- texel
- One pixel of a texture. It is called a texel to keep it apart from a pixel of the screen.
- sRGB
- The colour encoding of photos and screens. It stores dark shades finely. A picture in sRGB must be converted before light is computed.
- linear
- Numbers that are real amounts of light: twice the number, twice the light. Lighting maths needs this.
- CORS
- The browser rule that lets one website read another website's files only if that site allows it with a header.
Step by step
01Make a loader and ask for the file
const loader = new THREE.TextureLoader() makes the loader. await loader.loadAsync(url) starts the download and waits. Paths starting with / are relative to your site's root, so /data/3d/textures/x.jpg is the same file on this page and in your project's public/ folder.
02Decide what the picture is
A colour picture (the surface's map, an emissive map) gets colorSpace = THREE.SRGBColorSpace. Everything else (normal, roughness, metalness, AO) is data and keeps the default.
03Put it on a material
material.map = texture. If the material was already drawn without a map, add material.needsUpdate = true once: the shader must be rebuilt to read a texture.
04Plan for failure
Wrap the load in try / catch. Decide the fallback before you need it: a plain colour, the previous texture, or a message in #status (Phase 10).
Try it yourself
- 1
Skip the colour-space line
In the first example delete
texture.colorSpace = THREE.SRGBColorSpace;. The concrete turns lighter and flatter, because the photo's sRGB numbers are read as linear light. Put the line back and the stone darkens. - 2
Load a missing file
In the first example change the file name to
concrete_floor_03_diff_1k.jpg. The code stops atawait loadAsync: the console shows an uncaught#<Event>error (anEvent, not anError), the slab stays plain grey, and nothing after thebefore loadline prints. Wrap the load intry / catchto keep the rest alive.
Code & diagrams
The slab turns while you look at the status bar: the texture count rises only after the first frame. Concrete photo: Poly Haven, CC0.
Console
Run the scene to see its console.Expected console output
before load: textures in GPU memory = 1
loaded: true | size: 1024 x 1024
colorSpace right after load: ""
textures in GPU memory (not used yet): 1
textures in GPU memory after the first frame: 2Real results from this page: a wrong folder, a file that is not a picture and another website all reject with an Event of type error. The last line shows load() returning before the picture exists.
Console
Run the scene to see its console.Expected console output
right path | ok: 1024x1024
wrong folder | failed: Event error
not an image | failed: Event error
other website | failed: Event error
load() returns at once | image so far: null | isTexture: trueBoth tiles use the same texel, 128 out of 255. The left one is told it is sRGB and shows 128. The right one is not told, and the maths treats 128 as a true amount of light: it shows 188, a washed-out look. The numbers are read back from the canvas.
Console
Run the scene to see its console.Expected console output
SRGBColorSpace texel 128 -> screen 128
NoColorSpace texel 128 -> screen 188Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Render before the picture arrives
Use const texture = loader.load(url) (no await) and render at once, as the last line of the second example does.
Myth vs fact
Myth
A failed texture throws an Error with a message I can read.
Fact
It rejects with a plain Event (type: "error"). The real cause is in the browser's network panel: the status code (404), or a CORS message.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
A texture bigger than the GPU's
maxTextureSizeis not refused: three.js shrinks it with a canvas and warnsTexture has been resized from (WxH) to (WxH). Read the limit fromrenderer.capabilities.maxTextureSize(4096 on some phones, 16384 on many desktops) and never ship a bigger picture than your smallest target. - ▸
For 8-bit images in sRGB, three.js uploads an sRGB format, so the GPU converts to linear while sampling, for free and with correct blending of neighbouring texels. The format is chosen at upload, so set
colorSpacebefore the first render. Measured: changing it afterwards does nothing (the screen keeps 188 for the grey texel) untiltexture.needsUpdate = trueuploads the texture again.
Remember this
- 1
The problem this solves: textures are the first thing in this course that comes from outside your code. Outside things are slow, they fail, and they have a meaning three.js cannot guess. Without a plan you get a black plane, a washed-out photo, or a crash on a phone.
- 2
**A texture is a
Textureobject that wraps an image.**new THREE.TextureLoader()fetches the file;loader.loadAsync(url)returns a promise that resolves with the texture once the picture has arrived.loader.load(url)returns a texture at once withimagestillnull: the mesh draws black until the picture arrives (measured: the pixel in the middle of a plane reads0, 0, 0before, and the photo's colour after). Preferawait loadAsync, so the code after it knows the picture is there. - 3
Nothing is on the GPU until it is used. Loading only decodes the picture into memory. The upload to the GPU happens at the first
render()that draws a material using the texture. Open the status bar's texture count in the first example: it stays the same after the load and rises after the first frame. (A scene with a Standard material starts at 1: three.js r186 keeps one small lookup table texture for its lighting.) - 4
**Failure is an
Event, not anError.** If the file is missing, is not an image, or the server refuses it, the promise rejects with anEventwhosetypeis"error"; there is no message and no status code. Usetry / catchand say what you tried to load. The second example shows four cases. One more trap: a wrong letter case (Rubber.jpgforrubber.jpg) works on a Windows dev machine and returns 404 on a Linux server. - 5
CORS is the failure that only happens on other websites. An image from another origin loads only if that server sends an
Access-Control-Allow-Originheader. three.js asks for it (crossOrigin = "anonymous"). If the header is missing the browser blocks the picture and logsAccess to image at '...' from origin '...' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.(the page's own files and files inpublic/never have this problem). The preview here has no network, so this page cannot show it live; the words above come from a real run in Chromium on two local servers. - 6
**
colorSpacesays what the numbers mean. A photo stores colours the way screens show them (sRGB, which spends more numbers on dark shades). The lighting maths works on true amounts of light (linear). So for a colour** map you settexture.colorSpace = THREE.SRGBColorSpace, and three.js converts on the way in.TextureLoaderleaves it asNoColorSpace, which means "treat the numbers as they are". That is right for data maps (Topic 6.4) and wrong for a photo: the third example shows a grey texel of 128 appearing as 188.
Explain it without notes
A friend's texture is washed out and pale. What is the likely cause, and what is the one-line fix?
Why does loadAsync reject with an Event, and how do you find the real reason for a failed texture?
Practice
Write an async function loadColour(url) that returns a ready colour texture, or null when the file fails, and logs the failing URL.
A plane shows a photo that looks too bright. What do you check first?
Done when you can
Load a texture with
await loadAsyncand say when it reaches the GPUSet
colorSpacecorrectly for colour and for data mapsCatch a failed load and say where the real reason is
Explain CORS in one sentence and why local files never meet it