Triangles

Fans, Strips and Lists

Triangle Fans

Strips

Triangle Lists

Message Handler Update

Wireframe

Lesson Downloads

Fans, Strips and Lists

Triangles are the most complex object that today's video cards can render and these triangles come in 3 flavours: Fans, Strips and Lists. In this lesson we will explain how each of these is defined and show how to draw using them. To keep things easier to follow, each of the triangle drawing routines is in a separate function that has it's data declared locally.

I will also cover WireFrame mode and make a change to our message handler to add support for it.

Triangle Fans

Fans are very efficient space-wise. For each added vertex after the first 3 you get another triangle. The primary limitation is that the very first vertex anchors the whole fan, all of the remaining triangles must use that first vertex as their first vertex. You can make squares, fans, and other radial shapes. Each and every isolated shape you want to draw requires a new function call to the renderer. This is inefficient in terms of CPU use and video card use. Modern video cards like to be fed a lot of data at a time, ideally at least 100 triangles. Because of these limitations, Triangle Fans are rarely used anymore. Here is our function to draw 2 Triangle Fans.

void draw_fans(void){
static tri_vertex fan1[]={ //A square with a dark center

   {100,200,1,0xFF000000},  {0,100,1,1,0xFFFF0000},{200,100,1,1,0xFF00FF00},
   {200,300,1,1,0xFF0000FF},{0,300,1,1,0xFFFFFFFF},{  0,100,1,1,0xFFFF0000}
};
static tri_vertex fan2[]={ //A coloured fan

   {325,300,1,1,0xFFFFFFFF},
   {250,175,1,1,0xFFFF0000},{300,165,1,1,0xFF7F7F00},{325,155,1,1,0xFF00FF00},
   {375,165,1,1,0xFF007F7F},{400,185,1,1,0xFF0000FF}
};

   g_d3d_device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN,  //PrimitiveType

                                 4,                  //PrimitiveCount

                                 fan1,               //pVertexStreamZeroData

                                 sizeof(tri_vertex));//VertexStreamZeroStride


   g_d3d_device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN,  //PrimitiveType

                                 4,                  //PrimitiveCount

                                 fan2,               //pVertexStreamZeroData

                                 sizeof(tri_vertex));//VertexStreamZeroStride



}

In each case, the first vertex plotted is the central point for the object. The first object is a square with coloured corners and center. The second fan is something more recognizable as "fan-like". The standard culling rules apply, so make sure all triangles are defined with clockwise winding order. Since Triangle Fans are so rarely used (and for good reason) we won't spend more time on them.

Strips

Strips share the space-saving storage of Fans but have fewer limitations. The first 3 vertices define the first triangle and every additional vertex defines another triangle made from itself and the previous 2 vertices. Since a Strip isn't anchored to a single position like a Fan is you have fewer limitations in what you can represent. One odd thing about Strips is how the culling rules apply. For every second triangle, the culling rules are reversed, otherwise you wouldn't be able to make useful shapes with them. This can make generating Strips by hand a very tedious affair, but it's also something you would rarely do so it's not much of a limitation.

Another limitation Strips share with Fans is that you cannot create unconnected shapes. For each unconnected shape you would need a separate draw call. But with Strips there is a work around. You can use invisible triangles to stitch together otherwise unconnected shapes. There are 2 ways to do this. The first is to use back facing triangles, but if your model ever rotates then those "invisible" triangles could suddenly appear, so it's not a good solution. The second way is to use degenerate triangles. A degenerate triangle has no mathematical area, so it isn't rendered. This is generally done by repeating a vertex. If you have 2 strips, you can usually just repeat the last vertex of the first shape. Here is our draw_strips function.

void draw_strips(void){
static tri_vertex strip[]={
   //These define our first shape

   {250,150,1,1,0xFFFF0000},{300, 50,1,1,0xFF00FF00},{350,150,1,1,0xFF0000FF},
   {400, 50,1,1,0xFFFF0000},{450,150,1,1,0xFF7F7F00},
   //degenerate triangle to stitch them together

   {450,150,1,1,0xFF7F7F00},
   //Our second shape

   { 50,350,1,1,0xFFFF0000},{100,250,1,1,0xFF00FF00},{150,350,1,1,0xFF0000FF},
   {200,250,1,1,0xFFFF0000},{250,350,1,1,0xFF7F7F00},

};

   g_d3d_device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP,  //PrimitiveType

                                 9,                  //PrimitiveCount

                                 strip,              //pVertexStreamZeroData

                                 sizeof(tri_vertex));//VertexStreamZeroStride


}

The geometry may not be very exciting, but it should illustrate my point. Geometry that naturally falls into a strip-like shape is a good candidate for Strips, but with a bit of effort you can make anything work. While degenerate triangles aren't rendered some processing is still done on them. If you have a situation where you see that a lot of degenerate triangles are needed, you may want to consider using Triangle Lists instead.

Triangle Lists

Triangle Lists are by far the easiest type of triangles to deal with. They of course have the standard culling rules (clockwise only, by default anyway), but other than that you can do anything. You do pay a price for this flexibility though, each triangle has to be defined by 3 vertices on its own. In many cases, that's not a problem and their flexibility makes them worth using. In a later lesson we'll look at using Index Buffers, which make Triangle Lists almost as compact as similar Triangle Strips while maintaining their flexibility.

