2. Geometric Primitive Types

OpenGL requires you to specify the Geometric Primitive Type of the Vertices you wish to draw. This is usually expected when you begin drawing in either Immediate Mode (GL.Begin), GL.DrawArrays or GL.DrawElements.

Geometric Primitive Types in OpenTK.OpenGL

Fig. 1: In the above graphic all valid Geometric Primitive Types are shown, their winding is Clockwise (irrelevant for Points and Lines).

This is important, because drawing a set of Vertices as Triangles, which are internally set up to be used with Quads, will result only in garbage being displayed.
Examine Figure 1, you will see that v3 in a Quad is used to finish the shape, while Triangles uses v3 to start the next shape. The next drawn Triangle will be v3, v4, v5 which isn't something that belongs to any surface, if the Vertices were originally intended to be drawn as Quads.

However Points and Lines are an Exception here. You can draw every other Geometric Primitive Type as Points, in order to visualize the Vertices of the Object. Some more possibilities are:

  • QuadStrip, TriangleStrip and LineStrip can be interchanged, if the source data isn't a LineStrip.
  • Quads can be drawn as Lines with the restriction that there are no lines between v1, v2 and v3, v0.
  • Polygon can be drawn as LineLoop
  • TriangleFan can be drawn as Polygon or LineLoop

The smallest common denominator for all filled surfaces (i.e. no Points or Lines) is the Triangle. This Geometric Primitive Type has the special attribute of always being planar and is currently the best way to describe a 3D Object to GPU hardware.
While OpenGL allows to draw Quads or Polygons aswell, it is quite easy to run into lighting problems if the surface is not perfectly planar. Internally, OpenGL breaks Quads and Polygons into Triangles, in order to rasterize them.

  1. Points
    Specifies 1 Point per Vertex v, thus this is usually only used with GL.DrawArrays().
    n Points = Vertex * (1n);
  2. Lines
    Two Vertices form a Line.
    n Lines = Vertex * (2n);
  3. LineStrip
    The first Vertex issued begins the LineStrip, every consecutive issued Vertex marks a joint in the Line.
    n Line Segments in the Strip = Vertex * (1+1n)
  4. LineLoop
    Same as LineStrip, but the very first and last issued Vertex are automatically connected by an extra Line segment.
    n Line Segments in the Loop = Vertex * (1n);
  5. Polygon
    Note that the first and the last Vertex will be connected automatically, just like LineLoop.
    Polygon with n Edges = Vertex * (1n);
    Note: This primitive type should really be avoided whenever possible, basically the Polygon will be split to Triangles in the end anyways. Like Quads, polygons must be planar or be displayed incorrectly. Another Problem is that there is only 1 single Polygon in a begin-end block, which leads to multiple draw calls when drawing a mesh, or using the Extensions GL.MultiDrawElements or GL.MultiDrawArrays.
  6. Quads
    Quads are especially useful to work in 2D with bitmap Images, since those are typically rectangular aswell. Care has to be taken that the surface is planar, otherwise the split into Triangles will become visible.
    n Quads = Vertex * (4n);
  7. QuadStrip
    Like the Triangle-strip, the QuadStrip is a more compact representation of a sequence of connected Quads.
    n Quads in Quadstrip = Vertex * (2+2n);
  8. Triangles
    This way to represent a mesh offers the most control over how the Triangles are sorted, a Triangle always consists of 3 Vertex.
    n Triangles = Vertex * (3n);
    Note: It might look like an inefficient brute force approach at first, but it has it's advantages over TriangleStrip. Most of all, since you are not required to supply Triangles in sequenced strips, it is possible to arrange Triangles in a way that makes good use of the Vertex Caches. If the Triangle you currently want to draw shares an edge with one of the Triangles that have been recently drawn, you get 2 Vertices, that are stored in the Vertex Cache, almost for free. This is basically the same what stripification does, but you are not restricted to a certain Direction and forced to insert degenerated Triangles.
  9. TriangleStrip
    The idea behind this way of drawing is that if you want to represent a solid and closed Object, most neighbour Triangles will share 2 Vertices (an edge). You start by defining the initial Triangle (3 Vertices) and after that every new Triangle will only require a single new Vertex for a new Triangle.
    n Triangles in Strip = Vertex * (2+1n);
    Note: While this primitive type is very useful for storing huge meshes (2+1n Vertices per strip as opposed to 3n for BeginMode.Triangles), the big disadvantage of TriangleStrip is that there is no command to tell OpenGL that you wish to start a new strip while inside the glBegin/glEnd block. Ofcourse you can glEnd(); and start a new strip, but that costs API calls. A workaround to avoid exiting the begin/end block is to create 2 or more degenerate Triangles (you can imagine them as Lines) at the end of a strip and then start the next one, but this also comes at the cost of processing Triangles that will inevitably be culled and aren't visible. Especially when optimizing an Object to be in a Vertex Cache friendly layout, it is essential to start new strips in order to reuse Vertices from previous draws.
  10. TriangleFan
    A fan is defined by a center Vertex, which will be reused for all Triangles in the Fan, followed by border Vertices. It is very useful to represent convex n-gons consisting of more than 4 vertices and disc shapes, like the caps of a cylinder.

When looking at the graphic, Triangle- and Quad-strips might look quite appealing due to their low memory usage. They are beneficial for certain tasks, but Triangles are the best primitive type to represent an arbitrary mesh, because it's not restricting locality and allows further optimizations. It's just not realistic that you can have all your 3D Objects in Quads and OpenGL will split them internally into Triangles anyway. 3 ushort per Triangle isn't much memory, and still allows to index 64k unique Vertex in a mesh, the number of Triangles can be much higher. Don't hardwire BeginMode.Triangles into your programs though, for example Quads are very commonly used in orthographic drawing of UI Elements such as Buttons, Text or Sprites.

Should TriangleStrip get an core/ARB command to start a new strip within the begin/end block (only nVidia driver has such an Extension to restart the primitive) this might change, but currently the smaller data structure of the strip does not make up for the performance gains a Triangle List gets from Vertex Cache optimization. Ofcourse you can experiment with the GL.MultiDraw Extension mentioned above, but using it will break using other Extensions such as DirectX 10 instancing.