Why "half" isn't half
Set a pixel to 0.5 and you might expect half the light of
1.0. You don't get it. A display turns stored values into emitted
light along a curve close to light = value2.2, so a
stored 0.5 comes out around 0.22 of full brightness —
noticeably dimmer than halfway. That mapping isn't a bug; it's deliberate, and
it's called gamma.
Why not just store light directly?
Because your eyes aren't linear either. Human vision is far more sensitive to differences among dark tones than bright ones. If you spread a limited set of integer codes (8 bits, say) evenly across light, you burn most of them on highlights you can't tell apart and leave visible banding in the shadows. Spreading them evenly across encoded values matches perception — more codes where the eye actually needs them.
The top strip steps evenly through linear light: the dark end lurches and the bright end barely changes — a wasteful use of steps. The bottom strip steps evenly through stored values, and those read as even jumps in brightness. That match is the entire reason encoding exists.
Blend in light, not in code
Here's where it bites. To blend two colors — a fade, a 50/50 mix, the average of four texels — you have to average the light, not the stored values. Averaging encoded values averages two points on a curve, which lands below the true midpoint and reads as muddy and dark. The fix is three steps: decode to linear, blend, re-encode.
That's exactly the toggle at the top of this page. In linear the fade
blends correctly; in encoded the middle sags. The gray proof makes it
undeniable: a field of one-pixel black/white lines physically averages to
linear 50% gray, and only the patch mixed in linear light matches it — the
naive 0.5 patch sits too bright beside it.