Colour and Texture

Look closely at a brick wall in almost any video game. It has a thousand tiny variations — mortar lines, chipped corners, subtle grime, a hint of moss. You might imagine the artist modelled every brick as separate geometry. They did no such thing. That entire wall is very often a single flat quadrilateral — four corners, two triangles — with a photograph of a brick wall glued onto it. The geometry is trivial; all the richness lives in an image. This trick, called texture mapping, is how modern graphics gets astonishing detail almost for free. And to pull it off we need two ideas working together: how colour is stored as numbers, and how an image is pinned onto a surface.

RGB: colour is three numbers

A screen makes colour by adding light. Every pixel is three tiny lamps — one red, one green, one blue — and their brightnesses mix in your eye. This is the RGB model, and it is additive: start from black (all lamps off) and add light to build up toward white. Full red plus full green makes yellow; full green plus full blue makes cyan; all three at full makes white.

Each channel's brightness is usually stored in 8 bits, giving 2^8 = 256 levels per channel, numbered 0 to 255. Three channels of 8 bits each is 24-bit colour, and the total number of distinct colours is

2^{24} = 2^{8}\times 2^{8}\times 2^{8} = 256\times256\times256 = 16{,}777{,}216,

the famous "16.7 million colours" — more shades than the human eye can reliably tell apart, which is exactly why 24-bit became the standard. A colour is then just a triple like (255, 128, 0) (a warm orange: full red, half green, no blue).

Mix the channels yourself

Below are three overlapping lamps — one per channel. Drag each slider to change that channel's brightness and watch the overlaps: where two lamps meet you get a secondary colour, where all three meet you head toward white. The readout shows the three 8-bit channel values, (R, G, B), each running 0255. (The three discs are drawn in the page's theme palette rather than literal red/green/blue so the figure stays on-theme in every colour scheme — but the logic of additive mixing is identical.)

Other ways to name a colour

RGB is natural for screens but awkward for humans — "a bit less blue and a touch brighter" is not obvious in RGB numbers. So other models exist for other jobs:

Everyone learns in art class that red and green paint make a muddy brown. So why does the screen insist red light plus green light makes bright yellow? Because they are opposite processes. Screens are additive: they start black and add light, so piling colours on climbs toward white. Paint and ink are subtractive: they start from white paper and each pigment absorbs (subtracts) some wavelengths, so piling colours on sinks toward black/brown.

The two systems even have swapped "primaries": the secondary colours of light — cyan, magenta, yellow — are exactly the primary colours of ink (that is where CMY comes from). If you ever design something on screen in RGB and it prints looking dull and dark, this mismatch is usually the culprit: the bright, glowing colours a monitor can emit simply cannot all be reproduced by subtracting light with ink.

UV coordinates: pinning an image to a surface

Now the second half: how does the brick photo know where to sit on the quad? We give every vertex of the geometry a pair of texture coordinates (u, v), each running from 0 to 1, that say where in the image that corner should pull its colour from. Think of the texture image as a unit square: (0,0) is one corner, (1,1) the opposite one, (0.5, 0.5) the middle. Assign the four corners of the quad the UVs (0,0), (1,0), (1,1), (0,1) and the whole photo stretches neatly across it.

The key point: UVs are stored only at the vertices. But a triangle has thousands of pixels between its corners, and each of those needs its own (u,v) to look up. Where do those come from? Interpolation.

Barycentric interpolation: filling in the middle

Any point P inside a triangle with corners A, B, C can be written as a weighted blend of the three corners:

P = w_1 A + w_2 B + w_3 C, \qquad w_1 + w_2 + w_3 = 1,\quad w_i \ge 0.

The three weights (w_1, w_2, w_3) are the barycentric coordinates of P — they measure how close P is to each corner. At corner A itself the weights are (1,0,0); dead centre they are (\tfrac13,\tfrac13,\tfrac13). The magic is that any per-vertex quantity interpolates the very same way. So the texture coordinate at P is

(u, v)_P = w_1\,(u,v)_A + w_2\,(u,v)_B + w_3\,(u,v)_C,

and the same blend works for colours, normals, or depth. This is exactly how the rasterizer fills a triangle: for each pixel it finds the barycentric weights, blends the corner UVs to get this pixel's (u,v), and reads the texture there. Smooth detail, from three little labels at the corners.

Worked example: interpolating a value

A triangle's three corners carry the texture u-values u_A = 0, u_B = 1, u_C = 0.5. A pixel P has barycentric weights (w_1, w_2, w_3) = (0.25, 0.25, 0.5) — and note they sum to 1, as they must. Then its interpolated u is

u_P = 0.25\,(0) + 0.25\,(1) + 0.5\,(0.5) = 0 + 0.25 + 0.25 = 0.5.

The identical blend gives the interpolated colour. Suppose the corners are red-ish, green-ish and blue-ish and we want the red channel at P with R_A = 200, R_B = 0, R_C = 100: R_P = 0.25(200) + 0.25(0) + 0.5(100) = 50 + 0 + 50 = 100. Do that for all three channels and you have the smoothly blended colour — the reason a shaded triangle fades gracefully from corner to corner instead of showing three hard blocks.

Nothing stops an artist assigning UVs bigger than 1 — in fact it is done on purpose. When a lookup lands outside the unit square, a wrap mode decides what to do:

Clamping a stray value is just \max(0, \min(1, u)): shove anything below 0 up to 0, anything above 1 down to 1. A single line of arithmetic that keeps every lookup safely on the image.