Note: this article was written a long time ago and contains OpenGL Primitive Drawing techniques that are outdated by modern standards (they are not using VBO bindings.) However, The Immediate mode (which is described in this article) is often used to study OpenGL from scratch. The principle are still the same! Just keep this in mind when reading this article, that this is no longer the optimal way of rendering primitives in an OpenGL application.
In computer graphics, a 3-dimensional primitive can be anything from a single point to an n-sided polygon. From the software standpoint, primitives utilize the basic 3-dimensional rasterization algorithms such as Bresenham's line drawing algorithm, polygon scan line fill, texture mapping and so forth.
But on this page, let us learn about the very basic nature of primitives and the OpenGL API approach to drawing them. Primitives make up entire 3-dimensional objects. In computer games, for example it is the character models and enormous environments that seem to stretch for miles.
The type of a primitive is defined by the number of vertices it is constructed from. A point has only one vertex, a line has two, a triangle has three. But what is a vertex exactly? Is it one of the edge-points of an n-sided polygon or is it a single point in space?
The answer is that it can be both depending on the interpretation of the problem you are trying to solve or more importantly, the context in which it is used. By definition, a single primitive shares the same plane with all of its vertices. Let's briefly go over the definition of the basic primitives and then jump into the OpenGL code that allows us to draw them on the screen.
A point, for example (a single location defined by [x, y, z] coordinates), is defined by one vertex, but often simply thought of as a simple 3D location in space. Points are used in many various places in computer graphics, but without a doubt, they are extra-ordinarily useful when adding detail to your 3d environments.
Points can make excellent particle effects such as sparks, or dust particles glimmering as they pass through the rays of light. Even though we are working with 3D graphics, rendering a single point on the screen by itself creates an illusion of 2D space, because our monitor screens are flat, or 2-dimensional and there is nothing to indicate depth. Therefore, one way to make points seem interactive is to have them move on the screen in interesting patterns such as circles, spirals and variations of the sine and cosine formulas.
A line has only 2 vertices - which are its starting and its ending points; a line can be thought of as two connected vertices in 3D space, if the two vertices are positioned in the same exact [x,y,z] location, it can be thought of as a point, but almost never used in that way. Lines are great for visual representations of the concepts of physics, such as springs, as well as other interesting objects that don't make sense to be drawn with points or polygons, such as electrical wires.
A polygon is a 3-Dimensional surface defined by 3 or more vertices. A triangle is, for instance, a polygon with 3 vertices. This all may already be obvious to you if you have previously studied 3D graphics, but note that the triangle is the most-useful basic planar primitive in 3-dimensional computer graphics. We make entire objects out of polygons, characters and game environments. It is not uncommon for graphics cards to use the number of polygon being rendered in one frame as a benchmark standard for testing the hardware speed. Modern graphics cards are capable of rendering billions of triangles on the screen at an optimal frame rate for smooth, real-time computer graphics simulations.
A quad is not truly a primitive, because in software it is always broken into two triangles anyway. However, under the OpenGL implementation quads are treated as separate primitives, as long as the two triangles they always consist of are coplanar. Some 3d modeling software such as ZBrush only allows quads as the building point for the models for many different reasons. Clearly, for example, it is easier to do spatial math on quads than it is on triangles.
A cube is not a primitive and is generally thought of as a 3d object, consisting of primitives that are triangles or quads (depending on API implementation). Usually in computer graphics primitives restrict themselves to triangles, or quads. A four-sided polygon can generate a quad but that quad will still be composed of 2 polygons. Remember that OpenGL implementation treats quads as a separate primitive, as long as both of the triangles it consists of are coplanar. We will see why, shortly.
glVertex2f(100.0f, 150.0f); defines a point at x = 100, y = 150, z = 0; this function takes only 2 parameters, z is always 0. glVertex2f can be used in special cases and won't be used a lot unless you're working with pseudo-2D sprites or triangles and points that always have to be constrained by the depth coordinate.
glVertex3f(100.0f, 150.0f, -25.0f); defines a point at x = 100, y = 150, z = -25.0f; this function takes 3 parameters, defining a fully 3D point in your world. This function will be used a lot to define any kind of shapes you will possibly want.
glVertex4f(100.0f, 150.0f, -25.0f, 1.0f); this is the same as glVertex3f, the only difference is in the last coordinate that specifies a scaling factor. The scaling factor is set to 1.0f by default. This won't make a lot of use and I'm not going to explain this function in details. It can be used to make your 3D points look thicker than one pixel. I don't want to sound pathetic but why would you want to use that functionality? (A few boring ideas creep into my mind but you'll just have to trust me with this, we're not going to use this function. If you're really desperate, I recommend searching the net for it in hope that someone out there explained it).
glBegin( int mode ); and glEnd( void );
glBegin and glEnd delimit the vertices of a primitive or a group of like primitives. What this means is that everytime you want to draw a primitive on the screen you will first have to call glBegin, specifying what kind of primitive it is that you want to draw in the mode parameter of glBegin, and then list all vertices one by one (by sequentially calling glVertex) and finally call glEnd to let OpenGL know that you're done drawing a primitive. The parameter mode of the function glBegin can be one of the following:
These flags are self-explanatory.
As example I want to show how to draw some primitives and then you will be able to understand how to draw the rest of them.
// This code will draw a point located at [100, 100, -25]
glVertex3f(100.0f, 100.0f, -25.0f);
// Next code will draw a line at starting and ending coordinates specified by glVertex3f
glVertex3f(100.0f, 100.0f, 0.0f); // origin of the line
glVertex3f(200.0f, 140.0f, 5.0f); // ending point of the line
// The following code draws a triangle</span>
glVertex3f(100.0f, 100.0f, 0.0f);
glVertex3f(150.0f, 100.0f, 0.0f);
glVertex3f(125.0f, 50.0f, 0.0f);
// This code will draw two lines "at a time" to save
// the time it takes to call glBegin and glEnd.
glVertex3f(100.0f, 100.0f, 0.0f); // origin of the FIRST line
glVertex3f(200.0f, 140.0f, 5.0f); // ending point of the FIRST line
glVertex3f(120.0f, 170.0f, 10.0f); // origin of the SECOND line
glVertex3f(240.0f, 120.0f, 5.0f); // ending point of the SECOND line
As you can see OpenGL makes drawing primitives a very simple task indeed. Take a look at the last code example where I demonstrate drawing 2 lines at a time. This functionality can be used with any primitive as long as you specify the right number of vertices.
For example, to draw two lines in one glBegin-glEnd sequence you need to specify FOUR vertices. Likewise, to draw 2 triangles in one shot, you would call glBegin(GL_TRIANGLES) and specify SIX vertices afterwards. glBegin and glEnd can be expensive calls in tight loops.
If you have an object containing a polygon and, say a line (to indicate its normal?), it would be better to have two loops: first one for drawing all the polygons in the object and second one to draw all the lines in the objects, rather than calling glBegin and glEnd 4 times in one loop each iteration.
This is an oversimplified example, don't take my word for it. I honestly haven't tested whether that would actually be faster (please e-mail me if you know for sure), but I am almost sure that it will be. As you all know, in programming sometimes it can be faster, sometimes it can't be.
It all depends on the situation and I don't have the time right now to investigate the effects of glBegin and glEnd versus the for-loops in different cases. Anyway, back to the topic.
glVertex is not the only function you can use inside glBegin and glEnd. Here is the full listing of all functions you can use inside glBegin and glEnd (for reference only, these functions will be explained later in following tutorials as we explore OpenGL in more detail):
If any other OpenGL function is called between glBegin and glEnd, the error flag is set and the function is ignored.
The next tutorial will put to action what I just described here for each glBegin mode. Drawing points and lines is fairly obvious so I'm not going to cover it. I will start with drawing triangles right away since triangles are most important primitives in 3D graphics. In addition to this tutorial I, again, included a printable version of all functions that can be used inside glBegin and glEnd with brief coverage of each; don't try to memorize what each function does on the list because it's a hard task, since those functions have to be used in action in order to be completely understood. And they will be used in action in future tutorials.