Texture filtering: anisotropy

Mipmapping allows textures to be scaled down by any amount while maintaining high quality filtering and cache-friendly memory access patterns. But mipmaps don’t work so well when different axes are scaled by different amounts:


How do we choose which mip level to use for the elongated cat?

  • By default the GPU will select mipmaps according to whichever axis has the smallest scale. This is efficient and avoids aliasing artifacts, but can lead to unwanted blurriness along the larger axis.

  • You can use SamplerState.MipMapLevelOfDetailBias to force selection of a larger mip level, which reduces blurriness at the cost of aliasing artifacts and poor cache efficiency. Basically heading back toward the bad old days when we had no mipmaps at all.

Non uniform scaling is rare in 2D (although my cat has been eating so much lately, he is well on the way to horizontally scaling up his belly exactly like the above image 🙂 but it happens all the time in 3D:


Any time you apply a perspective transform to a textured triangle which is not exactly facing the camera, non uniform scaling will result.

Anisotropic filtering improves the quality of these situations by recognizing when textures are being scaled differently in different directions, and taking a larger number of samples to compensate.

To enable it:

    device.SamplerStates[0].MinFilter = TextureFilter.Anisotropic;
    device.SamplerStates[0].MagFilter = TextureFilter.Linear;
    device.SamplerStates[0].MipFilter = TextureFilter.Linear;
    device.SamplerStates[0].MaxAnisotropy = <n>;

MaxAnisotropy is typically set to 2 or 4. Some recent cards support as high as 16, so check the caps to see how high yours can go.

How it works:

  • Compute the aspect ratio between the larger and smaller scaling axes
  • If this is greater than MaxAnisotropy, clamp to MaxAnisotropy
  • Choose mip level according to this value (which will usually be whichever axis is larger)
  • To avoid aliasing, sample the texture multiple times along a line determined by the axis which is being shrunk the most, averaging the results
  • The number of samples depends on the ratio between the larger and smaller axes
  • GPUs are smart: if the scaling happens to be uniform, they will only take a single sample even when anisotropic filtering is enabled
  • Thanks to the aspect ratio clamping, there will never be more than MaxAnisotropy samples per destination pixel

How does it look? Here is our favorite landscape with mipmaps but no anisotropic filtering:


And now with mipmaps plus anisotropic filtering:


Both images are free from noisy aliasing, but the anisotropic version has better texture detail on surfaces which are angled relative to the camera, and is less blurry.

So why doesn’t everyone use anisotropic filtering all the time?

Because this quality improvement comes at a price! Sometimes a high price. All these extra samples take time for the texture unit to compute, and cost extra memory bandwidth too.

Or it might be free. If your game is CPU bound, or bottlenecked by a different part of the GPU, anisotropic texture fetches might not actually slow things down at all. So try turning it on and see what happens. If it is too slow with a high MaxAnisotropy setting, try a smaller value to find the right balance between quality and performance.

Back when I was working on MotoGP, we wanted anisotropic filtering for quality reasons, but couldn’t afford to use it everywhere. We ended up tweaking MaxAnisotropy differently for every texture, setting it to 4 for billboard adverts where the quality improvement was especially noticeable, to 3 for most of the road surface and grass, and 2 for less important things like trees and crowds. We sometimes even used different settings for each layer of a multitexturing shader, for instance a high anisotropy for the base texture but none at all for the detail texture.

Comments (3)

  1. Vyacheslav says:

    >Some recent cards support as high as 16, so check the caps to see how high yours can go.

    Recent, yeah. Like Radeon 7000.

    Of course, NVidia screws it with 16x anisotropy starting with 6*** series, but still, to call it "recent videocards"?

    >Or it might be free. If your game is CPU bound, or bottlenecked by a different part of the GPU…

    It could also depend on the pattern in which the texture is sampled. Videocards do a very nice job to put the most of those expensive samples in cache if you’re not sampling it randomly.

  2. Randy says:

    Would modifying the MaxAnisotropy value on the fly be advisable, or even possible?  Say you detect that your game is starting to skip draw calls due to slowdown.  Would dynamically lowering the rate until things caught back up be possible?  Or would switching the value mid-execution cause new overhead (not to mention particularly funky looking results if it was switching back and forth often)?

  3. ShawnHargreaves says:

    Hey Randy,

    You could modify anisotropy on the fly, but I'm not sure it would be a great idea. Dynamically reducing detail based on framerate monitoring is a technique that some games use, but can be tricky to get right since you are basing these decisions on somewhat delayed timing data. And even if you do decide to invest in dynamic detail adjustment, you'd probably get more mileage from model LOD, shader LOD, perhaps resolution adjustment, rather than just altering your filter settings. I guess it's possible this could be a sensible place to start if your game is 100% bottlenecked by anisotropic filter lookups, but that's kinda an unusual place for a GPU to be limited…