Grain does not survive the delivery encode

A shader looks correct in the engine, gets captured, gets encoded, and arrives on a store page looking soft and blotchy. Nothing in the shader changed. The encode is a stage in the pipeline and it has opinions about high frequencies.

The same amplitude of grain, both halves put through the same simulated encode. Left: grain authored at one pixel. Right: grain authored at three pixels.

The left half is where per-pixel grain goes: mostly gone, and what is left has turned into low-frequency blotching that is worse than no grain at all. The right half still has grain, because grain at three pixels is a frequency the encoder keeps.

To be clear about what is on screen: the right half is a simulation, not an encoder. It block-averages at 8×8 and attenuates each pixel's deviation from that average, which is the visible consequence of dropping high-frequency coefficients. It is directionally right and it is not x264.

Why per-pixel noise is the first thing to go

Every mainstream codec transforms 8×8 or 16×16 blocks into frequency coefficients, then quantizes them, and the quantizer is coarser for high frequencies because that is where the eye is least sensitive. Per-pixel noise is entirely high frequency by construction. It occupies the coefficients the encoder is most willing to zero.

Worse, it does not go quietly. Grain is incompressible, so the encoder spends bits on it until it hits the rate cap and then starts taking bits from everything else. A grainy capture at a fixed bitrate has a softer image and no grain, which is the opposite of both goals.

The scales that survive

The exact numbers move with bitrate and resolution, which is the reason to test rather than to take the table. The shape of the answer does not move.

The better answer is to not bake it

Grain is cheap to generate and expensive to transmit, which is an unusually clear argument for generating it at the far end:

The same logic, applied to everything else fine

Grain is the clearest case but not the only one. Anything that lives at the pixel scale is negotiating with the encoder:

The check that costs ten minutes

Encode a capture at the bitrate the platform actually uses, then look at the encoded file rather than the master. Not a screenshot of the timeline: the delivered artifact. Most of these problems are invisible until that step and obvious immediately after it.

Rules of thumb

  1. Per-pixel grain does not survive a delivery encode. It costs bits and arrives as blotching.
  2. If it must be baked in, author it at three to four pixels and verify on the encoded file.
  3. Better: generate grain at the display, or use AV1's grain synthesis and send a clean picture.
  4. Dither belongs after delivery too, for exactly the same reason.
  5. Grain steals bits from the rest of the image. A grainy capture at a fixed rate is softer everywhere.
  6. Judge every fine detail on the delivered file, never on the master.

All 61 notes How to use them Credits