Shawn Hargreaves Blog
Multisampling is a compromise for people who really want to use supersampling, but can't afford it.
The idea is simple: instead of increasing the resolution for all rendering, what if we do triangle rasterization and depth/stencil tests at the higher resolution, but leave pixel shading and texture lookups at the original, lower resolution?
Where standard rendering goes like:
And supersampling is:
The process for multisampling is:
Unlike supersampling, which can be implemented by drawing to a standard rendertarget and using a pixel shader to apply the final downsize filter, multisampling requires dedicated hardware support. In XNA, it is enabled by adding this to your Game constructor:
graphics.PreferMultiSampling = true;
Because triangle rasterization and depth testing are performed at the higher resolution, multisampling is every bit as good as supersampling at combating triangle edge and geometry aliasing. But because the pixel shader only runs once per final output pixel, it does not help at all with texture map or shader aliasing.
This is an important and easily missed point: multisampling only knows about the edges of polygonal geometry.
Multisampling can smooth the edges of triangles, and also the edges formed where one zbuffered triangle intersects another, but it knows nothing about what happens within the interior of each triangle. Texturing, shading, and lighting produce exactly the same results as if multisampling was not used at all (think about it: the pixel shader runs just once, then we write out 2 or 4 copies of the same color, after which the downsample filter averages these identical colors, producing the exact same value we started with). If your triangles have interior borders caused by texture lookups or shader code (for instance the border of an alpha cutout sprite), multisampling will be irrelevant. This often surprises people when they see aliasing on cutout sprites and turn on multisampling in an attempt to fix it, but nothing changes. Yet this is the nature of the beast. If you want to antialias alpha cutout borders within a triangle, you need supersampling, or one of the texture/shader based techniques I will talk about later.
So why is multisampling popular? (and it is very popular, to the extent where many people confuse the generic term "antialiasing" with the specific technique "multisampling")
Simple: it's cheap. On some hardware (notably Xbox 360) it can be very cheap indeed.
Remember that the GPU is an asynchronous parallel processing pipeline. Multisampling increases the cost of some stages along this pipeline (notably rasterization, depth/stencil testing, and framebuffer writes), but it does not affect the areas that are most often performance bottlenecks (vertex fetch, vertex shading, texture fetch, and pixel shading). Adding work to things that were not the perf bottleneck can sometimes even be entirely free, so multisampling often turns out to improve visual quality for low cost.
Even if you are bottlenecked by framebuffer bandwidth, hardware designers are clever and can pull all sorts of neat silicon tricks to make multisampling efficient, taking advantage of the coherency where the same color value is usually written many times in a row. Why waste memory bandwidth saying "write X, ok, write X again, and again, and one more 'gain", when you could send just a single copy of X over the bus, followed by a couple of bit flags indicating which multisamples this pixel shader result should cover?
Oh yeah, before I go I should show you how it looks. Here's our favorite tank model, first with no antialiasing, then using 4x multisampling:
And the same images zoomed in:
does it lower framerate much on wp7?
> does it lower framerate much on wp7?
Same as any platform, it could vary anywhere from free to quite expensive depending on what your perf bottleneck is. Really something you have to try with the specific workload of your app.
I though depth test comes before running the pixel shader, like this:
For each final output pixel covered by a triangle:
- Perform the depth test
- Run the pixel shader
- Write the pixel color
> I though depth test comes before running the pixel shader
Welcome to a world of complexity :-)
GPUs are highly asynchronous, so many things happen simultaneously, speculatively, or in pieces scattered through the pipeline. We tend to simplify this when thinking about how the rendering pipe works, specifying a conceptual view of what order things happen that isn't necessarily the same as how they actually work.
Conceptually, the depth test comes after the pixel shader. It has to, because the pixel shader can output a computed depth value, and it's obviously not possible to do the depth test until you know what depth value this should test against!
In practice, most silicon vendors will optimize for the common case where the pixel shader does not happen to compute a new depth value. They want to early-out and skip shading occluded pixels, so they may move the depth test to before those shaders where this is possible, while leaving it after for shaders where that would produce incorrect results.
This is further complicated by the need to split depth read/test from depth write (you don't want to write a new depth value if the pixel shader chooses to kill the current pixel!) and by hierarchical early z reject logic, which typically runs slightly ahead of the regular ztest and pixel shader logic.
The general rule is that hardware manufacturers are free to reorder, split up, or parallelize work to boost efficiency in whatever way they like, as long as that doesn't change the results compared to the simple conceptual view of the pipeline that we program against.
You sure know how to explain things in a simple way, and also at certain times (like in the comment) reveal how much complexity is behind a certain technology but still without making it hard to read and undertand what you're saying :)
The question I have in my mind now is how can you make the pixel shader output a new depth value (as far as I know you can't modify the POSITION# values in the pixel shader), but I think I should look that up myself :D
What is the way to enable multi-sampling if you are not using game constructor and related components (ie. you implement your own IGraphicsDeviceService and create GraphicsDevice manually.) ?? Adjusting only the parameters to GraphicsDevice constructor doesn't seem to have any effect.
The GraphicsDevice constructor parameters (in particular PresentationParameters) are exactly how you specify to use multisampling.