Textures and sampling

06-textures-and-sampling.md · updated 2026-10-15

A texture is an image; a sample is asking "what colour is the image at this spot?" Everything hard about textures comes from the gap between the grid the image is stored on and the grid you're drawing onto.

UVs and the texel/pixel ratio

UVs say which part of the image goes here; the texel-to-pixel ratio decides whether it will look good.

Each vertex carries a UV in 0–1 space — (0,0) one corner of the image, (1,1) the opposite — and the rasteriser interpolates it across the triangle. A texel is one cell of the stored image; a pixel is one cell of the screen. The ratio between them is the whole story:

Filtering and mipmaps

Nearest picks a texel. Bilinear blends four. Mipmaps make sure there are only ever four worth blending.

Magnifying, nearest snaps to the containing texel (crisp, blocky — right for pixel art) and bilinear blends the 4 nearest (smooth, slightly soft).

Minifying can't be fixed by blending 4 of 400 texels; you need an average of all of them. Mipmaps precompute that: a chain of half-size copies, each a box average of the last, for 33% extra memory. The GPU estimates the pixel's footprint from the UV derivatives, picks the mip where one texel ≈ one pixel, and samples there. Trilinear also blends the two neighbouring levels so the switch isn't a visible band.

Mip bias trades the two failure modes: negative selects a sharper level (crisper, shimmers), positive a blurrier one (stable, mushy). Anisotropic filtering fixes surfaces seen at a glancing angle, where the footprint is a long thin sliver: isotropic selection must use the long axis and over-blurs, while aniso takes several samples along it and keeps the short axis sharp. It is the best-value quality setting there is on ground and road textures.

Wrapping, atlases, colour space

Wrap mode decides what happens outside 0–1; atlases are why that matters.

repeat tiles, clamp stretches the edge texel forever, mirror flips each tile. Pick wrong and you get an obvious seam or a one-pixel smear along an edge.

A texture atlas packs many small images into one so they draw in one batch. The cost: bilinear filtering near a sub-image's border reaches into its neighbour (bleeding), and lower mips average the whole atlas together. Fix it by padding gutters and insetting UVs by half a texel.

Colour textures are sRGB; data textures are not. Albedo and other colour maps are sRGB-encoded and must be converted to linear before any maths. Normal maps, roughness, metalness, masks and heightmaps are numbers, not colours — flag them linear or the numbers come out wrong.

Demo

Sampling a texture onto a receding plane — turn mips off and watch the far half shimmer; hover to see one pixel's footprint drawn over real texels.

Gotchas

Symptom Cause
Distant surfaces crawl and sparkle in motionMinification without mipmaps, or a negative mip bias
Everything in the distance is mushMip bias too positive, or no anisotropic filtering on a glancing surface
Thin bright/dark line along a tiled edgerepeat with bilinear sampling across the 0/1 boundary, or a half-texel UV offset
Sprites in an atlas show slivers of their neighbourBleeding — no padding gutter, UVs not inset by half a texel
Pixel art is blurryBilinear filtering where you wanted nearest
Texture looks washed out or too darksRGB texture read as linear, or vice versa
Normal map gives weirdly flat lightingNormal map flagged as sRGB; it must be linear
Mipmaps make an atlas bleed worse at distanceLower mips average across sub-image boundaries — needs per-tile padding or array textures

Open the demo →

← all notes & demos