Skip to content

Build1 publisher3 min readPublished

Mipmaps are three levers, not one checkbox: stability, residency, and meaning

A Unity-focused writeup argues the "smaller copies of a texture" model explains none of the real failures: crawling motion, foliage that vanishes, atlases that bleed only in lower levels.

The Engineer · Build desk

Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction

Illustration accompanying Mipmaps are three levers, not one checkbox: stability, residency, and meaning
Generated illustration

What happened

  • Mipmaps are usually introduced as smaller copies of a texture, which is correct but does not explain why a no-mipmap screenshot looks sharp while motion shimmers, why foliage disappears, why normal-mapped highlights flash, or why an atlas bleeds only in lower levels.
  • A more useful model treats a mip chain as three things: a tool for temporal image stability, a mechanism for allocating bandwidth and resident memory, and a hierarchy whose levels can preserve different kinds of meaning.
  • For a 4096 x 4096 texture on a floor viewed at a shallow angle, one screen pixel may cover hundreds of texels; sampling only mip 0 selects a tiny subset of that footprint, so a small camera movement selects a different subset and produces crawling detail, moire, and flicker.
  • For ordinary implicit sampling the GPU estimates UV change across neighboring fragments, in simplified form rho = max(length(ddx(texelPosition)), length(ddy(texelPosition))) and lod = log2(rho); screen-space UV derivatives drive mip selection.
  • Distance matters for mip selection but is not the rule: projected size, UV scale, surface angle, texture resolution, projection, bias, and anisotropy all affect the result.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

A writeup published on dev.to makes a narrow but useful argument: the standard introduction to mipmaps as "smaller copies of a texture" is correct and explains almost nothing, since it does not account for why a no-mipmap screenshot looks sharp while motion shimmers, why foliage disappears, why normal-mapped highlights flash, or why an atlas bleeds only in lower levels [1]. The reason that matters for anyone shipping a build is that a mip chain is doing three jobs at once, according to the author: temporal image stability, allocation of bandwidth and resident memory, and a hierarchy whose levels can preserve different kinds of meaning [2]. Those three have different symptoms and different fixes, and one Generate Mip Maps toggle hides all of them.

Start with stability. On a 4096 x 4096 texture on a floor at a shallow angle, one screen pixel may cover hundreds of texels, so sampling mip 0 picks a tiny subset of that footprint, and a small camera move picks a different subset, which is what produces crawling detail, moire, and flicker [3]. Selection is driven by screen-space UV derivatives, simplified in the source as rho = max(length(ddx(texelPosition)), length(ddy(texelPosition))) and lod = log2(rho) [4]. Distance is not the rule; projected size, UV scale, surface angle, texture resolution, projection, bias, and anisotropy all move the result [5]. That is why the source insists on testing fine grids, gravel, leaves, thin text, and repeating patterns with a slow, repeatable pan, and on temporarily disabling TAA to isolate texture aliasing [6]. Negative mip bias gets judged the same way, in motion, because it selects higher-resolution levels and raises aliasing, bandwidth, cache pressure, and streaming demand [7]. Also note that trilinear filtering does not replace anisotropic filtering: a road may need anisotropic sampling even when mip transitions are already smooth [8].

The residency lever has the arithmetic that operators usually get backwards. Each level holds a quarter of the texels of the previous one, so 1 + 1/4 + 1/16 + ... converges to 4/3 and a full chain costs roughly 33% more texels than mip 0 alone [9]. Read in reverse: mip 0 is about three quarters of the chain, dropping the maximum resolution by one level leaves about a quarter of the texel count rather than half, and two levels leave about one sixteenth [10]. For a 4096 square texture that chain runs from mip 0 down to a 1 x 1 mip 12, thirteen levels in total [11][12]. Compression blocks, minimum mip storage, alignment, and platform details change the exact bytes [13]. Separately, the sampler may ask for mip 0 while streaming has only mip 2 and coarser levels resident, which is the blurry-after-a-cut effect that resolves a moment later [14].

The third lever is where content breaks. Because mip generation is filtering, the wrong color space changes the meaning of lower levels: base color is normally color, while roughness, metallic, AO, height, vectors, coefficients, LUT values, and masks are normally linear data, with HDR and emissive intensity maps as common exceptions [15]. Unity's Box filter is smoother; Kaiser keeps more sharpness and potentially more shimmer [16]. And a shader doing clip(alpha - 0.5) on a leaf that is alpha 1 against alpha 0 will lose geometry once downsampling produces boundary values such as 0.4: the average is not wrong, the surviving coverage changed between levels [17]. Unity exposes Preserve Coverage to adjust lower levels for that case [18].

Two things to watch. First, terminology: the piece is written against Unity 6.5 (6000.5) names, and inspector labels differ in 2022/2023 LTS, other Unity 6 versions, and between URP and HDRP package versions [19]. Second, the interaction list the author flags before enabling negative bias globally, anisotropic filtering, UV density, source resolution, render scale, and the active upscaler [7], plus discontinuities between render scales as a symptom to look for [6].

Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories