Tutorial: Using Anti-Aliasing (MSAA) in the iPhone
This will be a rather short but useful tutorial where we will check out how to implement MSAA Anti-Aliasing on iPhone, iPad and iPod Touch. I will also showing a difference between using no anti-aliasing and using 4x anti-aliasing in the upcoming game that we are working on in Gando Games. ( Hex Reaction ).So first, what impact does anti-alias have in a game?
(You can click the images to expand them)
The screenshot to the left shows no anti-aliasing at all. Notice how the edges of the Hexagons look quite jagged in contrast to the picture at the right where you can see that the hexagon edges are far smoother. I didin’t want that kind of “jagged” look for Hex Reaction, so I decided to aim for newer devices and use 4x Anti-Alias. I mention newer devices because anti-aliasing is nice but it does use quite a bit of processing power, so iPhone 3GS up are able to handle it with no real problems, but it’s hard to get full performance using 4x anti-aliasing on an iPhone 3G.
In the iPhone we have Multi Sample Anti Aliasing (MSAA) completely sorted out and you won’t have to do it “manually”. That’s quite a time saver, and I strongly recommend you to experiment with antialias.
So, what is MSAA ?
Quoting the SGIS_Multisample Specification 0.6
The technique is to sample all primitives multiple times at each pixel. The color sample values are resolved to a single, displayable color each time a pixel is updated, so the antialiasing appears to be automatic at the application level. Because each sample includes depth and stencil information, the depth and stencil functions perform equivalently to the single-sample mode.
Fortunately for iPhone/iPod touch/iPad developers, it’s quite easy to get Multi Sample Anti-Aliasing (MSAA) running. It is a great way to smooth out polygon edges in either 2D or 3D.
So let’s do some coding:
The first thing we need to do is to setup the following variables in our EAGLView.h (or equivalent):
GLint backingWidth,
backingHeight;
//Buffer definitions for the view.
GLuint viewRenderbuffer,
viewFramebuffer;
//Buffer definitions for the MSAA
GLuint msaaFramebuffer,
msaaRenderBuffer,
msaaDepthBuffer;The view buffers shouldn’t be anything new for you, on the other side we have declared a Framebuffer plus a render and a depth buffer for the MSAA.
The next piece of code shouldn’t be anything new to you, it’s included in the classic Apple OpenGL ES template, and just init the viewRenderBuffers, etc. If it is something you want to explore in-depth, you can check out this post that explains the following code with more detail.
//Create our viewFrame and render Buffers. glGenFramebuffersOES(1, &viewFramebuffer); glGenRenderbuffersOES(1, &viewRenderbuffer); //Bind the buffers. glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer]; glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth); glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
So, we have init and bound the viewFramebuffer and the viewRenderbuffer. Still, nothing new… But now it get’s a bit different
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //Generate our MSAA Frame and Render buffers glGenFramebuffersOES(1, &msaaFramebuffer); glGenRenderbuffersOES(1, &msaaRenderBuffer); //Bind our MSAA buffers glBindFramebufferOES(GL_FRAMEBUFFER_OES, msaaFramebuffer); glBindRenderbufferOES(GL_RENDERBUFFER_OES, msaaRenderBuffer); // Generate the msaaDepthBuffer. // 4 will be the number of pixels that the MSAA buffer will use in order to make one pixel on the render buffer. glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, 4, GL_RGB5_A1_OES, backingWidth, backingHeight); glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, msaaRenderBuffer); glGenRenderbuffersOES(1, &msaaDepthBuffer); //Bind the msaa depth buffer. glBindRenderbufferOES(GL_RENDERBUFFER_OES, msaaDepthBuffer); glRenderbufferStorageMultisampleAPPLE(GL_RENDERBUFFER_OES, 4, GL_DEPTH_COMPONENT16_OES, backingWidth , backingHeight); glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, msaaDepthBuffer); |
The previous snippet of code, generates 3 buffers in the lines 2, 3 and 13. This buffers will hold the Frame, Render and Depth buffer for the MSAA. The “magic” will come in our “Draw” method, which should basically look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | - (void) draw { [EAGLContext setCurrentContext:context]; // // Do your drawing here // // Apple (and the khronos group) encourages you to discard depth // render buffer contents whenever is possible GLenum attachments[] = {GL_DEPTH_ATTACHMENT_OES}; glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 1, attachments); //Bind both MSAA and View FrameBuffers. glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, msaaFramebuffer); glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, viewFramebuffer); // Call a resolve to combine both buffers glResolveMultisampleFramebufferAPPLE(); // Present final image to screen glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer); [context presentRenderbuffer:GL_RENDERBUFFER_OES]; } |
Notice the lines 11 and 12, where we discard what we don’t really need of our depth buffer, this is nice for better performance.
As a conclusion, you can see that it is really easy to use MSAA now, you don’t have to do it 100% “by hand” like in the past. I encourage other developers to use anti-aliasing to improve the look of their games and apps, since a lot of them could benefit from this technique.
>Be sure to check other tutorials in the tutorials section!






eviltenchi September 20th
I’m a bit of a noob when it comes to programming. that being said, where exactly do I put these pieces of code? I am using unity which is sets up Open GL ES in appCOntroller.mm so I am having trouble applying the code to it.
Nicolas Goles September 27th
Hey there eviltenchi,
My experience with Unity 3D is quite limited, though I have based a lot of the architecture of our 2D game engine by looking how Unity works.
Maybe you could drop me an e-mail to nicolas at gandogames.com , show me your appController and we could figure it out
.
Remember that anti aliasing is an expensive operation so if you are targeting low-end devices, you will see significant slow-downs.
Cheers!
eviltenchi September 29th
I figured it out with some help from the Unity forums, thanks anyway though. I’ll probably have to try this code out if I do a non-Unity game
Nicolas Goles September 29th
I’m very interested in your problem solution though! , if you could post the Unity Forums link it would be awesome.
eviltenchi October 1st
http://forum.unity3d.com/threads/60785-iPhone-4-MSAA-Test-Results
The last appController.mm is the one that I used and someone posted a fix for discarding buffers a few posts afterwards so I replace that in the code as well.
Nick DiZazzo February 18th
This doesn’t work for me. I’m creating the normal view and render buffers, then the MSAA buffers, as the article instructs me to.
After doing so, I run “glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES)” To see if the FBO was created successfully, but it returns 0×8cd6 == GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT.
I’m not sure what to do at this point since all I’ve done is tried to set up the frame buffer object.
Nick DiZazzo February 18th
Well… The FBO is now magically being created and not returning an error, but I’m getting a nice big ‘ol black screen
Here is the code I have in my drawing method:
http:// pastebin.com/QP5RxFLk
Nick DiZazzo February 18th
Ok – for those of you who are running into the same issues as I, you must bind the MSAA frame buffer before you issue your draw calls. Obviously.
*facepalm*
So:
glBindFramebufferOES(GL_FRAMEBUFFER_OES, msaaFrameBuffer);
// Get the game controller to render our scene
// Discard depth buffer
GLenum attachments[] = {GL_DEPTH_ATTACHMENT_OES};
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 1, attachments);
//Bind both MSAA and View FrameBuffers.
glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, msaaFrameBuffer);
glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, viewFramebuffer);
// Call a resolve to combine both buffers
glResolveMultisampleFramebufferAPPLE();
// present the view frame buffer
horseshoe7 April 18th
Nick DiZazzo! Thank you! That fixed my problem as well. Otherwise I was just getting the first rendered frame and nothing else worked.
Otherwise, thanks to the author for the tutorial. I don’t understand a single thing that went on, but the main point is right now, it worked!
Evan B May 15th
Uh…. Hello, I’m the stupid guy who can’t get this to work…!
I guess the OpenGLES template changed lately, as the current one (well, in the last XCode 3) does not have a “draw” method (there is “drawFrame” but that is in the view controller, not the EAGLView itself.) I’ve googled round in circles for a couple of days, and there isn’t one complete altered template to show how to make full screen anti-aliasing, just some snatches of code and vague instructions about where to paste them in, based on things that don’t match up to what I’m seeing. So in conclusion, thanks, but any more clues would be appreciated!
Daniel June 16th
Including this code went alright, that is, it compiles without errors and my OpenGLES program runs as fine as before with the non-MSAA code. However, I can see absolutely no effect in my game. All the lines and borders are just as pixelated and jaggy as before. What could be the problem?
mkotliarov August 2nd
Look in the PowerVR training course, there is a couple examples of configuration of EAGLView. It works for me. See IntroducingPVRShell and IntroducingPOD.
Gavin August 22nd
Thank you very much for this, my iPad game looks miles better now, and performance is still fine. Objects that were badly obscured or jagged (sometimes disappearing completely at a distance) now look just great.
Meta October 3rd
Thanks, that works for me too.
Tester November 24th
Thanks for this tutorial. I had one issue with it: Your framebuffer pixel format is GL_RGB5_A1_OES, which caused severe color banding for me. Changing it to GL_RGBA8_OES fixed it, but I haven’t measured the performance impact. Just to let others who run into color banding issues know it.
Add Yours
YOU