I must say that I had a lot of fun reading up on this. I had come across most of the terms in this post before when glancing over option screens for games, but I never really deeply understood the difference betwen bilinear or anisotropic texture filtering. Let alone understand what a mipmap was. I actually really enjoy knowing what is going on under the hood :).
In the previous example we did not configure any sampler_state settings and just used the default ones. That turns out to use the fastest way of sampling the texture, using point sampling, and that leaves us with a lot of ugly artefacts.
Figure 1. Eeeeewwwww!
Only click for the full size version if you have a strong stomach…
So lets try and use some filtering methods to make this look better. The first thing we need to look at is actually not a texture filtering method but another method to increase rendering speed and reduce aliasing artefacts called MIP mapping.
A mipmap is a sequence of progressively lower resolution versions of the base texture. This way we can use a lower resultion version of the texture when the object is farther away from the camera. It’s a lot faster looking up the texel color from the lower resolution version then averaging the color of almost your entire image to get the pixel color when the object is far away. And you can apply any crazy filtering method to generate the lower resolution versions as you don’t have to do it in real-time. All it costs is some extra storage space, about a third of the original texture.
Figure 2. MIP mapped crate texture
Figure 3. Regular vs mipmapped texture
There are tools you can use to create mipmaps for your textures like the compressonator or the newer AMD compress (I actually haven’t tried that last one yet). These tools have loads of options to optimize your textures, but I’m lazy so I just ticked the option ‘Generate Mipmaps’ in the Content Processor options for my texture.
Now that we have the mipmaps for our texture, let’s apply some filtering. I will go trough the options going from fastest and crudest to the most intensive and highest quality one.
I’m giving special mention to the opengl-tutorial.org for having clear information on the subject (and all the tutorials on the site are a great intro to modern OpenGL).
Point sampling (aka Nearest-neighbor interpolation)
sampler_state { MinFilter = point; MagFilter = point; MipFilter = None; }
This basically uses no filtering and just takes the texel color that is at the UV coordinates.
Figure 4. Point Sampling
Bilinear filtering
sampler_state { MinFilter = linear; MagFilter = linear; MipFilter = point; }
Bilinear filtering is taking the four nearest texels into account and those colors are combined using the weighted average according to the distance of the UV coordinates to those texels. This smoothes out the hard edges.
Figure 5. Bilinear sampling
Bilinear filtering is mostly used in combination with mipmaps because it would suffer much of the same artefacts of point sampling if you don’t use mipmaps. The thing to note here is that the MipFilter is set to point. This means that the nearest mipmap level is chosen and then bilinear filtering is applied.
Trilinear filtering
sampler_state { MinFilter = linear; MagFilter = linear; MipFilter = linear; }
Trilinear filtering is an extension to the bilinear filtering method where not only the nearest mipmap level is sampled but the two neares mipmap levels and then linearly interpolation the results. This gives a smoother boundary at the places where we switch from one mipmap level to the next.
Figure 6. Bilinear vs Trilinear sampling
I must admit that I’m a bit dissapointed about my example above, you can barely make out where the mipmap level changes and the texture becomes more blurry in the bilinear case. There are certainly more convincing cases to be found.
Anisotropic filtering
sampler_state { MinFilter = anisotropic; MagFilter = anisotropic; MipFilter = linear; MaxAnisotropy = 16; }
And finally we get to anisotropic filtering which is most noticable with surfaces that are at a high angle relative to the camera. In that case, the fill area for a pixel will not be square but rather trapezoidal in shape. Anisotropic filtering samples the texture as a non-square shape. Some implementations simply use rectangles instead of squares (as depicted in the picture below). Anisotropic filtering will calculate the colour in the fill area by taking a fixed number of samples configured by the MaxAnisotropy setting.
Figure 7. Anisotropic sampling
Figure 8. Trilinear sampling vs Anisotropic sampling
This is a big improvement, the texture becomes a lot less blurry on the sides of the crate. It typically does not get any better than this (yet) on consumer graphics cards.
The Code
The only code that was added building on the previous example has nothing to do with the texture filtering.
First it was necessary to add some debug text to show the active filtering mode.
SpriteBatch _spriteBatch;
SpriteFont _consolasFont;
protected void DrawText()
{
_spriteBatch.Begin();
...
_spriteBatch.DrawString(_consolasFont, mipmappingText, new Vector2(10f, 50f), Color.White);
...
_spriteBatch.End();
}
Code Snippet 1. Drawing tekst with SpriteBatch
A thing worth mentioning is that SpriteBatch changes some rendering settings on the GraphicsDevice so before rendering our 3D object we must set the necessary settings back like we want them or we will have unexpected results.
_graphicsDM.GraphicsDevice.BlendState = BlendState.Opaque;
_graphicsDM.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
Code Snippet 2. Reset settings on GraphicsDevice
The second thing that was added is a simple switch to select a different Technique in the shader that uses a different texture filtering method.
switch (_currentSamplingMethod)
{
case TextureSamplingMethod.Point:
_effect.CurrentTechnique = _effect.Techniques["PointSampling"];
break;
case TextureSamplingMethod.Bilinear:
_effect.CurrentTechnique = _effect.Techniques["BilinearSampling"];
break;
case TextureSamplingMethod.Trilinear:
_effect.CurrentTechnique = _effect.Techniques["TrilinearSampling"];
break;
case TextureSamplingMethod.Anisotropic:
_effect.CurrentTechnique = _effect.Techniques["AnisotropicSampling"];
break;
}
Code Snippet 3. The if-then for advanced programmers: a switch statement!
Downloads