Texture filtering: mipmaps

Texture filtering: mipmaps

  • Comments 18

In my previous article about texture filtering I mentioned that scaling down images using  point sampling, or scaling to less than half size using linear filtering, looks bad because some source pixels are discarded.

Why exactly is this a problem?

Try this thought experiment:

We have a 256x256 texture, which is being scaled down by a factor of 1/256, so the entire image covers just a single screen pixel. What color should this pixel be?

If we use point sampling, only one pixel of the 256x256 image will be used, and the others discarded. This can give pretty random results depending on which specific pixel happens to be chosen!

Consider what happens if we tile our texture, so the scaled down image is repeated 100 times. If each repetition lines up exactly with one screen pixel, they will all choose the same source pixel, but if repetitions and screen pixels do not line up (maybe you are scaling down by 1/255 or 1/257 rather than 1/256, or using a 3D perspective projection), each repetition will end up choosing a different random location in the source texture. This is basically the same thing as:

    for each destination pixel:
        destination = sourceTexture.GetPixel(random.GetNext(), random.GetNext());

which is not going to give the best looking results  :-)

It would obviously be better if we could average all the pixels from the source texture, rather than having to choose just one of them. But averaging 256x256 color values is too slow to do in realtime.

When you can't afford to compute something on the fly, it is time to precalculate...

A mipmap is a precalculated copy of an image that has been shrunk to a lower resolution using a more accurate, higher quality filtering algorithm than would be possible in realtime on the GPU. Mipmaps are arranged in a chain where each image is half the size of the previous one, for instance:

  • Original = 256x256
  • Mip 1 = 128x128
  • Mip 2 = 64x64
  • Mip 3 = 32x32
  • Mip 4 = 16x16
  • Mip 5 = 8x8
  • Mip 6 = 4x4
  • Mip 7 = 2x2
  • Mip 8 = 1x1

The GPU will automatically choose the appropriate image depending on how much the texture is being shrunk down.

To draw with mipmaps, you need two things:

  • You must include mipmap data when creating your textures (for instance by setting the Generate Mipmaps content processor parameter)

  • You must set SamplerState.MipFilter
    • TextureFilter.None = do not use mipmaps
    • TextureFilter.Point = low quality (but sometimes faster) mipmapping
    • TextureFilter.Linear = higher quality mipmapping
    • If you draw using BasicEffect, this will automatically set MipFilter = Linear

The difference between Point and Linear mip filtering is how the mipmap level is chosen:

  • MipFilter = Point
    • Chooses the appropriate mip level depending on how much the texture is being shrunk
    • If this is a fractional value, rounds up to the next larger mip level
    • This means the mip image may still need to be shrunk by a small amount
    • But linear filtering is good for shrinking as long as we don't go below half size
    • To shrink below half size, we'd just choose a smaller mip level instead
    • There can sometimes be visible artifacts when switching between mip levels

  • MipFilter = Linear
    • Samples from both the next larger and next smaller mip levels
    • Interpolates between them based on how close the current image scale is to the two mip levels
    • Avoids artifacts along boundaries between mip levels
    • But we must now sample 8 rather than 4 source pixels for each destination, so this can cost more if your app is texture fetch limited

Let's see this in action. Here is the linear filtered terrain from my previous post:

image

And now using mipmaps:

image

Note how the distant hills appear smooth and free from noisy aliasing.

 

Popular urban myth #1

"Mipmaps take up too much memory"

Actually, mipmaps use little extra memory, thanks to the power of powers:

  • Mip 1 = 1/4 the original size
  • Mip 2 = 1/16 the original size
  • Mip 3 = 1/64 the original size
  • Mip 4 = 1/256 the original size

If you continue this sequence, you'll see that an entire mipmap chain all the way down to 1x1 takes up just 1/3 more memory than the original texture. So it's a negligible overhead, especially once you get past that first mip.

 

Popular urban myth #2

"Mipmaps are slower for the GPU to render"

