Section 3.2
Primitives
The term "primitive" in computer graphics refers to a geometry element that is not made up of simpler elements. In OpenGL, the primitives are points, lines (meaning line segments), and polygons. Other graphics systems might have additional primitives such as Bezier curves, circles, or spheres, but in OpenGL, such shapes have to be approximated by lines or polygons.
Primitives can be generated using glBegin/glEnd or by more efficient methods that we will cover in Section 3.3. In any case, though, the type of primitive that you are generating is indicated by a constant such as GL.GL_POLYGON that could be passed as a parameter to gl.glBegin. There are ten such constants, which are said to define ten different primitive types in OpenGL. (For most of these primitive types, a single use of glBegin/glEnd can produce several basic primitives, that is, several points, lines, or polygons.)
In this section, we'll look at the ten primitive type constants and how they are used to generate primitives. We'll also cover some of the attributes that can be applied to primitives to modify their appearance.
3.2.1 Points
Individual points can be generated by using the primitive type GL.GL_POINTS. With this primitive type, each vertex that is specified generates an individual point. For example, the following code generates 10 points lying along the circumference of a circle of radius 1 in the xy-plane:
gl.glBegin(GL.GL_POINTS); for (int i = 0; i < 10; i++){ double angle = (2*Math.PI/10)*i; // Angle in radians, for Java's functions. gl.glVertex3d( Math.cos(angle), Math.sin(angle), 0); } gl.glEnd();
By default, a point is drawn as a single pixel. If lighting is off, the pixel takes its color from the current drawing color as set by one of the glColor methods. If lighting is on, then the same lighting calculations are done for the point as would be done for the vertex of a polygon, using the current material, normal vector, and so on; this can have some interesting effects, but generally it makes more sense to draw points with lighting disabled.
An individual pixel is just barely visible. The size of a point can be increased by calling gl.glPointSize(size). The parameter is a float that specifies the diameter of the point, in pixels. The range of sizes that is supported depends on the implementation.
A point that has size larger than 1 appears by default as a square, which might be a surprise. To get circular points, you have to turn on point smoothing by calling
gl.glEnable(GL.GL_POINT_SMOOTH);
Just turning on this option will leave the edges of the point looking a little jagged. You can get nicely antialiased points by also using blending. Recall that antialiasing can be implemented by using partial transparency for pixels that are only partially covered by the object that is being drawn. When a partially transparent pixel is drawn, its color is blended with the previous color of the pixel, rather than replacing it. Unfortunately, blending and transparency are fairly complicated topics in OpenGL. We will discuss them in more detail in the next chapter. For now, I will just note that you can generally get good results for antialiased points by setting the following options, in addition to GL_POINT_SMOOTH:
gl.glEnable(GL.GL_BLEND); gl.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);
However, you should remember to turn off the GL_BLEND option when drawing polygons.
3.2.2 Lines
There are three primitive types for generating lines: GL.GL_LINES, GL.GL_LINE_STRIP, and GL.GL_LINE_LOOP. With GL_LINES, the vertices that are specified are paired off, and each pair is joined to produce a line segment. If the number of vertices is odd, the last vertex is ignored. When GL_LINE_STRIP is used, a line is drawn from the first specified vertex to the second, from the second to the third, from the third to the fourth, and so on. GL_LINE_LOOP is similar, except that one additional line segment is drawn from the final vertex back to the first. This illustration shows the line segments that would be drawn if you specified the points A, B, C, D, E, and F, in that order, using the three primitive types:
When a line segment is drawn, a color is first assigned to each vertex, as usual, just as if it were a vertex in a polygon, including all lighting calculations when lighting is enabled. If the current shade model is GL_SMOOTH, then the vertex colors are interpolated between the two vertices to assign colors to the points along the line. If the shade model is GL_FLAT, then the color of the second vertex is used for the entire segment.
By default, the width of a line is one pixel. The width can be changed by calling gl.glLineWidth(width), where width is a value of type float that specifies the line width in pixels. To get antialiased lines, you should call
gl.glEnable(GL.GL_LINE_SMOOTH);
and you should set up blending with the same two commands that were given above for antialiased points.
A line can also have an attribute called a stipple pattern which can be used to produce dotted and dashed lines. The stipple pattern is set by calling
gl.glLineStipple( multiplier, pattern );
where pattern is a value of type short and multiplier is an integer. The 16 bits in pattern specify how drawing is turned on and off along the line, with 0 turning drawing off and 1 turning it on. For example, the value (short)0x0F33 represents the binary number 0000111100110011, and drawing is tuned off for four steps along the line, on for four steps, off for two, on for two, off for two, and finally on for the final two steps along the line. The pattern then repeats. The multiplier tells the size of the steps. When multiplier is one, each bit in pattern corresponds to one pixel-length along the line; when multiplier is two, each bit in pattern corresponds to two pixel-lengths; and so on. The pattern value (short)0x0F33 would draw the line with a long-dash / short-dash / short-dash stippling, and the multiplier value would determine the length of the dashes. (Exactly the same stippling could be produced by using pattern equal to (short)0x3535 and doubling the multiplier.)
The line stipple pattern is only applied if the GL_LINE_STIPPLE option is enabled by calling
gl.glEnable(GL.GL_LINE_STIPPLE);
3.2.3 Polygons
The remaining six primitive types are for drawing polygons. The familiar GL.GL_POLYGON, which we used almost exclusively before this section, is used to draw a single polygon with any number of sides. The other five polygon primitives are for drawing triangles and quadrilaterals and will be covered later in this section.
Up until now, I haven't bothered to mention an important fact about polygons in OpenGL: It is correct to draw a polygon in OpenGL only if the polygon satisfies certain requirements. OpenGL will attempt to draw a polygon using any sequence of vertices that you give it, by following its usual rendering algorithms. However, for polygons that don't meet the requirements, there is no reason to expect that the results will be geometrically correct or meaningful, and they might vary from one OpenGL implementation to another.
First of all, very reasonably, any polygon drawn by OpenGL should be planar; that is, all of its vertices should lie in the same plane. It is not even clear what a non-planar polygon would mean. But you should be aware that OpenGL will not report an error if you specify a non-planar polygon. It will just draw whatever its algorithms produce from the data you give them. (Note that polygons that are very close to being planar will come out OK, so you don't have to be too obsessive about it.)
The other requirement is less reasonable: Polygons drawn by OpenGL must be convex. The technical definition of convex is that whenever two points lie in the polygon, then the entire line segment between the two points also lies in the polygon. Informally, convexity means that the polygon does not have any indentations along its edge. Note that a convex polygon can never be self-intersecting, like the non-convex polygon on the lower right in the illustration below.
In order to draw a non-convex polygon with OpenGL, you have to sub-divide it into several smaller convex polygons and draw each of those smaller polygons.
When drawing polygons, you should keep in mind that a polygon has a front side and a back side, and that OpenGL determines which side is the front by the order in which the vertices of the polygon are generated. In the default setup, the vertices have to be specified in counterclockwise order when viewed from the front (and therefore in clockwise order when viewed from the back). You can reverse this behavior by calling gl.glFrontFace(GL.GL_CW). You might do this, for example, if you read your polygon data from a file that lists vertices in clockwise order as seen from the front.
The front/back distinction is used in two cases. First, as we will see in the next chapter, it is possible to assign different material properties to the front faces and to the back faces of polygons, so that they will have different colors. Second, OpenGL has an option to do back-face culling. This option can be turned on with gl.glEnable(GL.GL_CULL_FACE). It tells OpenGL to ignore a polygon when the user is looking at the back face of that polygon. This can be done for efficiency when the polygon is part of a solid object, and the back faces of the objects that make up the object are all facing the interior of the object. In that case, the back faces will not be visible in the final image, so any time spent rendering them would be wasted.
When drawing polygons, whether with GL_POLYGON or with the more specialized primitive types discussed below, the default is to to draw the interior of the polygons. Sometimes, it is useful just to draw the edges of the polygon. For example, you might want to draw a "wireframe" representation of a solid object, showing just the polygon edges. Or you might want to fill a polygon with one color and then outline it with another color. You can use glPolygonMode to determine how polygons are rendered. You can actually apply different settings to front faces and back faces at the same time. Assuming that you want to treat both faces the same, the three possible settings are:
gl.glPolygonMode( GL.GL_FRONT_AND_BACK, GL.GL_FILL ) gl.glPolygonMode( GL.GL_FRONT_AND_BACK, GL.GL_LINE ) gl.glPolygonMode( GL.GL_FRONT_AND_BACK, GL.GL_POINT )
The default mode is GL_FILL, where the interior of the polygon is drawn. The GL_LINE mode produces an outline of the polygon. And the GL_POINT mode just places a point at each vertex of the polygon.
To draw both the interior and the outline, you have to draw the polygon twice, once using the GL_FILL polygon mode and once using GL_LINE. When you do this, however, you run into a problem with the depth test that was discussed in Subsection 2.2.2: Along an edge of the polygon, the interior and the outline of the polygon are at the same depth, and because of computational inaccuracy, the depth test can give erratic results when comparing objects that have the same depth values. At some points, you see the edge; at others, you see the interior color. OpenGL has a fix for this problem, called "polygon offset." Here are two pictures of the same surface. The surface is rendered by drawing a large number of triangles. The picture on the top left does not use polygon offset; the one on the bottom right does:
Polygon offset can be used to add a small amount to the depth of the pixels that make up the polygon. In this case, I applied polygon offset while filling the triangles but not while drawing their outlines. This eliminated the problem with drawing things that have the same depths, since the depth values for the polygon and its outline are no longer exactly the same.
Here is some code for drawing the above picture with polygon offset, assuming that the method drawSurface generates the polygons that make up the surface.
gl.glEnable(GL.GL_LIGHTING); // Draw polygon interiors with lighting enabled. gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL); // Draw polygon interiors. gl.glEnable(GL.GL_POLYGON_OFFSET_FILL); // Turn on offset for filled polygons. gl.glPolygonOffset(1,1); // Set polygon offset amount. drawSurface(gl); // Fill the triangles that make up the surface. gl.glDisable(GL.GL_LIGHTING); // Draw polygon outlines with lighting disabled. gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE); // Draw polygon outlines. gl.glColor3f(0,0,0); // Draw the outlines in black. drawSurface(gl); // Outline the triangles that make up the surface again.
Polygon offset can be enabled separately for the FILL, LINE, and POINT polygon modes. Here, it is enabled only for the FILL mode, so it does not apply when the outlines are drawn. The glPolygonOffset method specifies the amount of the offset. The parameters used here, (1,1), produce a minimal change in depth, just enough to make the offset work. (The first parameter, which I don't completely understand, compensates for the fact that a polygon that is tilted relative to the screen will require a bigger change in depth to be effective.)
3.2.4 Triangles
Triangles are particularly common in OpenGL. When you build geometry out of triangles, you don't have to worry about planarity or convexity, since every triangle is both planar and convex. OpenGL has three primitive types for drawing triangles: GL.GL_TRIANGLES, GL.GL_TRIANGLE_STRIP, and GL.GL_TRIANGLE_FAN.
With GL_TRIANGLES, the vertices that you specify are grouped into sets of three, and every set of three generates a triangle. If the number of vertices is not a multiple of three, then the one or two extra vertices are ignored. You can, of course, use GL_TRIANGLES to specify a single triangle by providing exactly three vertices.
With GL_TRIANGLE_STRIP, the first three vertices specify a triangle, and each additional vertex adds another triangle, which is formed from the new vertex and the two previous vertices. The triangles form a "strip" or "band" with the vertices alternating from one side of the strip to the other.
This illustration shows the triangles produced using GL_TRIANGLES and GL_TRIANGLE_STRIP with the same set of vertices A, B, ..., I, specified in that order:
Note that the vertices of triangle DEF in the picture on the left are specified in clockwise order, which means that you are looking at the back face of this triangle. On the other hand, you are looking at the front face of triangles ABC and GHI. When using GL_TRIANGLES, each triangle is treated as a separate polygon, and the usual rule for determining the front face apply.
In the picture that is made by GL_TRIANGLE_STRIP, on the other hand, you are looking at the front faces of all the triangles. To make this true, the orientation of every second triangle is reversed; that is, the order of the vertices is considered to be the reverse of the order in which they are specified. So, in the first triangle, the ordering is ABC; in the second, the order is DCB; in the third, CDE; in the fourth, FED, and so on. This might look complicated, but you rarely have to think about it, since it's the natural order to ensure that the front faces of all the triangles are on the same side of the strip.
The third primitive for drawing triangles is GL_TRIANGLE_FAN. It is less commonly used than the other two. In a triangle fan, the first vertex that is specified becomes one of the vertices of every triangle that is generated, and those triangles form a "fan" around that initial vertex. This illustration shows typical uses of GL_TRIANGLE_FAN:
3.2.5 Quadrilaterals
Finally, OpenGL has two primitives for drawing quadrilaterals: GL.GL_QUADS and GL.GL_QUAD_STRIP. A quadrilateral is a four-sided polygon. As with any polygon in OpenGL, to be valid, a quadrilateral must be planar and convex. Because of this restriction, it's more common to use triangles than quads. However, there are many common objects for which quads are appropriate, including all the shape classes in my glutil package.
GL_QUADS, as you can probably guess, draws a sequence of independent quadrilaterals, using groups of four vertices. GL_QUAD_STRIP draws a strip or band made up of quadrilaterals. Here is an illustration showing the quadrilaterals produced by GL_QUADS and GL_QUAD_STRIP, using the same set of vertices -- although not generated in the same order:
For GL_QUADS, the vertices of each quadrilateral should be specified in counterclockwise order, as seen from the front. For GL_QUAD_STRIP, the vertices should alternate from one side of the strip to the other, just as they do for GL_TRIANGLE_STRIP. In fact, GL_TRIANGLE_STRIP can always be used in place of GL_QUAD_STRIP, with the same vertices in the same order. (The reverse is not true, since the quads that are produced when GL_QUAD_STRIP is substituted for GL_TRIANGLE_STRIP might not be planar.)
As an example, let's approximate a cylinder -- without a top or bottom -- using a quad strip. Assume that the cylinder has radius r and height h and that its base is in the xy-plane, centered at (0,0). If we use a strip of 32 quads, then the vertices along the bottom of the cylinder have coordinates (r*cos(d*i),r*sin(d*i),0), for i = 0, ...,31, where d is 1/32 of a complete circle. Similarly, the vertices along the top edge of the cylinder have coordinates (r*cos(d*i),r*sin(d*i),h). Furthermore, the unit normal to the cylinder at the point (r*cos(d*i),r*sin(d*i),z) is (cos(d*i),sin(d*i),0) for both z = 0 and z = h. So, we can use the following code to generate the quad strip:
double d = (1.0/32) * (2*Math.PI); // 1/32 of a circle, for Java's functions. gl.glBegin(GL.GL_QUAD_STRIP); for (int i = 0; i <= 32; i++) { // Generate a pair of points, on top and bottom of the strip. gl.glNormal3d( Math.cos(d*i), Math.sin(d*i, 0); // Normal for BOTH points. gl.glVertex3d( r*Math.cos(d*i), r*Math.sin(d*i), h ); // Top point. gl.glVertex3d( r*Math.cos(d*i), r*Math.sin(d*i), 0 ); // Bottom point. } gl.glEnd();
The order of the points, with the top point before the bottom point, is chosen to put the front faces of the quads on the outside of the cylinder. Note that if GL_TRIANGLE_STRIP were substituted for GL_QUAD_STRIP in this example, the results would be identical as long as the usual GL_FILL polygon mode is selected. If the polygon mode is set to GL_LINE, then GL_TRIANGLE_STRIP would produce an extra edge across the center of each quad.
Here is an applet that lets you experiment with the ten primitive types and with options that affect the way they are drawn.
When the applet starts, there are 8 points. The points are arranged in a circle and are generated in counterclockwise order, starting at the top of the circle. Each point is surrounded by a small white hexagon that is not part of the primitive. You can drag the points, and you can delete them all by clicking the "CLEAR" button. You can add a new point by double-clicking. You can select the type of primitive that is drawn with the points, and you can set other options, using the controls below the drawing area. When polygons are drawn, they are both filled and outlined in black. Note that the original positions of the points are not suitable for drawing triangle strips or quad strips, and there is nothing in the program to protect you from non-convex polygons. By default, the points are assigned colors that range over the entire spectrum. When you add a point, the colors change; if you find this confusing, you can turn off the colors. This is a purely 2D OpenGL application, and it does not use lighting.