Project 1 help session : OpenGL
This page is designed to assist you in completing project 1, Impressionist. The following is a quick tutorial for using a subset of OpenGL -- enough to get you through project 1.
- OpenGL tutorial
- Graphics Primitives
- Image routines
- OpenGL Transformations tutorial
- Pushing/Popping Matrices
OpenGL - 2D/3D Graphics Library developed at Silicon Graphics Inc. OpenGL has gained widespread industry acceptance in the high-end 3D markets, as well as in the low-end consumer markets. OpenGL's main thrust is as a 3D programming interface. Although it is targeted to the 3D market, it is more than adequate for use in 2D. OpenGL provides fine grained control over images as well as 3D objects. If you plan on continuing on in the computer graphics field after 457, knowing OpenGL is a must.
- C language interface
- Is a state machine
- When you execute a command, it remains in effect until you specifically change it. For instance, if you set the drawing color to red by calling glColor3f(1.0, 0, 0), everything you draw from then on will be red. A second call to glColor* is required to change the drawing color.
- Graphic primitives:
- points, line segments, or polygons
- defined by a group of one or more vertices
- subject to several drawing modes which affect color, pattern,...
- Manipulation of frame buffer contents:
- useful for displaying images (arrays of data)
- also useful for handling expose events
- Clipping, Shading, Texture mapping, etc.
OpenGL has its own definitions for variable types, and you should use these whenever you're writing in OpenGL. They are simply re-definitions of the basic types; GLint is simply an int. Why do you want to use them then? OpenGL programmers reserve the right to change the way OpenGL handles data internally. If you use their variables, then they ensure that the changes will be invisible to your program. If you don't, then the float that you send to glColor3f might not work in a future release. The following table shows the OpenGL variables and their corresponding data types.
|OpenGL Type Definition||Data Type|
|GLint, GLsizei||int or long|
|GLubyte, GLboolean||unsigned char|
|GLuint, GLenum, GLbitfield||unsigned int or unsigned long|
All the functions in the OpenGL library have names beginning with "gl". Defined constants have names beginning with "GL_".
Since C doesn't allow function overloading, it is common for there to be a family of functions for performing the same operation, differing only in the number and/or types of arguments accepted by each. The names of these functions have tags at the end to indicate the type of argument. The whole family will be referred to with a star syntax. For instance, glColor*() refers to any of the 32 functions available within OpenGL for setting the current color, including:
|glColor3f( GLfloat, GLfloat, GLfloat ) -Takes 3 floats.|
|glColor4d( GLdouble, GLdouble, GLdouble, GLdouble ) - Takes 4 doubles (the fourth is the alpha value)|
|glColor3ubv( GLubyte* ) - Takes a vector (or array) containing 3 unsigned bytes.|
OpenGL comes with a library of convenience functions called the glu library. All of the function names begin with glu.
We will use OpenGL in the Impressionist program to draw the various brush strokes. We will mainly stick to drawing lines and points. If you choose to do so, you can make fancier brushes using some of the other primitives (such as triangles, squares, and general polygons) for extra bells and whistles. Here's a brief introduction (you'll find more in the man pages and reference manuals)
- glColor - specify the color in which to draw primitive(s)
- glBegin, glEnd - delimit the vertices of a primitive or a group of like primitives
- GL_POINTS - treats each vertex as a single point
- GL_POLYGON - vertices define a solid, convex polygon
- see OpenGL manual for other modes
- glVertex - specify a vertex
- void glVertex2i( GLint x, GLint y )
- glFlush() - tells OpenGL to draw primitives now.
void glBegin( GLenum mode )
Example Code -- primitives: The following bits of code show you how draw single-pixel dots, a triangle outline (see OpenGL manual for other ways to do this), and a filled triangle (as a three-sided polygon).
glColor3f( red, green, blue ); // drawing a single-pixel dot to the // window, at pixel coordinate (x,y) glBegin( GL_POINTS ); glVertex2i( x, y ); glEnd(); // drawing an outlined triangle (many ways to // do this) having vertices A, B, and C glBegin( GL_LINE_STRIP ); glVertex2i( Ax, Ay ); glVertex2i( Bx, By ); glVertex2i( Cx, Cy ); glVertex2i( Ax, Ay ); glEnd(); // drawing a filled triangle having // vertices A, B, and C glBegin( GL_POLYGON ); glVertex2i( Ax, Ay ); glVertex2i( Bx, By ); glVertex2i( Cx, Cy ); glEnd(); glFlush(); // don't forget this!
Basic OpenGL Transformations
In the Impressionist project, you will have to control various aspects of the brush strokes, such as their position and orientation. Since you will be using OpenGL to complete this project, you will find it very beneficial to know a little bit about how "transformations" (shifting, rotation, scaling, etc,) are done in OpenGL.
Recall that OpenGL is basically a state machine. For many aspects of it, you setup certain parameters, and until you change them, GL will use those parameters for everything it draws.
You have probably already seen how this is used for things like object color (via glColor) and drawing mode (via glBegin / glEnd). However, there are also state variables for things like position (accomplished via "translation", or shifting) and direction (accomplished via rotation).
For example, you can call glRotate* to set the rotation state. If you tell OpenGL to rotate 45 degrees around the z axis (with glRotate3f( 45, 0.0, 0.0, 1.0) ), then everything you draw will be rotated 45 degrees before it's drawn to the screen. This is a quick and easy way of changing the location and orientation of an object.
So how does this apply to Impressionist? Recall that you'll be drawing various brush strokes on a digital canvas, each at a different position and in a different direction. By setting the GL state variables for position and orientation before drawing your brush, you can use the same code regardless of the brush's position or orientation. The stroke will automatically be drawn at the correct position and in the correct direction!
Here are the OpenGL calls needed to do some simple image transformations.
Choosing the right matrix
There are several matrices in OpenGL. Amongst them are the projection matrix, to control the camera position, and the modelview matrix, to control drawing. We want to use the modelview matrix, so we need to explicitly tell that to OpenGL with a call to glMatrixMode:
Image transformations are accomplished using matrices. A series of matrices are multiplied to produce a given image transformation. Without going into too much detail, let's just say that you'll want to save your current transformation matrix ("push" it onto a matrix stack) before doing your brush-specific translation/rotation, and restore the original matrix ("pop" it off the matrix stack) when you're done with that brush stroke.
Here are the calls you need to use.
glPushMatrix(); <&<Do the translation, rotation>> <&<Draw the brush stroke>> glPopMatrix();
To draw at a certain point, you'll want to "translate" your origin to that position. Here is the call you use in OpenGL to do 2D translation:
glTranslate*( startX, startY, 0.0 );
(Note: the * is replaced by a letter that depends on the type of the parameters you pass.)
You can also take advantage of OpenGL to automatically rotate the things you draw. Here is the OpenGL call you'll want to use:
glRotate*( angle, 0.0, 0.0, 1.0 );
(Note: we use 0.0, 0.0, 1.0 because we want to rotate around the z-axis.)