In fact, they are often much faster! If you aren't scaling down a texture, having mipmaps won't cost anything. But when you are scaling down, mipmaps can save crazy amounts of memory bandwidth.

Remember our example of a tiled 256x256 texture, where each repeat is being scaled down to 1x1 (a common situation in things like terrain rendering). Without mipmaps, every destination pixel will sample a radically different location in the source texture, so the GPU must jump around fetching colors from different areas of memory. GPUs typically have very small texture caches, relying on the fact that textures tend to be accessed sequentially, so this access pattern will thrash the cache and can bring even a high end card to its knees. But with mipmaps, the GPU can simply load a small mip level which will easily fit in the cache, and can then render many destination pixels without having to go back to main memory.

So mipmaps are not just for reducing aliasing: they can actually speed up rendering, too.

Shawn's Recommendation™: if you are going to draw a texture in 3D, or planning to scale it down to less than half size, you should always use mipmaps.

  • Excellent article as always, thank you.

  • Do I need always generate all levels for mip-maps? Can I have lowest level let's say to be at 16x16?

    Thanks,

    fn2000

  • > Do I need always generate all levels for mip-maps? Can I have lowest level let's say to be at 16x16?

    You can do that if you want, but why would you want to?

    Partial mipmap chains are hardly ever a useful thing in practice (excepting a couple of very specific cases where you are implementing manual shader filtering for sprite sheets).

  • Hi,

    What if the original is 512x512?

    Would there still be 8 Mips which would result in mip 8 being 2px x 2 px, or would it scale down until it reached a mip where the texture is 1px x 1 px (which would be mip9 in this case) ?

    Thanks, great article

  • > What if the original is 512x512?

    A complete mip chain goes all the way down to 1x1, using however many images are necessary to get there.

    You can create partial mipmap chains if you want to stop at some larger size, but that is rarely a useful thing to do.

  • can you get access to a specific mipmap from the chain? For example I'd like to have access to the 1x1 texture to use as exposure average in hrd mapping

  • can you get access to a specific mipmap from the chain?

    Several of the sampler state values control how mipmaps are selected.

    You can also specify this explicitly using tex2lod in your pixel shader.

  • i am rendering the scene onto the texture rather than backbuffer using setRenderTarget in one pass(i am using directx 9.0 shaders). then i am blending that texture with another one and getting the output onto the screen(thats on backbuffer) in second pass. everything is working fine i am also getting the o/p but the problem is the rendered scene which is stored on texture is getting shrunk while rendering on screen. i have also changed the texture size but its not working tried wid projection matrix also.

  • Hi Shawn, does the original texture have to have square dimensions, & does the length have to be a power of 2?

  • Square textures are not required by any modern hardware. Non pow2 support depends on if you target Reach or HiDef: blogs.msdn.com/.../reach-vs-hidef.aspx

  • Is it possible to use mipmaps to scale up?

    My original texture is 32x32 and I want to display it as 64x64 or higher? Can I do that?

    Thanks

  • > Is it possible to use mipmaps to scale up?

    You can scale up using texture filtering (your choice of point, linear, or anisotropic) but mipmaps aren't really applicable here.

    What would a precalculated 64x64 mipmap for a 32x32 source image be? That would basically just mean changing your source image to be 64x64 instead, which is entirely possible but not what we would normally call mipmapping!

  • It makes sense. Thanks, Shawn.

  • I wasn't able to find much info on it, but I was curious about XNA's mipmap generation pipeline. Could you tell me if it does sRGB or gamma aware downsampling, say something around pow(2.2)? I was going to give it a try using a standard black and white striped texture, but figured you might be able to give more detailed info about it. Thanks.

  • Excellent article.

    One question, what if I'm using a texture atlas, say with 4 possible textures in a single one?

    I guess the mipmap is just one for the whole texture, so it would cause a lot of glitches, right?

Page 1 of 2 (18 items) 12
Leave a Comment
  • Please add 6 and 2 and type the answer here:
  • Post