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 - 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 591, knowing OpenGL is a must.
Basic features:
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.Other features:
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 |
GLbyte |
signed char |
GLshort |
short |
GLint, GLsizei |
int or long |
GLfloat, GLclampf |
double |
GLubyte, GLboolean |
unsigned char |
GLushort |
unsigned short |
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 void glBegin(GLenum mode)
GL_POINTS
- treats each vertex as a single point GL_POLYGON
- vertices define a solid, convex polygon
glVertex()
- specify a vertexvoid glVertex2i(GLint
x, GLint y)
glFlush()
- tells OpenGL to draw primitives nowExample 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!
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.
There are several matrices in OpenGL. Amongst them are the projection matrix,
to control the projection transfromation (e.g. field of view, clipping planes,
and whehter the projection is perspective or othorgonal, etc.), and the modelview
matrix, to control the relative transformation between the drawn primitives
and the camera. We want to use the modelview matrix, so we need to explicitly
tell that to OpenGL with a call to glMatrixMode()
:
glMatrixMode(GL_MODELVIEW);
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:
glPushMatrix(); glTranslate*(startX, startY, 0.0); // Note that the translation is // done outside glBegin and GlEnd glBegin(...); glVertex2i(...); glEnd(...); glPopMatrix();
(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.)
One thing that might help is to be checking for errors after each call. When it seems like nothing is happening, OpenGL is often returning an error message somewhere along the line. The begin-end block is a good possibility, and if that's the problem there will be an error code returned.
GLenum errCode;
errCode = glGetError();
if (errCode != GL_NO_ERROR) {
printf("Error: %s(%i) in %s.\n", gluErrorString(errCode),
errCode, "method()");
}