Transparency and blending
Opaque geometry is order-independent: the depth buffer decides who wins, so you can submit draws in whatever order is fastest. A transparent surface gives that up. Its result depends on what was already in the colour buffer — so it depends on what you drew before it, and in what order. Everything hard about transparency falls out of that one sentence.
The blend equation
Blending is always out = src·srcFactor + dst·dstFactor; the only thing you choose is the two factors. src is what the fragment shader just produced, dst is what is already in the buffer. Three settings cover almost everything:
| Mode | srcFactor | dstFactor | behaviour |
|---|---|---|---|
| Alpha blend | srcAlpha | 1 − srcAlpha | lerp towards src — glass, foliage, UI |
| Additive | srcAlpha or 1 | 1 | only brightens — fire, sparks, magic |
| Multiply | dstColor | 0 | only darkens — tint, stained glass |
Addition and multiplication are commutative, so additive and multiply particles look the same in any order. Alpha blending is not, and that is the whole problem.
Order dependence buys you a second pass
Draw opaque first in any order, then transparents sorted back-to-front. Engines split the frame into an opaque pass (sorted by material to minimise state changes, roughly front-to-back so early-Z throws work away) and a transparent pass (sorted strictly by distance, farthest first). The sort is per-object, so it is approximate: two interpenetrating transparent meshes cannot be ordered correctly at all, and a long quad sorted by its centre pops as the camera passes.
Depth test on, depth write off
A transparent surface must be hidden by the world, but must not hide other transparents. Leave the depth test on and the glass behind a wall correctly disappears. Turn the depth write off and the farther quad you draw next still passes the test. Get it the wrong way round and you see it instantly: with writes on, the first transparent drawn stamps depth and the ones behind it vanish entirely; with the test off, transparents float over the whole scene.
Premultiplied vs straight alpha
Premultiplied means RGB has already been multiplied by alpha, so the blend becomes src·1 + dst·(1 − srcAlpha). Straight alpha breaks under filtering: bilinear and mipmap taps average in the RGB of fully transparent texels, which tools usually leave black, and you get dark fringes around sprites and foliage. Premultiplied values interpolate correctly, and they fold additive and alpha into one mode — alpha 0 with bright RGB is a pure additive glow.
The escape hatch
If you do not need partial transparency, use alpha test and get ordering back for free. discard below a threshold leaves every pixel fully opaque or absent, so the depth buffer sorts as usual — which is why foliage, fences and decals are cutouts. You pay with hard edges (alpha-to-coverage softens them) and a disabled early-Z path. Order-independent transparency (weighted blended OIT, per-pixel linked lists) removes the sort for real, for extra buffers and bandwidth.
Demo
Blending and sort order — break the image with reversed draw order or a stray depth write, and watch one pixel's blend arithmetic in the readout.
Gotchas
| Symptom | Cause |
|---|---|
| Transparent objects disappear behind other transparents | depth writes left on in the transparent pass |
| Transparent objects draw over walls they are behind | depth test turned off, when only the write should be |
| Dark halo around sprites and foliage | straight-alpha texture filtered or mipmapped; use premultiplied |
| Two transparent quads flip order as the camera moves | per-object sort by centre; split the mesh or accept it |
| Additive particles blow out to flat white | additive ignores dst; fewer, dimmer, or tone-map after |
| Transparents come out unlit in a deferred renderer | the G-buffer holds one surface per pixel; transparents need a forward pass |
| Cutout foliage is slower than its triangle count suggests | discard defeats early-Z; keep alpha-tested material sorted and small |