O'Reilly logo

iPhone 3D Programming by Philip Rideout

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

Rendering Anti-Aliased Lines with Textures

Sometimes full-screen anti-aliasing is more than you really need and can cause too much of a performance hit. You may find that you need anti-aliasing only on your line primitives rather than the entire scene. Normally this would be achieved in OpenGL ES like so:

glEnable(GL_LINE_SMOOTH);

Alas, none of the iPhone models supports this at the time of this writing. However, the simulator does support line smoothing; watch out for inconsistencies like this!

A clever trick to work around this limitation is filling an alpha texture with a circle and then tessellating the lines into short triangle strips (Figure 6-9). Texture coordinates are chosen such that the circle is stretched in the right places. That has the added benefit of allowing round end-cap styles and wide lines.

Line anti-aliasing with textured triangle strips

Figure 6-9. Line anti-aliasing with textured triangle strips

Using a 16×16 circle for the texture works well for thick lines (see the left circle in Figure 6-9 and left panel in Figure 6-10). For thinner lines, I find that a highly blurred 16x16 texture produces good results (see the right circle in Figure 6-9 and right panel in Figure 6-10).

Antialiased lines

Figure 6-10. Antialiased lines

Let’s walk through the process of converting a line list into a textured triangle list. Each source vertex needs to be extruded into four new vertices. It helps to give each extrusion vector a name using cardinal directions, as shown in Figure 6-11.

Line extrusion

Figure 6-11. Line extrusion

Before going over the extrusion algorithm, let’s set up an example scenario. Say we’re rendering an animated stick figure similar to Figure 6-10. Note that some vertices are shared by multiple lines, so it makes sense to use an index buffer. Suppose the application can render the stick figure using either line primitives or textured triangles. Let’s define a StickFigure structure that stores the vertex and index data for either the non-AA variant or the AA variant; see Example 6-17. The non-AA variant doesn’t need texture coordinates, but we’re including them for simplicity’s sake.

Example 6-17. Structures for the extrusion algorithm

struct Vertex {
    vec3 Position;
    vec2 TexCoord;
};

typedef std::vector<Vertex> VertexList;
typedef std::vector<GLushort> IndexList;
    
struct StickFigure {
    IndexList Indices;
    VertexList Vertices;
};

The function prototype for the extrusion method needs three arguments: the source StickFigure (lines), the destination StickFigure (triangles), and the desired line width. See Example 6-18 and refer back to Figure 6-11 to visualize the six extrusion vectors (N, S, NE, NW, SW, SE).

Example 6-18. Line extrusion algorithm

void ExtrudeLines(const StickFigure& lines, StickFigure& triangles, float width)
{
    IndexList::iterator sourceIndex = lines.Indices.begin();
    VertexList::iterator destVertex = triangles.Vertices.begin();
    while (sourceIndex != lines.Indices.end()) {
        
        vec3 a = lines.Vertices[lines.Indices[*sourceIndex++]].Position;
        vec3 b = lines.Vertices[lines.Indices[*sourceIndex++]].Position;
        vec3 e = (b - a).Normalized() * width;

        vec3 N = vec3(-e.y, e.x, 0);
        vec3 S = -N;
        vec3 NE = N + e;
        vec3 NW = N - e;
        vec3 SW = -NE;
        vec3 SE = -NW;
        
        destVertex++->Position = a + SW;
        destVertex++->Position = a + NW;
        destVertex++->Position = a + S;
        destVertex++->Position = a + N;
        destVertex++->Position = b + S;
        destVertex++->Position = b + N;
        destVertex++->Position = b + SE;
        destVertex++->Position = b + NE;
    }
}

At this point, we’ve computed the positions of the extruded triangles, but we still haven’t provided texture coordinates for the triangles, nor the contents of the index buffer. Note that the animated figure can change its vertex positions at every frame, but the number of lines stays the same. This means we can generate the index list only once; there’s no need to recompute it at every frame. The same goes for the texture coordinates. Let’s declare a couple functions for these start-of-day tasks:

void GenerateTriangleIndices(size_t lineCount, IndexList& triangles);
void GenerateTriangleTexCoords(size_t lineCount, VertexList& triangles);

Flip back to Figure 6-9, and note the number of triangles and vertices. Every line primitive extrudes into six triangles composed from eight vertices. Since every triangle requires three indices, the number of indices in the new index buffer is lineCount*18. This is different from the number of vertices, which is only lineCount*8. See Example 6-19.

Example 6-19. Line extrusion initialization methods

void GenerateTriangleIndices(size_t lineCount, IndexList& triangles)
{
    triangles.resize(lineCount * 18);
    IndexList::iterator index = triangles.begin();
    for (GLushort v = 0; index != triangles.end(); v += 8) {
        *index++ = 0 + v; *index++ = 1 + v; *index++ = 2 + v;
        *index++ = 2 + v; *index++ = 1 + v; *index++ = 3 + v;
        *index++ = 2 + v; *index++ = 3 + v; *index++ = 4 + v;
        *index++ = 4 + v; *index++ = 3 + v; *index++ = 5 + v;
        *index++ = 4 + v; *index++ = 5 + v; *index++ = 6 + v;
        *index++ = 6 + v; *index++ = 5 + v; *index++ = 7 + v;
    }
}

void GenerateTriangleTexCoords(size_t lineCount, VertexList& triangles)
{
    triangles.resize(lineCount * 8);
    VertexList::iterator vertex = triangles.begin();
    while (vertex != triangles.end()) {
        vertex++->TexCoord = vec2(0, 0);
        vertex++->TexCoord = vec2(0, 1);
        vertex++->TexCoord = vec2(0.5, 0);
        vertex++->TexCoord = vec2(0.5, 1);
        vertex++->TexCoord = vec2(0.5, 0);
        vertex++->TexCoord = vec2(0.5, 1);
        vertex++->TexCoord = vec2(1, 0);
        vertex++->TexCoord = vec2(1, 1);
    }
}

Et voilà…you now know how to render antialiased lines on a device that doesn’t support antialiased lines! To see this in action, check out the AaLines sample from this book’s example code.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required