Drawing Anti-Aliased Circles in OpenGL on the iPhone/iPad

March 16th 2011

During the development of Corrino Software's iPhone/iPad game Pizarro, I ran into problems with some of the inherent limitations of the OpenGL ES implementation in iOS. The iOS devices don't support anti-aliasing for polygons or for lines wider than 1 pixel. I knew Pizarro would require drawing primitives in OpenGL, as opposed to sprites, both for performance and memory reasons. However, without antialiasing, these primitives would be ugly and the lines jagged.

I was off to several false starts. Initially, I tried drawing the circles with CoreGraphics and converting them to OpenGL textures, but the performance was dreadful and brought older devices to their knees. It seemed ridiculous overhead for something that should be so simple: drawing an antialiased circle. After much trial and error, I was ultimately able to overcome -- or, some might say, circumvent -- these problems, and so thought I'd share with the world the clever trick I used.


To start off, it's worth saying a few things about Pizarro, the game I was developing. Pizarro is a very simple game. One or more red balls bounce around in a box. The objective of the game is to touch the screen and hold to create expanding circles to cover the area without having these circles collide with the bouncing balls. When 80% of the surface area has been covered, you advance to the next level. Obviously, the majority of the in-game graphics were circles. It was essential that they look good. The appearance and feel of the game itself depended on it.

Typical Pizarro Game Scene
pizarro circles0

As you can see from the screenshot of the final version of Pizarro above, the circles look great and are beautifully antialiased. How did I get them antialiased on iOS, which as of writing has an implementation of OpenGL ES that doesn't support antialiasing for polygons (and by extension, circles)?

First efforts

I was developing Pizarro for iOS using the excellent, open-source cocos2d engine. The version of Cocos2D I was using, 0.99.5, included some built-in functions for drawing primitives, including a function called ccDrawCircle in the CCDrawingPrimitives class. All the primitives functions in the engine only drew the shapes as outlines. I wanted filled circles, not outlines, so I created a duplicate of the ccDrawCircle function, which I called ccFillCircle, where only one line was changed:

<div class="codeblock">
<pre>glDrawArrays(GL_LINE_STRIP, 0, segs+additionalSegment);</pre>


<div class="codeblock">
<pre>glDrawArrays(GL_TRIANGLE_FAN, 0, segs+additionalSegment);</pre>
ccFillCircle using GL_TRIANGLE_FAN
pizarro circles1

b2d294b366c501cf9a0316d2f25e02a1 774b7b36c93b6999e4263c09f45781d5 b0ab4b60a0849b0d1158b74f3f89b50c 0d6c01caea330d9a13216a8a955a821c
<div class="codeblock">
glEnable(GL_POINT_SMOOTH); glVertexPointer(2, GL_FLOAT, 0, &p); glDrawArrays(GL_POINTS, 0, 1);
These are large points drawn using GL_POINT_SMOOTH
pizarro circles3

circles point smooth vs triangle strip


Ultimately, I was able to circumvent this by drawing the points to a CCRenderTexture which is used as a background for the game scene. The OpenGL drawing code for the smooth point would be called only once. The circles that were to expand during the touch phase would still be drawn at 60fps using GL_TRIANGLE_STRIP. So, currently Pizarro's expanding circles look much uglier than the final circle. However, in the heat of gameplay it's not so noticable, especially on the iPhone 4's retina display.

<br><center><strong>GL_POINT_SMOOTH for many circles at 60fps is too slow</strong><br>
    <img src="/images/pizarro_circles2.png" width="480" height="294" alt="pizarro circles2">

<p>Of course, if you only have a few small circles, you might be able to get away with drawing them using GL_POINT_SMOOTH.
The iPhone 4, which is much faster than previous devices, handles it reasonably well.</p>
<p>Another problem I ran into with using GL_POINT_SMOOTH was that when the points reached a certain size, they would no
    longer be drawn.  A bit of googling located the source of the problem:  There are point size limitations in OpenGL ES
    and drawing calls that try to draw points larger than the limit will fail.</p>

<p>My solution to this was to use code to check for the point size.  If it was larger than the maximum smooth point
    size, I used GL_TRIANGLE_FAN.  If not, I'd use the GL_POINT_SMOOTH method:</p>
4cad81103b3fe43d2e232c2a423166df bedce09fe55750f896d43908ac04b8af
<br><center><strong>Large circles break the GL_POINT_SMOOTH method</strong><br>
    <img src="/images/pizarro_circles4.png" width="481" height="289" alt="pizarro circles4">

<p>Fortunately, the situation in which a circle can become this big is very unlikely to be realised
    in Pizarro since collision with the bouncing red ball is difficult to avoid at larger sizes.
    Still, it's a graphical mar on the game which I'm not entirely happy with.  Better support for antialiasing 
in the OpenGL ES implementation would be a great boon to iOS developers and would eliminate the need for tricks like this one.

Finally, it goes without saying that this only works for filled circles and won't help you at all if you're just looking to draw a circle outline. Depending on your needs, the GL_POINT_SMOOTH technique could be used to draw anti-aliased non-filled circles by drawing a smooth point and then drawing another slightly smaller point in the background-color. This, of course, won't work if you have a textured background. Still, overall, it's a neat trick to have in the iOS development toolchest.

-- Sveinbjorn Thordarson
March 16th 2011