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.MinFilter = TextureFilter.Anisotropic; device.SamplerStates.MagFilter = TextureFilter.Linear; device.SamplerStates.MipFilter = TextureFilter.Linear; device.SamplerStates.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.