void draw_lists(void){
static tri_vertex data[]={
   //Fan1

   {100,100,1,1,0xFF000000},{  0,  0,1,1,0xFFFF0000},{200,  0,1,1,0xFF00FF00},
   {100,100,1,1,0xFF000000},{200,  0,1,1,0xFF00FF00},{200,200,1,1,0xFF0000FF},
   {100,100,1,1,0xFF000000},{200,200,1,1,0xFF0000FF},{  0,200,1,1,0xFFFFFFFF},
   {100,100,1,1,0xFF000000},{  0,200,1,1,0xFFFFFFFF},{  0,  0,1,1,0xFFFF0000},
   //Fan2

   { 75,350,1,1,0xFFFFFFFF},{  0,225,1,1,0xFFFF0000},{ 50,215,1,1,0xFF7F7F00},
   { 75,350,1,1,0xFFFFFFFF},{ 50,215,1,1,0xFF7F7F00},{ 75,205,1,1,0xFF00FF00},
   { 75,350,1,1,0xFFFFFFFF},{ 75,205,1,1,0xFF00FF00},{125,215,1,1,0xFF007F7F},
   { 75,350,1,1,0xFFFFFFFF},{125,215,1,1,0xFF007F7F},{150,235,1,1,0xFF0000FF},
   //Strip1

   {250,150,1,1,0xFFFF0000},{300, 50,1,1,0xFF00FF00},{350,150,1,1,0xFF0000FF},
   {300, 50,1,1,0xFF00FF00},{400, 50,1,1,0xFFFF0000},{350,150,1,1,0xFF0000FF},
   {400, 50,1,1,0xFFFF0000},{450,150,1,1,0xFF7F7F00},{350,150,1,1,0xFF0000FF},
   //Strip2

   {250,350,1,1,0xFFFF0000},{300,250,1,1,0xFF00FF00},{350,350,1,1,0xFF0000FF},
   {300,250,1,1,0xFF00FF00},{400,250,1,1,0xFFFF0000},{350,350,1,1,0xFF0000FF},
   {400,250,1,1,0xFFFF0000},{450,350,1,1,0xFF7F7F00},{350,350,1,1,0xFF0000FF},

};

static int vert_count=sizeof(data)/sizeof(tri_vertex);
static int tri_count=vert_count/3;

   g_d3d_device->DrawPrimitiveUP(D3DPT_TRIANGLELIST, //PrimitiveType

                                 tri_count,          //PrimitiveCount

                                 data,               //pVertexStreamZeroData

                                 sizeof(tri_vertex));//VertexStreamZeroStride


}

To show the flexibility of the Triangle List we're drawing all the shapes that were drawn by the Fans and Strips. The original fan data had 12 vertices, but required 2 calls to DrawPrimitiveUP. The fan data as Triangle Lists has 24 vertices but only requires 1 call. The original strip data had 11 vertices (1 degenerate) while the list version has 18, but required no special handling.

Using Fans it is not possible to draw the List data set in a single call. You could use Fans but you would end up making calls that only drew a Fan with a single triangle in it. This would be incredibly inefficient. And having to repeat the data would mean losing most of the compactness Fans can provide. So while Fans are very compact in uses that fit their specialty they quickly become unusable for general data sets.

Doing this same data set with Strips would work better, though it would require a number of degenerate triangles. Not only to stitch the separate shapes together, but also to reset your drawing position within the fans. This would add overhead and would likely not be much smaller than the equivalent Triangle List representation of the data.

To transform the Fan data to List format took a little work. The initial triangle is fine as it is. For the remaining triangles in each fan I set the first vertex to be equal to the initial/anchor vertex. The unique vertex was used as the 3rd vertex and the second vertex came from the previous triangles last vertex.

Transforming the Strip data took a bit more juggling because of the alternating culling rule. To be honest I just juggled things around until they looked right. Sometimes that's all it takes. Often the best thing you can do is doodle on some paper to see the vertices and the shape in context. Restaurant or cocktail napkins work well for this, though the ultimate doodling surface, serviettes are only available to Canadians. Just like chesterfields (not that you should doodle on your chesterfield, and no sawing the table either).

So even though Triangle Lists do take up much more space, they can fit any situation. Of course there is no reason that an application can't make use of all of these types. Just choose the representation that best fits your data.

Message Handler Update

To support toggling back and forth between Wirefame mode, I added a new global variable (g_wireframe) and set it to be toggled by the 'w' key. All it took was the addition of an "else if" to the WM_CHAR handler. Here's the WM_CHAR handler for reference.

   case WM_CHAR:  // A key has been pressed, process the key

      if (p_wparam=='t') {
         g_draw_mode++;
         if(g_draw_mode > 2){
            g_draw_mode=0;
         }
      }else if(p_wparam=='w'){
         g_wireframe=!g_wireframe;
      }
      return 0;

Wireframe

In Direct3D there are many renderstates. These set global parameters for how things should be rendered. The renderstate that controls Culling was introduced in an earlier lesson. Another very useful renderstate is the Fill Mode. The Fill Mode can be set to 3 different values: Point, Wireframe, Solid. Solid is the default so you're used to that. Point draws a pixel where each vertex is and Wireframe draws the edges of each triangle.

You generally won't use wireframe for rendering, but it's very handy for debugging. When you run the sample application and turn on wireframe (the 'w' key) you'll be able to see each triangle. Sometimes when a vertex is in the wrong position it can be hard to see exactly what is wrong. Switching to Wireframe mode often makes those problems easier to see.

Another common problem is having triangles not get rendered. If you change the Cull mode to counter-clockwise, then the triangles that would normally show up will disappear. If the triangle that was missing reappears, then you know it was a problem with it's winding order.

Here's the code added to our render function to toggle wireframe mode.

   if(g_wireframe){
      g_d3d_device->SetRenderState(D3DRS_FILLMODE,D3DFILL_WIREFRAME);
   }else{
      g_d3d_device->SetRenderState(D3DRS_FILLMODE,D3DFILL_SOLID);
   }

Lesson Downloads

Back