Textures and sampling
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:
- Magnified (texels bigger than pixels, up close): you see the stored grid. Blocky or blurry, your choice of filter.
- Minified (texels smaller than pixels, far away): one pixel covers many texels but a naive sample reads only one, and which one it picks changes as the camera moves a hair. That's aliasing, and in motion it shimmers.
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 motion | Minification without mipmaps, or a negative mip bias |
| Everything in the distance is mush | Mip bias too positive, or no anisotropic filtering on a glancing surface |
| Thin bright/dark line along a tiled edge | repeat with bilinear sampling across the 0/1 boundary, or a half-texel UV offset |
| Sprites in an atlas show slivers of their neighbour | Bleeding — no padding gutter, UVs not inset by half a texel |
| Pixel art is blurry | Bilinear filtering where you wanted nearest |
| Texture looks washed out or too dark | sRGB texture read as linear, or vice versa |
| Normal map gives weirdly flat lighting | Normal map flagged as sRGB; it must be linear |
| Mipmaps make an atlas bleed worse at distance | Lower mips average across sub-image boundaries — needs per-tile padding or array textures |