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 1). 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.
Using a 16×16 circle for the texture works well
for thick lines (see the left circle in Figure 1
and left panel in Figure 2). 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 2).
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 3.
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 1. The non-AA variant doesn’t need
texture coordinates, but we’re including them for simplicity’s
sake.
Example 1. 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 2 and refer
back to Figure 6-11 to visualize the six
extrusion vectors (N, S, NE, NW, SW, SE).
Example 2. 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 1, 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 3.
Example 3. 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); } }
|