In one of my recent (but misguided) attempts to improve efficiency, I’ve improved my circle rendering to take advantage of the wonders of shaders. Previously, I’ve rendered circles with a triangle fan with about 20 or so segments. I could make that number dynamic and have them adaptively subdivide based on the distance to the camera, but you have to think, why are we trying to draw curved shapes with triangles anyway?
Queue the shader. Here’s the solution: render a quad with texture coordinates (-1, -1 for top left corner, 1, 1 for bottom right etc.) and make a shader that uses those coordinates to calculate the distance the pixel is from the center of the quad, drawing an alpha of zero when it is outside the radius of the circle.
Here’s the shader code:
void main()
{
if((gl_TexCoord[1][0] * gl_TexCoord[1][0]) + (gl_TexCoord[1][1] * gl_TexCoord[1][1]) < 1.0)
gl_FragColor = gl_Color;
else
gl_FragColor[3] = 0.0;
}
It’s fairly simple: if the squared distance from texture coords 0, 0 is below the squared distance to the edge of the quad (1.0*1.0 = 1.0) then draw the usual color, otherwise set the alpha to zero. You use the squared distance to avoid an expensive square root operation. To adjust the radius of the circle, just adjust the size of the quad.
What if we want just the outline of a circle (or an inner radius)?
uniform float InnerRadiusSquared;
void main()
{
float v = (gl_TexCoord[1][0] * gl_TexCoord[1][0]) + (gl_TexCoord[1][1] * gl_TexCoord[1][1]);
if((v InnerRadiusSquared))
gl_FragColor = gl_Color;
else
gl_FragColor[3] = 0.0;
}
Now we can use the uniform InnerRadiusSquared to set the inner radius. It’ll need to be the ratio of the inner radius to the outer radius (InnerRadius / OuterRadius) squared (again for performance). Awesome. But what if we want it textured? The texture coordinates are being messed with, it wouldn’t appear correctly? Notice that we’re using texture unit 1 (not 0) e.g. gl_TexCoord[1][]. So when drawing the quad we need to specify the texture unit we are passing the coords on is unit 1. We can pass normal texture coordinates for our texture on unit 0. So the quad drawing code would be:
//Bind you shader here
glBegin(GL_TRIANGLE_FAN);
glMultiTexCoord2i(GL_TEXTURE0, 0, 1); //For the texture
glMultiTexCoord2i(GL_TEXTURE1, -1, 1); //For the shader
glVertex2f(-2.f, 2.f); //Radius of 2
glMultiTexCoord2i(GL_TEXTURE0, 1, 1);
glMultiTexCoord2i(GL_TEXTURE1, 1, 1);
glVertex2f(2.f, 2.f);
glMultiTexCoord2i(GL_TEXTURE0, 1, 0);
glMultiTexCoord2i(GL_TEXTURE1, 1, -1);
glVertex2f(2.f, -2.f);
glMultiTexCoord2i(GL_TEXTURE0, 0, 0);
glMultiTexCoord2i(GL_TEXTURE1, -1, -1);
glVertex2f(-2.f, -2.f);
glEnd();
And the shader:
uniform sampler2D tex; //Automatically set to texture unit 0
void main()
{
if((gl_TexCoord[1][0] * gl_TexCoord[1][0]) + (gl_TexCoord[1][1] * gl_TexCoord[1][1]) < 1.0)
gl_FragColor = texture2D(tex, gl_TexCoord[0].st);
else
gl_FragColor[3] = 0.0;
}
I haven’t covered the vertex shader, here it is:
void main()
{
gl_Position = ftransform();
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
gl_FrontColor = gl_Color;
}
It just passes the values through, nothing special. Anyway, you could add the inner radius code to this and have a textured, outlined circle with only two triangles. Yay! So how does it perform? I’d imagine much better, depending on the number of segments you would be using otherwise. LiquidEngine seems to be CPU bound at the moment, and it only made 2-3 frames difference (with about 200-300 circles). I’m sure this will pay off in the end though, when the CPU side is optimized more. Obviously you can optimize further by batching many circles together into VBOs etc. rather than using the horribly slow quad drawing code here.
One major issue though is anti-aliasing. I don’t think multisampling takes extra samples into surfaces covered by shaders (at least not by default). So either this feature must be somehow turned on, or the shader must anti-alias the circle edge. I’m not really sure at this point, but I’ll hopefully post a solution someway down the track.

