Nowadays everyone is looking to get that last bit of polish in the visual presentation of their game. One big thing of late is anti-aliasing the image to remove the “jaggies” that the polygones produce when rasterized to the screen:
For example consider this text:
See how much smoother the anti-aliased one looks!
This is typically an expensive process that is usually accomplished by rendering the image at twice (or more) its size and then shrinking it down and averaging the pixels together when the image is dumped to the screen. Since the fast “render to” memory is limited on many graphics cards, the image is often divided up into strips and each strip is rendered separately into the fast memory and then copied out to the more abundant not-so-fast memory. After each strip or “tile” has been rendered, they are combined together on the screen. This method is called “tiling” and is a common method for handling anti-aliasing.
Since it is expensive, it makes sense to try and do something cheaper to achieve the same anti-aliased look. This lead us at Hidden Path to investigate some other ideas and what we came up with is pretty slick.
The idea is basically the same as the old accumulation buffer method that was used for creating depth of field and anti-aliased images on older graphics hardware. You take a shot of your scene, shift it by less than a pixel, and take another shot. You do this as many times as you like to get more samples per pixel. The results are weighted and added together in the accumulation buffer which is then dumped to the screen. However, by itself this really doesn’t buy us much because we will still have to render the whole scene several times. Our answer is to spread the creation of the accumulation buffer over multiple frames. We only render one accumulation pass each frame and then combine that pass with prior passes when we display the results on the screen. We’re currently only using two passes and we have gotten some really nice results.
To clarify the above, this is our process:
- SETUP: Draw the scene into textures A and B.
- START:
- Shift the camera projection matrix slightly (less than a pixel) in the positive diagonal direction.
- Draw the scene into texture A.
- Present textures A and B blended together at 50% each on the screen.
- Shift the camera projection matrix slightly in the negative diagonal direction.
- Draw the scene into texture B.
- Present textures A and B blended together at 50% each on the screen.
- REPEAT AT START
Here are some results from using it on my game Caster:
(Click on the images to see them at their full size)
And here is a section magnified (click to enlarge).
So now you might be saying “Ah, but texture B is lagging behind texture A. It will look blurry, you’ll see a double image”. And yes, that is correct. However, when the scene is still with no motion, we have perfect anti-aliasing. When there is very rapid motion or a low frame rate, you can detect blurring. However, this can also be looked at as a little motion blur of 1 frame for free (… okay, you’re probably not buying that last one)! Since Caster generally runs at a high frame rate (60 fps), this blurring is very small and makes sense when it is noticed like in the image below where the character is running around.
Now I haven’t tried it yet with 4 frames, but then you end up having 4 extra color buffers for your scene which can start getting just as expensive as tiling. Also, 1 frame of blur isn’t really much of an issue, but 4 frames might be a bit too much depending on what’s going on in the scene.
Weather or not this method is “free” depends on your setup. Some extra texture memory is required (or not if you don’t mind flicker or have a persistence of pixels on your screen… like old CRTs or televisions), to hold an extra color buffer of your scene. Either way, even if it’s not for free, it comes at a very low cost. Oh yeah, and no need for shaders for this so you can do this in fixed function too! So that means you can do this for… just about any 3D hardware out there right now!
This same technique of “temporal accumulation” can be applied to other things as well. For example, I’ve been using it in Caster for over 3 years now to blur my glow effect. Each frame I slightly change the position of where I render the glow effect on the screen and it is accumulated / blended in with all my prior renderings of the glow. I end up moving the glow texture in small circles to blur it out. The motion causes a shimmer of the glow and accumulating across all frames leaves motion trails. However, for my purposes this ends up being a feature that really like and want to keep.
So there you have it. Lesson learned: If something is very expensive to do in a frame and you can’t break it up into a separate threads, try thinking of ways to break it up across frames.