Professional Documents
Culture Documents
OpenGL is public, standard C language 3D graphics API based on an earlier, proprietary standard developed by SGI. OpenGL is a state machine of sorts, that takes in requests for graphics primitives, processes them through a rendering pipeline, and produces 2D pixel images as output. OpenGL actually consists of two libraries (GL and GLU), and to use it you must either write window system code yourself (X11 or Win32) or use a third party library such as "glut" to handle window creation and user input. Here is a sample Makefile for compiling an OpenGL program (simple.c) on our local linux environment:
LDLIBS = -L/local/Mesa4.01/lib -lglut -lGLU -lGL -L/usr/X11R6/lib -lXmu -lX11 -lm INCL = -I/local/Mesa4.01/include -I/usr/X11R6/include simple: simple.c cc -o simple $(INCL) simple.c $(LDLIBS)
In addition to this makefile, it turns out that you will need a LD_LIBRARY_PATH environment variable in order for the system to find your glut shared library. This is set in your .cshrc file with something like:
setenv LD_LIBRARY_PATH /local/Mesa4.01/lib
Note that you may already have an LD_LIBRARY_PATH, and you should just add the Mesa directory to your existing path if you have one. Compared with the earlier Icon 2D interface we have seen, OpenGL has many more features, and more complexity, for 3D programming. The glut library which interfaces OpenGL to the host window system is simple and easy to use, but limited and restrictive in its capabilities.
Callbacks
Most graphics API's are "event-driven", meaning that an application is written as a set of functions which are called by the window system in response to user input or when other services are required. glut follows this model in a strong sense; callback functions are "registered" with glut, after which glut's main loop owns the control flow of your program. You do not draw your graphics output in your main() procedure, you draw it in a "display callback" function that is invoked whenever the window needs to be redrawn.
The glBegin(type_of_object) function specifies what primitive is being depicted, and the glVertex family of functions allow any number of (x,y,z) coordinates in a variety of types and dimensions. lecture #13 (virtual lecture) began here
This lecture is presenting selected material from the OpenGL Primer, chapter 2.
Color in OpenGL
OpenGL supports "RGB" (true color) and "color index" (color map) modes; writing an application that will run on either is a bit awkward, and color index display hardware is vanishing, so it is reasonable to consider only the RGB color mode. Colors are specified in real numbers between 0.0 and 1.0. Some displays (notably, current generation Macs) support degrees of transparency for superimposing multiple images on screen, and use a fourth color component (alpha) where 1.0 means the color is solid and 0.0 means the color is invisible. Consider now the task of setting the foreground color with which objects are drawn. Although colors are real numbers between 0 and 1, you can use almost any numeric type to supply RGB values; integer values are converted into fractions of the maximum value, so for example many programmers who are used to working with 8 bits each of R, G, and B, can call a function that takes values like (255, 127, 0) and internally this is converted to the equivalent (1.0, 0.5, 0). So, like the glVertex family, there are many (28) functions in the families that set the foreground color, glColor*. An example call would be: glColor3f(1.0, 0.5, 0.0). Apparently they didn't bother to make 28 functions for setting the background color with glClearColor(), because that operation is far less common.
This discussion of color is well and good, but tragically it all becomes meaningless as you transition from "toy" programs to more realistic ones, because once you introduce lighting into your application, glColor*() has no effect! When lighting is introduced, the color of objects becomes a function of the color of light and the reflective properties of the objects, specified by "materials". We will see lighting in detail a little later.
Later on, we will see that you frequently compose these matrices, especially the model view matrix, while drawing a complex scene. From one transformation cooresponding to a given "parent" coordinate system, if you have several "child" coordinate systems whose coordinates are relative to the parent, each child will need to save the parent transformation, make changes and then draw their objects, and then restore the parent. When object hierarchies are multiple levels deep, a special type of stack is natural. OpenGL provides a built-in stack mechanism, in which the "push" operation copies the top stack element, to which the child may then apply transformations for their coordinate system relative to the parent coordinates. glPushMatrix() and glPopMatrix() are used in this way, and they operate on whichever current matrix you have specified using glMatrixMode().
GL_POINTS are not usually very interesting in 3D graphics, but opengl supports them and even has a "point size" attribute (set by function glPointSize()) that says how large (in pixels) plotted points should appear. Are points round, or square? Are they really spheres in a 3D scene? The fact that their "size" is given in pixels is at odds with the world coordinates used to specify position, so you can be suspicious that points are a "special case" hack in OpenGL. Lines There are three opengl primitives for depicting lines, similar to the two primitives used in Icon's 2D graphics. GL_LINES draws disconnected segments between pairs of points, similar to Icon's DrawSegment(), while GL_LINE_STRIP draws lines between every adjacent pair of points, forming a single connected object. GL_LINE_LOOP also the first and last points specified, for people too lazy to repeat the first point at the end. Lines have a color, a thickness (glLineWidth()), and a so-called stipple pattern that let's you specify dashes or dots in the lines you draw. But, you can only use these line styles if you call glEnable(GL_LINE_STIPPLE). Triangles, Triangle Strips, Triangle Fans Triangles get a lot of attention in 3D graphics because they are the smallest number of points necessary to specify a portion of a solid object's surface. 3D graphics hardware supports the rendering of triangles directly, while more complex surfaces are usually composed of many many (possibly millions) triangles. In addition to GL_TRIANGLES, are are cleverly optimized ways to specify sequences of physically connected triangles without having to specify the vertices they have in common multiple times. The net effect is only having to specify one new vertex per triangle. GL_TRIANGLE_STRIP connects each vertex with the previous two, while GL_TRIANGLE_FAN connects each vertex with the previous one, and the first vertex. Quads and Quad Strips Four vertex objects are also common, and may be rendered in hardware or broken up into triangles for rendering. GL_QUAD_STRIP defines each new quad using the last two vertices and two new vertices, costing new vertices per quad. As an afterthought, OpenGL also supports conventional 2D rectangles with the glRect* family. Polygons GL_POLYGON is the most general of these primitives, and is typically broken down into triangles during rendering. It is analogous to FillPolygon() in Icon's graphics. It is worth mentioning a few extra items relating to polygons. If their vertices cross, OpenGL's semantics are not guaranteed, implementations can handle them however they please. And convex polygons are MUCH easier and faster to render. If you use multiple colors in the middle of a glBegin/glEnd pair, opengl will use interpolation to gradually change the colors. You can specify glShadeModel(GL_FLAT) if you want each primitive to be a single color instead of an interpolation of the varying colors of its vertices.
Viewports
Within your window, you can select subregions and use them for different purposes; for example, to render simultaneous views looking forward and backward to provide "eyeballs in the back of your head".
glViewport(x, y, w, h) maps the current projection onto a specific region within the selected window. lecture #14 began here.
Display Lists
A display list is a collection of off-screen invisible OpenGL objects, stored on the server in a format that can be rapidly (re)displayed. They are created and accessed by integer handles. Display lists may be called from within other display lists.
glNewList(myhandle, GL_COMPILE); glPushAttrib(CL_CURRENT_BIT); glColor3f(1.0,0.0,0.0); glRectf(-1.0,0.0,0.0); glPopAttrib(); glEndList(); ... glCallList(myhandle);
Selection Mode
To select an object in a 3D scene, you rerender the scene in "selection mode", not writing to the frame buffer but labeling each object being rendered with an integer code, with the clipping set closely around the mouse click so that only objects on/near the mouse are rendered. Most of this work is done in the mouse callback function.
void mouse(int button, int state, int x, int y) { ... glInitNames(); glPushName(0);
glSelectBuffer(SIZE, nameBuffer); glGetIntegerv(GL_VIEWPORT, viewport); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluPickMatrix((GLdouble)x,(GLdouble)(viewport[3]-y),N,N,viewport); gluOrtho2D(xmin,xmax,ymin,ymax); draw_objects(GL_SELECT); glMatrixMode(GL_PROJECTION); glPopMatrix(); glFlush(); hits = glRenderMode(GL_RENDER); processHits(hits, nameBuffer); glutPostRedisplay(); }
The display callback function becomes trivial, because we have moved code to a helper function called to display or to check for hits:
glClear(GL_COLOR_BUFFER_BIT); draw_objects(GL_RENDER); glFlush();
The draw_objects function does the graphics functions, but "labels" each selectable object with an integer code:
void draw_objects(GLenum mode) { if (mode == GL_SELECT) glLoadName(1); glColor3f(1.0,0.0,0.0); glRectf(-0.5,-0.5,1.0,1.0); if (mode == GL_SELECT) glLoadName(2); glColor3f(0.0,0.0,1.0); glRectf(-1.0,-1.0,0.5,0.5); }
The processHits function considers all objects within N pixels (N was specified in gluPickMatrix) of the user click.
void processHits(GLint hits, GLuint buffer[]) { unsigned int i, j; GLuint names, *ptr; printf("hits = %d\n", hits); ptr = (GLuint *) buffer; for (i = 0; i < hits; i++) { names = *ptr; ptr += 3; /* skip over number of names and depths */ for (j = 0; j < names; j++) { if (*ptr == 1) printf("red rectangle\n"); else printf("blue rectangle\n"); ptr ++; } } }
Cameras
OpenGL's synthetic camera model emulates real world imaging. 3D images within a particular view volume are projected onto a 2D surface, as viewed from a particular center of projection. 6 degrees of freedom specify the camera position and where it is looking. Three angles specify what is up. Six additional values specify the viewing volume. orthographic projection camera is at infinity, projection plane is near the objects glOrtho(left,right,bottom,top,near,far) 3D orthographic projection defines a viewable box gluLookAt(ex,ey,ez,ax,ay,az,ux,uy,uz) Specify eye position and orientation. gluPerspective(yangle, aspectratio, near, far) 3D perspective projection, defines a viewable box Notice that glOrtho, and gluPerspective, use "camera coordinates" not world coordinates. "near" and "far" are distances from the camera. lecture #16 began here
Building objects
Graphics objects may be composed using either code or data.
void cube() { glColor3f(1.0,0.0,0.0); glBegin(GL_POLYGON); glVertex3f(-1.0,-1.0,-1.0); glVertex3f(-1.0,1.0,-1.0); glVertex3f(-1.0,1.0,1.0); glVertex3f(-1.0,-1.0,1.0); glEnd(); /* other 6 faces similar) */ }
Generally it will be preferable to store the graphics in a data structure (array, list, tree) and write code that walks the structure.
OpenGL Transformations
The early sections of OpenGL Primer Chapter 5 review 3D transformations that we've seen earlier. Transformations are built-in to OpenGL, greatly simplifying the programmer's job. Here are some highlights.
Points and directions (vectors) may be represented similarly, but they aren't the same. translating to a positive z value may move an object behind the camera in its default location. typical translation example:
glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0, 0.0, -1.0); /* move object away/in front of camera*/
multiple objects, 2nd object relative to first. Note transformations are concatenated/composed together by default.
glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glTranslatef(0.0, 0.0, -1.0); /* move object 1 */ glutWireTetrahedron(); glTranslatef(0.0, 0.0, -2.0); /* move object 2, -2 further than object 1 */ glutWireCube();
positive rotation == counterclockwise. Rotation's fixed point is the origin. the last transformation specified in the program is the first one applied. typical rotation:
glMatrixMode(GL_MODELVIEW); glLoadIdentity();
glTranslatef(x, y, z); /* move object back from origin */ glRotatef(angle, dx, dy, dz); /* rotate about axis specified by vector */ glTranslatef(-x, -y, -z); /* move object to origin */
What about more complex multi-piece objects such as the running man in Figure 5.10? With the right combination of pushes and pops, code similar to the above example would work... but its much cooler to do it as a tree traversal:
typedef struct treenode { GLfloat m[16]; void (*f)(); struct treenode *sibling, *child; } treenode; void traverse(treenode *root) { if (root == NULL) return; glPushMatrix(); glMultMatrix(root->m); root->f(); traverse(root->child); glPopMatrix(); traverse(root->sibling); } void display() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); traverse(torso_root); glutSwapBuffers(); } where treenode *torso_root = malloc(sizeof(treenode)); torso_root->f = torso; glLoadIdentity(); glRotatef(theta[0], 0.0, 1.0, 0.0); glGetFloatv(GL_MODELVIEW_MATRIX, torso_root->m); torso_root->sibling = NULL; torso_root->child = head_node; ... etc.
lecture #17 began here