[ Nomad Programming, Graphics, and Algorithms Tutorial ] [ Compiler : MS Visual C++ 6.0 ] [ Part 12 - Drawing Primitives ] [ http://nomad.openglforums.com ] <<------------------------------------------------------>> [ DISCLAIMER ] I will hold no responsibility to whatever happens to you, your computer, your sanity, your pet, or whatever that may happen to your existence for your reading these texts and for the outcome of the various source code(s) given in each tutorial. So in short, read at your own risk! [ INTRODUCTION ] Here we learn how to draw points, lines, triangles, and squares. If you're thinking what's the big deal about learning them, then be surprised that EVERY computer game you've played uses either points, lines, triangles, or squares...:). In fact, many video cards that offer hardware acceleration are optimized to display triangles only!!! So how would learning how to plot triangles and squares help you to create your Quake-3 engine clone? Well, every 3D model are rendered through (usually) triangles or (not so usually) squares! And not only the models are drawn this way, in fact, even the whole WORLD/MAP are rendered with triangles or squares! SideNote: Actually, the most important thing to learn here is how to draw triagles. Squares are not usually used since they're usually not accelerated by the video/graphics card. I personally use only the line and triangle rendering of OpenGL...the line to draw wireframe stuffs, the triangle to draw solid polygons...;). [ TUTORIAL ] Let's start by knowing that OpenGL uses various states to draw geometric objects. What are states? No, they (obviously) have nothing to do with the United States of America (cheap joke, really), they are what controls the output/rendering of your polygons (I DO hope that you at least know what a polygon is? Whazzat? You do? Oh, well, that's a relief...whew!...). States have a scope of, oh, usually just your whole program, nothing much...;). Let's see a sample so you'll have an idea of what states are and how they work. Let's take for example the way we want our polygons to render their colors. To specify a flat-shaded polygon (they have one color only per polygon), we use the GL_FLAT flag. To specify gouraud-shaded polygon (they can have many colors per polygon) on the other hand, we use the GL_SMOOTH flag. Here's how to set the state of how polygons render their color(s): //----8<----[ CODE BEGIN ]--------// glShadeModel(GL_SMOOTH); //----8<----[ CODE END ]--------// Until OpenGL sees a code that tells it to use flat-shading, it'll use gouraud-shading from there on. Now that's the concept of states in a nutshell...simple huh?...I think so too...:). Anyway, let's focus now on how to draw polygons on our window. Now, to draw something to the screen, you have to clear the screen first. Why? Because if we don't clear the screen my dear reader, we'll have a screen filled with the remnants of previous frames of course...;). Now, let's see the code to set what color we want to clear to: //----8<----[ CODE BEGIN ]--------// glClearColor(0.0, 0.0, 0.0, 0.0); glClearDepth(1.0); //----8<----[ CODE END ]--------// Tadah! We just told OpenGL that whenever we want it to clear the screen, clear it to a good solid black. We did that by stating the color in glClearColor()'s parameters. The first parameter accepts the Red intensity, the second the Green, the third the Blue, and the fourth the Alpha value. In OpenGL, the intensity has a range of 0.0 to 1.0, with 1.0 being the heaviest. If I were to use: //----8<----[ CODE BEGIN ]--------// glClearColor(0.0, 1.0, 0.0, 0.0); glClearDepth(1.0); //----8<----[ CODE END ]--------// I'd be telling OpenGL to clear the screen with a bright green! Haha, now wasn't that simple?...:). Oh, you're wond'rin' about that Alpha value huh? Don't worry, the Alpha value describes the translucency. Simply put, the more translucent, the more "see-through" the color is. You need not worry about translucency when defining the clearing color right now...we'll deal with translucency more when we tackle blending in another tutorial...:). Now, about that glClearDepth() thingie...it's there to tell OpenGL that we want to clear the whole depth buffer. The depth buffer is where the z-values of each pixel is stored. Specifying a 1.0 tells OpenGL to clear the whole depth buffer. (Read Part 11 again, I described there what the depth buffer is for, look for it in the part after I described what some fields of PIXELFORMAT- DESCRIPTOR is). Now, to ACTUALLY clear the screen and the depth buffer, we use the following function and flags: //----8<----[ CODE BEGIN ]--------// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //----8<----[ CODE END ]--------// The GL_COLOR_BUFFER_BIT flag tells OpenGL to clear the screen with the given color that was set by glClearColor(). The GL_DEPTH_BUFFER_BIT flag tells OpenGL to clear the depth buffer. It's as simple as that! Now that we know how to set the clear color and how to clear the screen and the depth buffer, let's know how to specify a color in OpenGL. If it is not obvious enough, we specify colors before drawing primitives (points, lines, polygons) to tell OpenGL what color to render our primitive...so learn this well...;). Actually, there's not much to learn, just one small function (plus one alternative that I'll show you guys later...:) ): //----8<----[ CODE BEGIN ]--------// glColor3f(1.0, 0.0, 0.0); //----8<----[ CODE END ]--------// And that's it! The parameters are like the first three parameters of glClearColor(), they are (in order, from left to right) the red, green, and blue intensities of the color. Again, color intensities may be defined from 0.0 to 1.0, with 1.0 being the heaviest. You could specify, for example, each parameter getting 0.5 as the value and get a nice gray color...:). Another alternative, if you're not comfortable working with numbers that have decimal points, is: //----8<----[ CODE BEGIN ]--------// glColor3ub(255, 0, 0); //----8<----[ CODE END ]--------// With this function, instead of specifying your color with floats, you do it with whole numbers. When using this function, color intensities may be defined from the range of 0 to 255. With (obviously) 255 being the heaviest. Those who are/were used to specifying colors in 15/16 and/or 24/32-bit modes under MS-DOS would have no problem using this...(Personally, I prefer using this...being that I dabble with Adobe PhotoShop a bit...;) ). Now that we know how to specify a color, let's set up the viewing stuffs that we'll have to deal with (you can think of this as setting up how you see your 3D-world...cool huh?...;) ). Now, this "setting up" business happens only when either the program was just run, or the user has resized the window. And so, we'll create a function named, well, hmm...let's name it UserResize(), ok?...:). Here's what UserResize() should have: //----8<----[ CODE BEGIN ]--------// void UserResize(int width,int height) { if (height == 0) height = 1; glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(45.0, (float)(width/height), 0.1, 100.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } //----8<----[ CODE END ]--------// Hmm...looks complicated...but not really...;). UserResize() accepts as parameters, the new width and height of the window. If you'd look and study the code, you'll notice that we check if the new height equals zero. That's because we don't want a divide-by-zero error when we call gluPerspective(). glViewPort() tells OpenGL the size of our screen. This function actually tells OpenGL how large each pixel should be inside our window (ie, if the user resizes our window, we, of course, update the size of our pixel, or else our scene will look weird!)...as simple as that...;). glMatrixMode(GL_PROJECTION) says that until OpenGL sees the line glMatrixMode(GL_MODELVIEW), all the OpenGL codes it sees will affect/be based on the projection matrix. Basically, the projection matrix is a set of numbers (4x4 really, if you know some matrix math) that gives us a perspective view. Meaning, things from far away looks smaller...:). gluPerspective() hails from the glu.h header file. You'll know from which header file each function is from by looking at the first few letters of the function (haha, ya didn't notice that did'ya?). Anyway... We told gluPerspective() that we want a 45.0 degree field of view angle. The second parameter (where we divided the width by the height) is to tell OpenGL how wide the screen is relative to its height (Oooohhhh....:) ). 0.1 and 100.0 is explained by the paragraph below...:). So how did I arrive with 0.1 and 100.0? Simple, you just have to imagine. Well, to me, I'd like my "eyes" to have a visibility range of up to 100 units away from me (you could treat "units" to be meters, feet, etc.). So, everything that's farther than 100 units away, I will not see. And everything closer (or smaller) than 0.1 units, I will not see also....;). And then we called glMatrixMode(GL_MODELVIEW). We ALWAYS set the matrix mode to GL_MODELVIEW whenever we're placing a polygon on the screen. So in short, GL_PROJECTION is used to specify/modify the perspective view, while GL_MODELVIEW is used when you want to specify the location of the polygons...very simple indeed! Oh yeah, glLoadIdentity() sets the matrix to its "default value". To those familiar with matrix math, this function replaces the current matrix with the (drum rolls...) Identity Matrix!!! If you don't know what an Identity Matrix is, just think of this function, for now, as resetting the default values of the currently chosen matrix. (Discussing this proves to be harder than I thought it would be...but I'll do my best...:) ). Now, after setting up the viewing stuffs, let's set up some more states: //----8<----[ CODE BEGIN ]--------// void InitOpenGL(int width,int height) { UserResize(width, height); glShadeModel(GL_SMOOTH); glClearColor(0.0, 0.0, 0.0, 0.0); glClearDepth(1.0); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); } //----8<----[ CODE END ]--------// See the function UserResize above? When we call InitOpenGL() in the beginning of the program, we also set up our perspective view! glShadeModel(GL_SMOOTH) you know already. It tells OpenGL to allow the polygons to have many colors. glClearColor() and glClearDepth() you also know already... glEnable(GL_DEPTH_TEST) you probably don't know yet...hehe...:). Here, we're telling OpenGL to enable depth-testing. This is for us to see the scene properly. Enabling depth-testing allows sorting of pixels based on their depth-values (the z-coordinate) that is in the depth buffer. Now, the type of sorting is defined by the function glDepthFunc(GL_LEQUAL). I think this is the default, not sure, haven't tried running an OpenGL program without it, but hey, no harm done putting that line of code there...;). What glDepthFunc(GL_LEQUAL) does is draw the "nearer" pixels on top of the "farther" pixels...:). After all the initialization of states, we're finally ready to specify vertices!!! What are vertices, you ask? It's plural for vertex, a vertex is a 3D point. A 3D point has an x, y, and z coordinate. (Note that you COULD just specify a "2D" point, leaving the z coordinate's value to 0. In fact, there's a function that does just that, but I won't tell you here. ...Oh alright...here's a hint: glVertex2f(x,y)...;) ) Anyway, here's how to specify a vertex: //----8<----[ CODE BEGIN ]--------// glVertex3f( 0.0f, 1.0f, 0.0f); //----8<----[ CODE END ]--------// As simple as that! To elaborate glVertex3f(), what I did above was told OpenGL that I have a vertex at coordinate (0.0, 1.0, 0.0). Note that coordinate (0.0, 0.0, 0.0) is at the center of the screen (by default), so what I did above was place a vertex one unit above the center of the screen...;). Also, remember ALWAYS that in OpenGL, negative values for the z-coordinate parameter will put the vertex further INTO the screen. The x and y coordinate you could treat like the way you treat the Cartesian Coordinate System (where positive x-values goes to the right, and positive y-values goes up). Now, one vertex won't be enough to create a polygon. A point, yes, but a polygon, no. To render a triangle, for example, you'd need at least three vertices (a bit obvious? yes? I thought so too...:) ). So here, I introduce to you guys how the vertices are treated. You'll know how to tell OpenGL to treat the vertices as a point, line, triangle, or quad. A quad is just a polygon with four sides...I can't say square because it could very well be a rectangle for all I care...;). OK, here's an example of drawing a triangle: //----8<----[ CODE BEGIN ]--------// glBegin(GL_TRIANGLES); glColor3ub(0,255,0); glVertex3f(0.0, 1.0, 0.0); glColor3ub(0,0,255); glVertex3f(-1.0, -1.0, 0.0); glColor3ub(255,0,0); glVertex3f(1.0, -1.0, 0.0); glEnd(); //----8<----[ CODE END ]--------// Tadah! That's how you'd specify how to draw a triangle in OpenGL. Give it three points, and (optionally) a color for each vertex, and you have a shaded triangle! Just remember to place your vertices between the glBegin() and glEnd()... The list below shows what you could draw and the number of required vertices/vertex to render your primitive correctly: //----8<----[ LIST BEGIN ]--------// GL_POINTS - Requires one vertex GL_LINES - Requires two vertices GL_TRIANGLES - Requires three vertices GL_QUADS - Requires four vertices //----8<----[ LIST END ]--------// There ARE other GL_BLAH-BLAHs you could use, but try and work with the four listed above first. This is slightly off topic: Remember we are using double buffers! To swap these buffers, we use: //----8<----[ CODE BEGIN ]--------// SwapBuffers(HDC); //----8<----[ CODE END ]--------// Where HDC is the Device Context of the window. If you neglect to add this in your rendering loop, chances are, you'd have to hit the reset-button of your PC...obviously, NOT a good thing to happen. And we're done! Look at the source code provided to know how everything fits together. [ ENDING ] And that's that! Not hard eh? If there is anything that's not clear to you, your mind, or to that part of you that lets you comprehend the things here, you may email me at (according to priority): willietang@hehe.com or willietang@yahoo.com Alright! The source code provided draws a smooth-shaded triangle. What was discussed here may seem a lot, but just look at the number of actual codes shown here, not much is it? What made this look long is my explanation and stuffs...;). Part 13 will be about scaling, rotation, and translation. Briefly, we could scale the whole scene into a smaller or larger size with just one function. We could also do rotations with one function, as well as translation. To learn how, look at Part 13. [ REFERENCE ] I want to recommend this site to you: URL : http://Romka.DemoNews.com This site offers a lot of downloadable demos showing off what OpenGL can do. It also has a lot of links to help you learn more about OpenGL. [ NOMAD.SOURCE ] Unfortunately, it was very difficult (and very awkward) to place codes here. So it was eventually decided that the source code be downloaded on my website at: http://nomad.openglforums.com Unzip the file and click on gfxOpenGL2.dsw to see the source codes (I AM, of course, assuming you're using Visual C++ 6.0 here...). <<------------------------------------------------------>> [ Written By: Willie Tang ] [ Written On: 05.05.2002 ] [ Updated On: 01.31.2003 ]