"The mip chain erases texture detail finer than the object it represents"
▸ SYMPTOM
- A prop reads as flat, plastic, or untextured in the running game.
- Open the source PNG and the texture is clearly there, with plenty of visible detail.
- The prop almost always has a texture. The mip chain already threw the contrast away.
▸ CAUSE
A mip level is a box downsample of the source image. Detail authored at a frequency finer than the scale the surface is seen at gets averaged out before a player ever sees it.
Contrast measured on the full-resolution image ranks sheets backwards. Measured across a set of sheets under an 8x box downsample: a fabric weave kept 21% of its 1:1 contrast, a paving sheet kept 38%, a wood grain kept 67%, and an iron sheet kept 80%.
The fabric sheet was the second most contrasty of the set at 1:1 and the flattest by a factor of two at 8x. Its detail sits at a frequency the downsample average erases.
The same mistake hides as a units bug. A tiling helper that maps N metres to one whole sheet gets read by a caller as N metres to one feature, one weave cell or one plank. The two differ by 10x to 30x on a normal seamless sheet.
Adding grain does not survive the downsample either. An 8:1 box downsample averages 64 samples, so independent per-pixel noise of standard deviation s comes out at s/8. On a 128 px field downsampled to 16, what survives is variation 20 to 40 px across.
▸ FIX
Measure relative contrast after the downsample, not on the full-resolution image.
- Compute
lum.std() / lum.mean()after an N:1 box downsample, which is what a mip level physically is. - Use a box filter for the measurement, not Lanczos. A sharpening resample flatters every sheet, and flatters the high-frequency ones most, which is exactly the population the check exists to catch.
- State in the generator's own comment how many features the sheet carries per tile, then multiply, so a metres-per-sheet number is never read as metres-per-feature.
Author the detail at the scale of the object or unit it represents, plus at least one coarser octave. A brick atlas needs contrast a brick wide, not only contrast at the mortar line and contrast at the storey.
Put a contrast floor in the generator's acceptance check. Hold at least a neighbouring reference prop's measured 8x contrast, so a later tune cannot quietly undo it.
Do not spend the budget on per-pixel noise to pass the floor. Nine of twenty-four regions on one module atlas failed a 0.040 floor while already carrying speckle and fine grain. The regions that passed unaided had features tens of pixels across.
▸ WHY IT WORKS
The contrast floor is a proxy for "reads at distance". Anything that satisfies the proxy without satisfying the intent gets caught by the next person who looks at a contact sheet.
An atlas authored with only fine grain and only coarse structure is a spectrum with a hole in the middle. A wall can carry brick-tone variation and a photographic grain and still read new and clean at range, because nothing in it varies at the unit's own scale or the octave just above it.
When a gate measures a statistic through a filter, work out what the filter does to each frequency band before adding material to pass it. Then spend the budget on the structure the surface would really have at that scale.
- Published