The full set of slides
-
Upload
duongtuyen -
Category
Documents
-
view
234 -
download
0
Transcript of The full set of slides
Computer Science 4751
Introduction to ComputerGraphics
Fall, 2014
1
About this course
The major goal of this course is to understand the construction
of a 3-dimensional graphics system, including the underlying
basic algorithms and data structures. An additional goal is to
gain some experience with modern high-performance graphics
software.
Particular issues we will be concerned with are:
• Modeling — capturing and describing essential graphical
details
• Rendering — displaying the graphics on some output
• Realism — improving the quality of the image
This is not a course in the use of a particular graphics package
(although we will use OpenGL in the examples and assignments,
and you will be expected to achieve some proficiency in its use.)
Rather, we will attempt to understand how a graphics system
works, and the underlying algorithms, principles, and ideas.
2
What this course is not designed to do:
• To teach specific graphics packages — e.g. “business graph-
ics” or Autocad.
• To learn about graphical user interfaces (GUI’s) and their
toolkits (TK, Motif, . . . )
• To show you how to draw graphs of scientific or other data.
• To teach you to a programming language (or a new pro-
gramming paradigm). You should already be a competent
programmer.
• To use any particular programming environment, IDE, or
SDK.
• To make you a “graphics artist”.
Following this course, you may be have some insight into some of
those things, but we are more interested in how the underlying
graphics systems are designed.
3
Why OpenGL?
• It is available for virtually every OS
• It has bindings for many languages including C, Java, Python
• Many high-level graphics systems are based on it
• It is supported by most high performance hardware
• A smaller, simpler, (but still powerful) subset is optimized
for lower resource systems (OpenGL ES)
• It is supported for virtually all tablet systems and “smart
phones”
• It is free and open
In addition, knowledge of OpenGL is a valuable skill.
The aim of this course is not to learn OpenGL per se, but it is
a valuable side effect of the course.
Most of the examples will be implemented using the C language,
but it is straightforward to apply the same ideas to other lan-
guages, particularly Java and C++.
4
Models for graphics systems
There are many models for graphics “applications programmer
interfaces” (API’s). One model is the “press camera” model.
Note that the press camera was more flexible (literally) than
modern cameras — the lens could be adjusted horizontally and
vertically, and the backplane could be tilted.
5
In this model, the viewer, or “eye” is the lens of a camera,
the image is the camera backplane. The “scene” is whatever
appears in the field of view of the camera.
Viewport
"eye"
"Window"
The part of the scene in which the image to be displayed is usu-
ally called the “window” and is modeled in “world coordinates”
— usually in 3 dimensions.
The image is displayed in the two-dimensional “viewport” and
uses “device coordinates.”
6
Because the camera shows an inverted image, we often pretend
that it is a kind of mirror, projecting an erect image in front of
the “eye.”
Viewport
"Window"
As in the case of the press camera, the viewport may be tilted
with respect to the viewing axis.
Many 3-dimensional graphics systems use a similar kind of
model for displaying (or rendering) a scene.
An interesting feature of software graphics systems is that the
scene itself is also described using functions from that system.
7
Graphics systems like OpenGL provide functions for modeling a
scene, using some graphics primitives. These primitives often
have attributes which can also be altered independently.
The graphics primitives are usually simple shapes, and the at-
tributes are things like color or reflectivity.
A scene is described in world coordinates, and appropriate
models of lighting are associated with the scene.
Generally, then, a viewpoint, or eye position, is determined,
and the scene is rendered from that viewpoint by the graphics
system.
The image is then displayed on the viewport.
The order of events is:
Model ViewRender
program graphicslibrary
display
software hardware
8
Looking at an interactive graphics system as a whole:
Application
Program
Graphics
System
Application
Model
Display
InputAPI
OpenGL is one possible API which could be used to develop an
interactive graphics system.
Note that the interaction between the application program is bi-
directional. (The user can interact with the application program
through some graphical interface.)
The API (OpenGL) is used through a programming language.
OpenGL has language bindings for many languages, including
C, C++, Java, and Fortran. The C language bindings are used
in these notes.
OpenGL itself deals directly with the display. The input is
handled by another environment; in our case, the GLUT utility
functions.
These will be discussed later.
9
OpenGL
OpenGL is concerned with the drawing of an image (rendering)
into a framebuffer. It draws primitives, subject to a set of
modes.
Primitives are points, line segments, polygons, or pixel rectan-
gles.
Primitives are specified, modes are set, and other OpenGL op-
erations are accomplished by sending commands in the form of
procedure calls or functions.
There are OpenGL bindings to a number of languages; we will
look at the C language bindings.
Primitives are defined by a set of one or more vertices. Vertices
define points, which may be endpoints of edges, corners of a
polygon, or simply points, in two or more dimensions.
Attributes other than position (e.g., color, texture) may also be
be assigned to a vertex.
The model for interpreting OpenGL is the client-server model
(more about this later).
10
OpenGL command syntax – C bindings
OpenGL commands are functions or procedures, and follow a
simple naming convention in the C language. The following
shows the glVertex command:
void glVertexnt (args)
where
n is the number of dimensions (2, 3, or 4) and
t is the data type (integer, float, double, etc.).
For example, we could have
glVertex2f(x1, y1); /* 2 dimension, type float */
glVertex2i(i1, j1); /* 2 dimension, type int */
Another form of the glVertex command can use a pointer to
an array of vertices, as follows:
GLfloat vertex[3] /* an array of 3 elements */
glVertex3fv(vertex); /* 3 D, type pointer to float */
11
Sets of vertices are used to specify points, lines, polygons, and
surfaces using a glBegin - glEnd construct; e.g., for a line:
glBegin(GL_LINES);
glVertex2f(x1, y1);
glVertex2f(x2, y2);
glEnd();
We can define a set of points similarly:
glBegin(GL_POINTS);
glVertex2f(x1, y1);
glVertex2f(x2, y2);
glVertex2f(x3, y3);
glEnd();
In general, objects are defined with primitives of the form:
glBegin(object_type);
glVertex..(...);
.
.
.
glVertex..(...);
glEnd();
12
Other object types are polylines (lines made up of several,
connected, line segments):
GL_LINE_STRIP
The closed version is
GL_LINE_LOOP
For solid surfaces, the object types are polygons, triangles,
quadrilaterals, and special types called strips and fans.
GL_POLYGON
GL_TRIANGLES
GL_QUADS
GL_TRIANGLE_STRIP
GL_QUAD_STRIP
The tutorial shapes shows the use of the primitive drawing
functions.
Note that OpenGL only guarantees that convex polygons will
be rendered correctly.
13
V1 V3 V5 V7
GL_TRIANGLE_STRIP
V0 V2 V4 V6
V1 V3 V5 V7
GL_QUAD_STRIP
V0 V2 V4 V6
GL_TRIANGLES GL_QUADS
V1 V2
V3V0
GL_TRIANGLE_FAN
V0
V7
V6
V1 V2
V3
V5
V0
V7
V6
V1 V2
V3
V4
V5
V4
V4
V5
14
Other Attributes – color
Although color can be represented in a number of different ways,
we will initially use the RGB (red - green - blue) color model. In
fact, we will extend this to the RGBA model, where A (alpha)
is a measure of the transparency.
The quadruple
(1.0, 1.0, 1.0, 0.0)
would represent solid, opaque white — opaque because the
transparency is 0.0, and white because all of red, green, and
blue are saturated.
(1.0, 0.0, 0.0, 0.0)
would represent solid, opaque red — only the red component is
present.
what would the following represent?
(0.0, 0.0, 0.0, 0.0)
(0.0, 1.0, 0.0, 0.0)
(0.0, 0.0, 0.5, 0.0)
The tutorial shapes shows the use of both the primitive draw-
ing functions and color.
15
Projections
Often we are interested in a 2-dimensional display of things from
a 3-dimensional world, so we must project the 3-dimensional
objects on the plane.
One of the simplest projections is the orthographic projection.
Here, in effect, the viewer looks directly along one of the axes
of the coordinate system (usually the z-axis).
The field of view is also generally defined as a window, and it,
or part of it, is projected onto a 2-dimensional viewport.
Primitives intersecting the viewport are clipped, and primitives
lying entirely outside the viewport are not displayed at all.
OpenGL has primitives (functions) which have both a two-
dimensional and a three-dimensional form. The three-dimensional
form is most commonly used, and most of the examples we will
see use three dimensional primitives. The projection of these
primitives onto the viewing plane is something we will discuss
in detail later. For the present, the function
glOrtho(left, right, bottom, top, near, far)
defines a viewing volume bounded by (left,right) in the x-
dimension, (bottom,top) in the y-dimension, and (near,far)
in the z-dimension.
16
The display environment
Although OpenGL is often used for event driven, interactive
applications, OpenGL itself does not have functions for user in-
teraction or for interacting with the local environment (creating
a display window, for example).
These functions are performed by another library (in our case,
the GLUT library — actually, the freeglut library) which sets
up the display environment — window size, position, etc. and,
in fact, calls the OpenGL functions we will write.
We will discuss the GLUT library in more detail later. Initially,
we will use “boiler plate” functions into which we will embed
the code we wish to display.
The following simple OpenGL program draws a black rectangle
on a white background.
17
A simple program — hello.c
/* hello.c
*
* Copyright (c) 1993-1997, Silicon Graphics, Inc.
* ALL RIGHTS RESERVED
*
* This is a simple, introductory OpenGL program.
* It draws a black square on a white background.
*/
#include <GL/glut.h>
void init (void)
/* this function sets the initial state */
{
/* select clearing (background) color to white */
glClearColor (1.0, 1.0, 1.0, 0.0);
/* initialize viewing values */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}
18
void display(void)
{
/* clear all pixels */
glClear (GL_COLOR_BUFFER_BIT);
/* draw black polygon (rectangle) with corners at
* (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0)
*/
glColor3f (0.0, 0.0, 0.0);
glBegin(GL_POLYGON);
glVertex3f (0.25, 0.25, 0.0);
glVertex3f (0.75, 0.25, 0.0);
glVertex3f (0.75, 0.75, 0.0);
glVertex3f (0.25, 0.75, 0.0);
glEnd();
/* don’t wait!
* start processing buffered OpenGL routines
*/
glFlush ();
}
19
int main(int argc, char** argv)
/*
* Declare initial window size, position, and
* display mode (single buffer and RGBA).
* Open window with "hello" in its title bar.
* Call initialization routines.
* Register callback function to display graphics.
* Enter main loop and process events.
*/
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (250, 250);
glutInitWindowPosition (100, 100);
glutCreateWindow ("hello");
init ();
glutDisplayFunc(display);
glutMainLoop();
return 0; // ANSI C requires main to return int.
}
20
Note that this does not look much like a conventional program
— there is really no “flow of control” to sequence events. This
is characteristic of event driven code — functions are written
which “handle” the various events.
In this simple example, there are no external events to handle;
there is no user interaction required, and any other types of
interaction (e.g. resizing a window) assume the system default
case.
Two functions were defined:
init() is simply a convenient place to put the initialization
information (the background color and projection)
display() is the function defining what is to be displayed in
the graphics window.
The functions glutInitWindowPosition(), glutInitWindowSize(),
and glutCreateWindow() set up a display window in a par-
ticular place, with a particular size and identifier on the screen.
glutDisplayFunc() calls the function which contains the OpenGL
drawing functions (display() in the present case).
glutMainLoop() enters the GLUT event processing loop (and
never returns). This causes the OpenGL commands to be exe-
cuted, as well as handling external events.
21
A more complex example — recursive refinement
Of course, we can use more complex programming structures
in our code. For example, the following code draws a simple
outline of a square, using 4 lines. The square is centered at the
origin, and has a “radius” of 1. (It is a crude approximation to
a circle.)
The program then recursively finds the center of each of the
lines, and normalizes the distance from the origin to 1, making
successively better approximations to a circle.
The program also parses a simple command line argument (an
integer) which it uses to set the level of recursion.
(The C language has I/O functions which are quite different
from JAVA or C++.)
1
22
/* recursive_circle.c
* This is a simple, introductory OpenGL program.
* It draws an approximation of a circle on a white
* background by recursively subdividing the sides of a
* square and renormalizing the distance from the origin.
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <GL/glut.h>
int n_levels=0; // the number of levels of recursion
void init (void)
{
/* select clearing color as white */
glClearColor (1.0, 1.0, 1.0, 0.0);
/* initialize viewing values */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
}
23
// the recursive function
void drawside(float x0,float y0,float x1,float y1,int n)
{
float xm, ym, norm;
if (n <= 0) { // the stopping condition
glBegin(GL_LINES); // drawing only done here
glVertex3f(x0,y0,0.0);
glVertex3f(x1,y1,0.0);
glEnd();
}
else { // the recursion
n--; // decrement counter
xm = 0.5*(x0 + x1); // calculate midpoints
ym = 0.5*(y0 + y1);
norm = sqrt(xm*xm +ym*ym);
xm = xm/norm; // normalize line
ym = ym/norm;
drawside(x0,y0,xm,ym,n);
drawside(xm,ym,x1,y1,n);
}
}
24
void display(void) // the new display function
{
float x0, y0;
/* clear all pixels */
glClear (GL_COLOR_BUFFER_BIT);
/*
* set initial sides of square with unit "radius"
* centered at (0.0, 0.0, 0.0).
*/
glColor3f (1.0, 0.0, 0.0);
x0 = 1.0/sqrt(2.0);
y0 = x0;
drawside(x0,y0,-x0,y0,n_levels);
drawside(-x0,y0,-x0,-y0,n_levels);
drawside(-x0,-y0,x0,-y0,n_levels);
drawside(x0,-y0,x0,y0,n_levels);
glFlush ();
}
25
int main(int argc, char** argv)
/*
* Declare initial window size, position, and
* display mode (single buffer and RGBA).
* Open window with "circle" in its title bar.
* Call initialization routines.
* Register callback function to display graphics.
* Enter main loop and process events.
*/
{
if (argc == 2) sscanf(argv[1],"%i",&n_levels);
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (400, 400);
glutInitWindowPosition (100, 100);
glutCreateWindow ("circle");
init ();
glutDisplayFunc(display);
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}
26
The function sscanf( ...) is one of the C input functions.
There are several, which do formatted input.
The simplest is the function scanf which takes input from the
standard input device (the keyboard). The function fscanf
takes input from a file which was previously opened.
The man pages give a brief description of these and other I/O
functions.
sscanf takes input from a character string and reads it accord-
ing to the specified format.
In this example, argv[1] is the character string corresponding
to the first command line argument for the program.
The constant character string "%i" is the format string, telling
the function that the string is an integer.
The third argument, &n levels, is a pointer to the integer
variable n levels which is the value of the integer which was
read.
The corresponding output functions are the printf family of
functions.
27
Simple animation
Animation is achieved by redrawing the screen repeatedly.
The GLUT interface to OpenGL has features to assist in ani-
mation. One is to double buffer the display memory. Another
is the provision of a function which causes the display to be
redrawn — usually after some change.
The following additions to the display and main functions
show a very simple animation of the previous example, in which
the color of the square is changed randomly every 0.2 second.
The random color change is accomplished with the following
change to the function display():
glColor3i (rand(), rand(), rand());
The double buffering and the re-running of the display()
function are effected by the following:
glutSwapBuffers();
glutPostRedisplay();
The C function
usleep(200000);
suspends execution of the process for 200000 microseconds.
28
void display(void)
{
/* clear all pixels */
glClear (GL_COLOR_BUFFER_BIT);
/* draw random color rectangle with corners at
* (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0)
*/
glColor3i (rand(), rand(), rand());
glBegin(GL_POLYGON);
glVertex3f (0.25, 0.25, 0.0);
glVertex3f (0.75, 0.25, 0.0);
glVertex3f (0.75, 0.75, 0.0);
glVertex3f (0.25, 0.75, 0.0);
glEnd();
/* start processing buffered OpenGL routines */
glFlush ();
glutSwapBuffers();
usleep(200000);
glutPostRedisplay();
}
29
int main(int argc, char** argv)
/*
* Declare initial window size, position, and
* display mode (double buffer and RGBA).
* Open window with "new_hello" in its title bar.
* Call initialization routines.
* Register callback function to display graphics.
* Enter main loop and process events.
*/
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT DOUBLE | GLUT RGB);
glutInitWindowSize (250, 250);
glutInitWindowPosition (100, 100);
glutCreateWindow ("new_hello");
init ();
glutDisplayFunc(display);
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}
30
User interaction
Recalling that OpenGL itself does not have functions to allow
user interaction, then how can we provide the interactive com-
ponent to our graphics programs?
A number of API’s identify six types or classes of logical input
devices (or modes of interaction). They are as follows:
String The input is a character string. The input device could
be a keyboard.
Choice Allows selection from one of a fixed number of options,
say, from a menu or a set of buttons.
Pick An an identifier for an object is returned. The object is
usually pointed to by a mouse controlled pointer, or other
pointing device. (This is commonly used in WWW appli-
cations to select a function from a point in an image.)
Locator A locating device (usually a pointer) which provides
a position in “world coordinates.” The pointer is often
controlled by a mouse or touch sensor. (By “world coordi-
nates” we mean the coordinate space of the program with
which the interaction is taking place.)
31
Dial Allows “analog” input — input over a continuous range
of values. It is sometimes implemented as a graphical dial,
or as a slidebar.
Stroke Returns an array of locations, say, the path of a cursor
following a mouse movement, or finger on a touch screen.
Questions — extending technology
How could these modes of interaction map onto, say, voice in-
put? video input? touch sensors?
What modes would be applicable to each case?
Consider a telephone answering system. What modes are used
here?
What other kinds of input could be used?
Do you own any devices with other types of input? (Likely,
your cell phone has several.) Do you know how they work?
Would they map unto the modes described previously?
32
Input modes
There are a number of ways in which a program can interact
with input devices (either real or “virtual” devices):
• Request mode In request mode, the program re-
quests input from the device, and waits until the input is
available. The device is “triggered” by the request, and re-
turns a value after the device has made the “measurement.”
In simple systems (e.g., DOS), this is often accomplished
using a “busy wait” loop:
trigger device
loop: test for input
if (no input) then go to loop
continue with program
Note that only one device is tested for input, and the pro-
gram “wastes time” looping until the input is ready.
In a more sophisticated system (e.g., a multi-process sys-
tem like Windows or UNIX), the program would “sleep”
until the input was ready (this assumes the device has an
interrupt capability) and another process could run.
In either system, the program could not continue until the
input was provided.
33
• Sample mode In sample mode, the device is pre-
sumed to have a valid value whenever it is addressed. Here,
no wait loop is required before the value is received. Often,
though, it is the change in value that is of interest, and
again a wait loop would be required.
Again, information is obtained from the device only under
program control, and only when that particular input is
required by the program. This mode of input could be used
by, say, a clock program, to update the time displayed.
• Event mode Event mode is a more natural mode
for a system with several independent processes, but which
share one or more input devices (say, a windowing envi-
ronment, sharing a mouse and keyboard.) Event mode is
typical for a graphical user interface (GUI) and is supported
by the GLUT library.
In event mode, whenever an event for a device is triggered,
its value, together with an identifier, is placed in an event
queue or similar data structure. Exactly what can trigger
an event, and to which queue an event is posted, depends
on the system in question. It may also depend on what
other events have occurred.
34
Typical events could be pressing a keyboard key or a mouse
button, which might generate external interrupts. Other
events could be moving the cursor from one window to an-
other. This type of event would be determined by the oper-
ating system or window manager, and a kind of “software
interrupt” generated.
Typically, processes call functions which examine an event
queue for events of given types.
User interaction with OpenGL
The GLUT library has a number of functions relating to
the keyboard and mouse, and can be used to provide quite
sophisticated interaction with OpenGL programs.
GLUT also provides other interaction modes such as menus.
These will be discussed later.
35
GLUT mouse events
As an example of a GLUT function, we will look at a function for
a mouse event. A mouse event occurs when a mouse button is
depressed or released. The information returned includes which
button was pushed, the state of the button after the event (up
or down), and the position of the cursor in screen coordinates
of the window.
(Note that an event is generated for a button press only if the
button is pushed when the cursor is inside the window.)
The GLUT mouse function
glutMouseFunc(m_c_f)
requires as its argument a mouse callback function (here, m c f)
of the form
void m_c_f(int button, int state, int x, int y)
where button can have the values GLUT_LEFT_BUTTON,
GLUT_RIGHT_BUTTON, or GLUT_MIDDLE_BUTTON,
state can have values GLUT_UP, or GLUT_DOWN,
and x and y are the positions of the mouse pointer in window
coordinates.
This function defines the actions to be taken if the event occurs.
36
For example, we may wish to terminate the program on the
pressing of the right mouse button. The callback function would
be:
void m_c_f(int button, int state, int x, int y)
{
if (button == GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
exit(0);
}
A more complex example might be if we wanted to draw a
square at the present mouse cursor position, following a left
button push, and exit on a right button push:
void m_c_f(int bt, int st, int x, int y)
{
if (bt == GLUT_LEFT_BUTTON && st == GLUT_DOWN)
drawSquare(x,y);
if (bt == GLUT_RIGHT_BUTTON && st == GLUT_DOWN)
exit(0);
}
37
The function drawSquare(int x, int y) could be:
void drawSquare(int x, int y)
{
y = wh - y /* wh == window height */
glColor3f(1.0, 0.0, 0.0)
glBegin(GL_POLYGON);
glVertex2i(x + size, y + size);
glVertex2i(x - size, y + size);
glVertex2i(x + size, y - size);
glVertex2i(x - size, y - size);
glEnd();
glFlush();
}
Here, wh is a global variable containing the window height, and
size is a global variable defining the size of the square.
Note that all the squares will be drawn red. (The sample pro-
gram shown later generates random colors.)
38
Mouse movement events
the function glutMotionFunc(m m c) sets the callback func-
tion for when mouse movement is detected in the current win-
dow, while a button is pressed.
It requires a callback function of the form
void m_m_c(int x, int y)
where x and y are the positions of the mouse pointer.
In the sample program following, the function drawSquare
shown previously is used as a callback for this function.
There is a complementary GLUT function
glutPassiveMotionFunc(m p c)
which sets the callback function for when mouse movement oc-
curs and no button is pressed.
39
Window events
On most systems, windows can be modified dynamically; e.g.,
the size or location can change. Such changes may give rise to
a window event, which may require service from the program.
For example, if an image is displayed and the window shrinks,
should the image shrink also, or should it be clipped?
Should the window style change if the window is resized? (By
adding scrollbars, for example.)
For window reshaping, The GLUT reshape function
glutReshapeFunc(reshape_callback)
is invoked. The callback function could resize the window,
change the projection, adjust the size of the viewport, mod-
ify the background color, etc.
Following is the code used in the square example:
40
/* reshaping routine called whenever the window
is resized */
void reshape_callback(GLsizei w, GLsizei h)
{
/* adjust clipping box */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (GLdouble)w, 0.0, (GLdouble)h,
-1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
/* adjust viewport and clear */
glViewport(0,0,w,h);
glClearColor (0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
/* set global size for use by drawing routine */
ww = w; /* global variable for window width */
wh = h; /* global variable for window height */
}
41
Keyboard events
Keyboard events are generated when the mouse cursor is in the
window and a key is depressed. The ASCII code for the key,
and the mouse location are returned. The single GLUT function
for this is:
glutKeyboardFunc(keyboard_callback)
To quit on typing of the letter ‘’Q’ or ‘q’ for example, we could
write the following function:
void keyboard_callback( unsigned char key, int x, int y)
{
if (key == ’Q’ | key == ’q’) exit(0);
}
To quit when the ‘esc’ key is pressed:
void keyboard_callback(unsigned char key, int x, int y)
{
switch (key) {
case 27:
exit(0);
break;
}
}
42
An example program with interaction
/* square.c */
/* This program illustrates the use of the glut library
for interfacing with a Window System */
/* The program opens a window, clears it to black, then
draws a trail of random colored boxes at the location of
the mouse each time the left button is clicked and the
mouse is moved. The right button exits the program.
The program also reacts when the window is
resized by clearing the window to black */
#include <GL/glut.h>
/* globals */
GLsizei wh = 500, ww = 500; /* initial window size */
GLfloat size = 3.0; /* half side length of square */
43
void drawSquare(int x, int y)
{
y = wh - y;
glColor3ub( (char) random()%256, (char) random()%256,
(char) random()%256);
glBegin(GL_POLYGON);
glVertex2f(x+size, y+size);
glVertex2f(x-size, y+size);
glVertex2f(x-size, y-size);
glVertex2f(x+size, y-size);
glEnd();
glFlush();
}
/* mouse callback routine */
void mouse(int btn, int state, int x, int y)
{
if(btn==GLUT_RIGHT_BUTTON && state==GLUT_DOWN)
exit(0);
}
44
/* reshaping callback routine -- called whenever
window is resized */
void myReshape(GLsizei w, GLsizei h)
{
/* adjust clipping box */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (GLdouble)w, 0.0, (GLdouble)h,
-1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
/* adjust viewport and clear */
glViewport(0,0,w,h);
glClearColor (0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
/* set global size for use by drawing routine */
ww = w; /* global variable for window width */
wh = h; /* global variable for window height */
}
45
void myinit(void)
{
glViewport(0,0,ww,wh);
/* Set 2D clipping window to match size of screen window
This choice avoids having to scale object coordinates
each time window is resized */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (GLdouble) ww, 0.0, (GLdouble) wh,
-1.0, 1.0);
/* set clear color to black and clear window */
glClearColor (0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}
/* display callback -- required by GLUT 3.0 */
void display(void)
{}
46
/* The main program -- note that there is no "flow
of control" -- the program merely calls functions
to handle interactions */
int main(int argc, char** argv)
{
glutInit(&argc,argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutCreateWindow("square");
myinit ();
glutReshapeFunc (myReshape);
glutMouseFunc (mouse);
glutMotionFunc(drawSquare);
glutDisplayFunc(display);
glutMainLoop();
return 0; /* ANSI C requires main to return int. */
}
/* Note that the function drawSquare has been used
as the callback for glutMotionFunc(). The drawing
is done only when glutMotionFunc() is active. */
47
Graphical output devices
Graphical display units are generally of two types: vector dis-
plays and raster displays.
Vector displays
Vector displays generally display lines, specified by their end-
points. The lines are generally smooth, and of a single color.
A common type of vector display is a pen plotter, although
many other vector displays are available.
While pen plotters are still in use for some functions, particu-
larly drafting and other line drawings, they have been generally
replaced by large-format color printers.
In fact, vector devices are becoming increasingly rare, due to
the difficulty of displaying solid surfaces with this technology.
48
Although vector devices are becoming rare, it is becoming more
common to store certain types of images as vectors. In fact,
many drawing programs (both 2D and 3D, for example, Au-
tocad and Inkscape) generate vector images, which are then
rasterized when displayed.
One key advantage of vector graphics is its scalability. A vector
description of an object can be rendered to virtually any size
without loss of detail.
What happens when a raster image is greatly enlarged?
Most internet browsers now support Scalable Vector Graphics
(svg) — a standard for vector graphics. The increasing resolu-
tion of displays and other output devices make the use of scale
independent graphics more desirable.
49
Raster displays
Raster displays typically have an array of addressable dots,
which can be individually set to a particular color or intensity.
The most common raster devices are displays (CRT and LCD)
and printers (inkjet, laser, dye-diffusion, dye sublimation).
We will examine briefly the technology for several of these de-
vices.
Cathode ray tube (CRT) technology
The standard computer monitor (or TV screen) is based on
the cathode ray tube (CRT). Essentially, a beam of electrons is
generated, and scanned back and forth over the inner surface
of a glass tube. The inner surface of the tube is coated with
a phosphor which emits light when electrons strike it. The
intensity of the emitted light from a point on the phosphor
depends on the number of electrons striking the phosphor.
The electron beam is scanned repeatedly over the phosphor
surface, with the intensity of the beam modulated to produce
the required dark and light spots. If the scanning rate is fast
enough, the screen produces a steady image.
(Television images are redrawn 30 times per second; high quality
monitors are refreshed 60 times per second, or more.)
50
Heatingfilament
(cathode)
Acceleratinganode
coils
Magneticdeflection
Focussingelectrode
Phosphorcoating
Electronbeam
Different phosphor materials emit different colors, so it is pos-
sible to construct a color display. Modern CRT devices use a
triad of colored phosphors — red, green, and blue — to produce
quite acceptable color output.
Usually, an electron gun is used for each of the primary colors,
and a perforated grid called a shadow mask is used to isolate
the electron beam to a single phosphor “dot”.
colortriad (phosphor)
shadow mask
"guns"Electron
51
The quality of a color CRT display depends on the number of
triads on the screen. There are a number of measures of this
“quality” — the number of lines (rows of dots) and the “dot
pitch” (distance between dots) being commonly quoted.
Presently, good monitors can exceed 2000 lines, and can have
more than 3800 triads per line. The dot pitch can be less than
0.25 mm.
(Regular television has 480 lines x 640 triads per line, and is
“interlaced” — every second line is redrawn each 1/60th of a
second, for a full refresh rate of 1/30th of a second.)
A “standard” 17 inch monitor (16 inches viewable) can have
specifications as follows:
0.25 mm dot pitch
refresh rate 48–120 Hz
1024 x 768 resolution at 65 Hz
1600 x 1200 resolution at 65 Hz
A high end monitor (24 inch) can have a dot pitch of 0.20 mm,
and a resolution of 2048 x 1536 at > 60Hz.
A high definition television (1080i) has a resolution of 1920 x
1080 at 60 frames per second (interlaced).
Recently, ultra high definition television is available with reso-
lution of 4096 x 2160 (or 3840 x 2160).
52
Liquid Crystal Display (LCD) technology
Liquid crystals are liquids with long organic molecules that can
polarize light. When an electric field is applied, the molecu-
lar alignment (and consequently the direction of polarization)
changes.
Basically, an LCD is a “sandwich” of liquid crystals between
two finely grooved plastic plates, where the grooves of one plate
are perpendicular to the grooves of the other.
Liquidcrystal
Polarizingfilters
lightBack-
In order to make a display, electrodes must be added. These
are on transparent layers, under the polarizing layers.
53
Different “shades of grey” can be obtained by varying the volt-
age across the electrodes. (Typically only 50–100 different shades
can be produced, per pixel.)
Liquid crystals can be colored to produce a color display, with
about 8–10 bit color resolution (256–1024 colors).
One problem with this type of liquid crystal is that it takes
about 1/4 of a second (250 ms.) to change state.
With this type display, rapid movement leaves “ghosts” for a
fraction of a second.
This problem can be eliminated by adding a transistor amplifier
to each pixel, so the voltage can be switched more rapidly.
This is a more expensive process, but produces pixels with a
response time about 10 times faster (25ms.) and with more
“shades of grey” — a contrast ratio of about 300:1.
This thin-film transistor technology (TFT) also allows smaller
and lighter displays.
Both types of LCD (TFT and DSTN) consume far less power
than a CRT (about 75% less), and are therefore almost exclu-
sively used in battery operated devices.
54
Hard-copy raster devices
The two major technologies for hard copy raster devices are
inkjet printers and laser printers.
Inkjet printers appear simple in operation; they merely force
drops of ink to spatter onto a sheet of paper, in fixed positions.
In reality, however, such printers can achieve resolutions of 1200-
2400 dots per inch (DPI) and considerable design effort has
gone into all aspects of these devices. (Special ink formulation,
droplet delivery, etc.)
Two methods are used to deliver drops; a piezo-electric pulse:
Piezodisc
Inkreservoir
cavity
Charge
Droplet
Nozzle
where changing the charge on the piezo disk causes it to expand
or contract and a droplet is forced from the outlet,
55
or a thermal technique in which some ink solvent is evaporated:
Heating resistor
Nozzle
Firing chamber
In either case, several hundred inkjets are contained in each
print head. All of these can be are fired independently.
Usually, drops are a single color, and color shades are obtained
by varying the number of color dots in a small area.
(In fact, this is also the case for color lithography using a print-
ing press.)
Some inkjet printers can mix different colors at a single point,
extending the color resolution.
Normally, four colors (red, green, blue, and black) are used, al-
though some printers may use additional colors for particularly
high color resolution.
56
Laser printers
Laser printers use a technology known as “xerography” in which
light is used to remove static electric charge from a drum, or
roller.
The roller is then placed in contact with a dry ink, some of
which bonds electrostatically to the drum. The ink powder is
then transferred to a sheet of paper, and the paper heated to
melt the ink and bond it to the paper.
For color printing, this is repeated four times — once for each
color — using four drums in series.
Laser Toner
Papertray
outputtray
Paper path
Heatingroller (fuser)
mirrorrotating
Drum
57
Other printing technologies
Instead of a laser, some printers use a liquid crystal shutter to
pattern the drum. Others use light-emitting diodes.
There are a number of other printer technologies. Two of special
note are a solid ink process in which dyes in a wax substrate
are deposited on a drum, and then cold-fused onto the paper
in one pass. This process is capable of mixing colors at a single
dot.
The most precise color technology is dye diffusion technology.
Here, the dye is heated until it becomes a vapor (sublimation)
and varying amounts of the dye are allowed to diffuse into points
on the paper. Again, four different colors are used, and each
dot can receive different amounts of each dye.
This process is slow and expensive, but produces very high qual-
ity color.
HeaterSheet of material
with dyes
PaperDye vapor
58
All the raster technologies have one thing in common — they
have a finite number of regularly spaced points which can be
set to some color.
A common abstraction for the programmer is the “frame buffer”
which is a data structure containing a number representing the
intensity of each color component at each of these points.
The most interactive component of a modern computer system
is the video output, and this usually has a dedicated controller,
as well as a frame buffer, separate from the processor and mem-
ory.
The most basic graphics card or graphics processor contains
a frame buffer and circuitry to convert the color values into the
voltages or currents used by the display. The graphics card also
generates the timing signals (horizontal refresh rate, pixel rate,
etc.) for the display.
Nowadays, graphics cards have powerful graphics processors
which handle many of the rendering functions and image trans-
formations directly in their circuitry. (We will describe many
of these functions later.)
59
The “graphics card”
A typical basic graphics card might look as follows:
Modern graphics cards contain much more memory than is re-
quired for the frame buffer.
Typically, some of this memory is used to store a description of
the scene, (information about primitives, vertices, colors, etc.)
which is interpreted by the graphics processor.
Other memory is used to store textures and other bitmaps.
60
The video processor
Modern video processors are very powerful computational de-
vices, rivaling that of the CPU. In fact, most have multiple,
pipelined processors. (Many graphics functions can readily be
parallelized.) The following shows the architecture of an early
Raedon processor by one of Canada’s largest graphics card de-
sign companies, ATI, now owned by AMD:
61
A recent moderate cost graphics card – note the
cooling for the graphics processors
Cards like this contain processors with far more raw computa-
tional power than in the CPU of the processor itself.
62
The previous graphics card, made by Nvidia, has a GPU (Graph-
ics Processing Unit) with much more raw computational power
than the CPU in the computer itself.
GPUs have become highly parallel multi-core systems enabling
efficient manipulation of large blocks of data.
Several generations of Nvidia processors have several sets of
banks of 192 core processors. These are effectively SIMD pro-
cessors, with each bank capable of running a different program.
The actual execution of the shader code, at the lowest level, is
run on these cores.
A high-end graphics processor may well have thousands (a mul-
tiple of 192) of those cores. For example, the GTX 780 Ti GPU
has 2880 of these units. (15 banks of 192 core processors.)
The next generation of Nvidia processors will have a different
microarchitecture, with 128 processors per block, but with in-
creased performance.
Their major competitor, the Radeon family from AMD, has a
similar kind of architecture, and also supports thousands of core
processors.
63
Can you see a 3-d figure here?
64
3-D displays
It is possible to generate a 3-dimensional (stereoscopic) image
by displaying two separate images, offset by a displacement cor-
responding to the eye displacement, in a way that the left eye
“sees” the left image, and the right eye “sees” the right image.
SCENE
The most common way this is done on a conventional display is
by using goggles with some kind of shutter system and rapidly
displaying on a screen a sequence of images synchronized with
the shutter.
Another type of system uses glasses which each reflect an image
from the surface of the glasses, or a small prism mounted in the
frame, directly into the left and right eyes, respectively.
There are also systems employing moving mirrors or projection
surfaces which allow a volumetric display — one which can
produce an image which can be viewed from different angles.
65
The company Dimension Technologies Inc. (DTI) has a tech-
nology called “parallax illumination” which can be applied to
LCD displays by adding an illumination plate behind the dis-
play which illuminates the even or odd column of pixels, de-
pending which image (left or right) is being displayed.
Pixels
DisplayLiquid Crystal
IlluminationPlateLight lines
For a certain range of viewing distances, and when both eyes
are directly in front of the display, the left eye will see one set
of columns, and the right eye will see the adjacent columns.
This parallax effect provides sufficient separation to produce a
stereoscopic effect without the use of goggles or glasses.
66
The effect disappears for off axis viewing, and for viewing at a
distance from the screen. It is possible to adjust the effect for
off axis viewing, by tracking the position of the eyes.
Viewing Zones
R R RL L LRL
Research into other related “auto-stereographic” viewing tech-
niques have produced results indicating that it may be possible
to produce similar stereographic images which can be viewed by
several people, provided that their eye motion can be tracked.
67
3-D “printing”
The technology for “numerically controlled machining” has been
available for a long time.
http://en.wikipedia.org/wiki/Numerical_control
Early machines were very expensive, and operated by removing
material from a workpiece, much as a machinist would do by
hand. More recently, machines which can form materials in dif-
ferent ways have become common, and at present many plastic
materials, and some metals, can be deposited in some way to
build structures by accreting material, rather than removing it.
Commercial systems use many different technologies; UV lasers
which polymerize a liquid plastic material, IR lasers which melt
small particles of material which fuse together, and extruders
which extrude soft or molten materials which then freeze in
place, building up a structure.
A common use is for the rapid prototyping of mechanical parts.
Presently, there are a number of low cost ”hobby” devices which
act as 3-D printers, typically heating a filament of plastic (usu-
ally ABS) to the point where it can fuse to other pieces of the
same plastic. Quite complex structures can be built up with
these devices.
68
Rep-Rap machines
One class of hobby level 3-D printer is the “Rep-Rap” machines
http://en.wikipedia.org/wiki/RepRap_Project
http://www.reprap.org/wiki/RepRap
These machine basically consist of smooth and threaded rods,
held together with plastic joints and other components printed
on a similar machine. The idea is that the machines are to
be self-replicating (at least, all but the metal and electronics
components).
The controller is typically a small, 8-bit microcontroller which
interprets commands which cause the print head to me moved.
Typically, movement is accomplished with “stepper motors.”
The drive mechanism is usually either a screw, or a gear and
ratchet (somewhat similar to a small bicycle chain.)
The extruder (which heats and extrudes the plastic material)
can be moved along three independent axes, although for the
most part, the material is deposited layer by layer.
69
A typical “home-made” Rep-Rap
70
One characteristic of most current graphics devices is that they
are raster devices — they approximate lines and surfaces with
discrete points. Even the 3-D printers usually deposit lines of
material, with small, fixed resolution.
Objects in the “real world”, on the other hand, have continuous
surfaces, and volumes.
Even OpenGL represents surfaces as polygons defined with ver-
tices, and the polygon edges are continuous straight lines joining
the vertices.
In order to represent these entities, we need to convert these
lines and surfaces to a set of points which best represents those
lines or surfaces at a particular resolution.
This process is called rasterization.
This is usually done at the time a scene is displayed on the
output device.
71
Rasterization
We will begin to examine how a graphics package like OpenGL
displays its primitive graphics elements.
The process by which primitives are converted to a two-dimensional
set of image points (pixels) is called rasterization. We can think
of the display as an array of rectangular pixels, each of which
can have a number of attributes (color, intensity, etc.)
012345
.
.
.
1 .2 3 4 5 ..0
The object is to determine which pixels form part of the object,
and then to assign the appropriate attributes to those pixels.
72
(Of course, in a real display, pixels may not be square, and there
may be attributes which cannot be displayed on a given device
— color on a monochrome device, for example.)
Note: In OpenGL, the center of a pixel is located halfway be-
tween the integers representing the array locations.
The preceding showed some of the difficulties with a raster im-
age — the “staircase” effect in the imaging of a straight line,
and the fact that primitives may have to be “clipped” by the
edge of a window.
There are ways of reducing the visual appearance of effects like
the staircase effect. This is called anti-aliasing, and usually
requires a wider linewidth. The general idea is to vary the
intensity of a pixel, so that it relates to the fraction of the line
passing through the pixel.
Given our simple set of primitives, how can they be rasterized?
Note that the “frame buffer” abstraction provides a complete
description of a raster image.
(Since the raster image is a finite array of “colored dots”, the
image is completely specified if we specify all the attributes of
each dot.)
73
In the following, we will assume that either the scenes we are
viewing are two-dimensional, or that they have already been
projected onto a two-dimensional surface.
We will also assume that they have been scaled to the view-
port, and that the individual pixel locations can be identified
by integers.
Further, two adjacent pixels are one unit apart.
x x
yy 1 unit
i i+1
j+1
j
The pixels form a unit grid, where each pixel has an integer
address in both the x and y dimensions.
Rendering is normally done in the viewport (the screen, in
screen coordinates), because integer arithmetic can be used for
most operations, and two-dimensional operations are simpler.
A 3-dimensional frame buffer might also require substantially
more memory.
74
Points
Points are represented by a square block of pixels. Most systems
require that a point be at least one pixel.
In many systems, points can be set to different sizes, and the
location of the pixels is obtained by either rounding or trunca-
tion.
Note that the two points here are the same size, but are rendered
differently. The difference depends on the actual location of the
pixel.
Anti-aliasing can be used here to make the two representations
more alike.
75
Lines
A simple algorithm for line rasterization, assuming the line has
been expressed in screen coordinates, is obvious from the equa-
tion for a straight line, yi = mxi + b
We simply evaluate the y value at each of the integer values of
x in the viewing area.
In fact, given an initial point, (x0, y0), the y value for the next
point could be calculated simply by adding the slope, m, to the
present y value (without evaluating the equation directly.)
Note that in the example shown, the line with the steeper slope
should have more points along the y-axis.
76
The algorithm could easily be modified to fix this by increment-
ing y by 1 and solving for x.
Because the value ofm is a real number, this algorithm requires
real arithmetic for the addition,
y = xmin * m + b;
for x= xmin, x <= xmax, x++ {
drawpixel(x, int(y + 0.5));
y = y + m;
}
This simple incremental algorithm is one of the simplest exam-
ples of a digital difference analyzer (DDA) algorithm. A DDA
was a mechanical calculator which used simple approximations
(finite differences)to the derivatives of a function to evaluate
the function itself. (For polynomials, a DDA which used high
enough order differences could give exact results.)
It used to be much slower to do real arithmetic than integer
arithmetic, so there was a strong incentive to find integer al-
gorithms for the primitive operations. (Now, many of those
functions are implemented in hardware, and integer arithmetic
uses fewer logic gates.)
77
The Bresenham algorithm
Bresenham’s algorithm, like the previous, is an incremental al-
gorithm, but uses only integer calculations.
We assume that the line segment goes through points (x0, y0)
and (x1, y1), and that the slope m satisfies 0 ≤ m ≤ 1.
In this case, if we have already drawn a pixel at one point, then
there are only two possible locations for the next pixel in the
line:
(x ,y )k k
M
A
Qb
a
B
Considering diagram A, we need only find which is larger, a or
b. In fact, if we take d = b − a as a decision variable, then
only the sign of d is important — if d is negative (i.e., b < a),
then the line is closer to the lower pixel.
It is possible to calculate d using only integer arithmetic.
The actual coordinate at point xk+1 = xk + 1 is
y = m(xk + 1) + B
78
so b = y − yk
= m(xk + 1) + B − yk
and a = (yk + 1)− y
= yk + 1−m(xk + 1)− B
subtracting,
b− a = 2m(xk + 1)− 2yk + 2B − 1
substituting m =y1 − y0x1 − x0
= ∆y/∆x
yields
b− a = 2(xk + 1)∆y/∆x− 2yk + 2B − 1
We can define a decision parameter
dk = ∆x(b− a)
= 2∆y · xk − 2∆x · yk + c
where c = 2∆y +∆x(2B − 1)
and is independent of pixel position (xk, yk).
If the pixel at yk is closer to the line path than that at yk + 1
then dk is negative. In that case, the lower pixel should be
plotted. Otherwise, the higher pixel should be plotted.
79
At step k + 1, the decision parameter is
dk+1 = 2∆y · xk+1 − 2∆x · yk+1 + c
Subtracting dk+1 from dk = 2∆y · xk − 2∆x · yk + c
dk+1 − dk = 2∆y(xk+1 − xk)− 2∆x(yk+1 − yk)
but xk+1 = xk + 1,
so dk+1 = dk + 2∆y − 2∆x(yk+1 − yk)
where (yk+1 − yk) is 0 or 1 depending only on the sign of dk.
d0 is evaluated by setting k = 0 as
d0 = ∆x(b− a) = 2(x0 + 1)∆y +∆x(−2y0 + 2B − 1)
= 2x0∆y + 2∆y − 2y0∆x + 2B∆x−∆x
= 2∆y −∆x + 2x0∆y − 2∆x(y0 −B)
but y0 −B = mx0 = x0∆y/∆x and therefore
d0 = 2∆y −∆x
From this, the dk can be evaluated recursively as:
dk+1 = dk + 2∆y − 2∆x[sign(dk)]
where sign(dk) is 0 if dk is negative, and 1 if dk is positive.
80
Bresenham’s line algorithm:
1. Input the endpoints (x0, y0) and (x1, y1)
2. Calculate constants ∆y,∆x, 2∆y, and 2∆y − 2∆x
and d0 = 2∆y −∆x
3. Plot the first point (x0, y0) in the frame buffer.
4. if dk < 0 then plot (xk+1, yk) and set dk+1 = dk + 2∆y
else plot (xk+1, yk+1) and set dk+1 = dk+2∆y − 2∆x
5. Increment k and go to 4 until k = ∆x
There are other integer algorithms which also can be used to
accomplish the same thing, but Bresenham’s line algorithm is
perhaps the best known.
For example, the book Computer Graphics: Principles and
Practice which was listed as a reference for this course describes
a similar algorithm, the midpoint algorithm, which is equivalent
to Bresenham’s algorithm for lines. It uses a similar idea, but
determines whether the line is above or below the midpoint
between the two pixels. The decision variable is the sign of
difference between the midpoint and the actual point.
81
A double step algorithm
Wu and Rokne (1987) developed an algorithm which looks ahead
at two pixels. Here, there are four possible cases.
case 3 case 4case 1 case 2
It is obvious that patterns 1 and 4 cannot occur on the same
line. Also, if the slope is > 1/2, case 1 cannot occur. Similarly,
if the slope is < 1/2 case 4 cannot occur.
So, a simple test of the slope narrows the choice to three cases.
Looking at the case where the slope is between 0 and 1/2, we
need to determine which of cases 1, 2, or 3 apply.
Setting the decision variable initially to d = 4∆y−∆x, then if
d < 0 then case 1 must be used; otherwise, test if d < 2∆y.
To increment d, the following rules apply:
dk+1 = dk + 4∆y if dk < 0 (case 1)
dk+1 = dk + 4∆y − 2∆x otherwise (cases 2 or 3)
82
Circles
Although OpenGL does not have a circle primitive, it is inter-
esting to look at how a circle could be rasterized.
A direct approach, solving the circle equation y = ±√R2 − x2
explicitly, is inefficient in a hardware implementation. (The
floating point square root function is more expensive than sim-
ple multiplication.)
The symmetry of a circle is helpful, for a circle centered at the
origin, because simple sign changes of the x and y values can
generate a full circle from only an octant (a 45◦ segment.)
(x,y)
(y,x)
(-x,-y) (x,-y)
(-x,y)
83
The midpoint algorithm for circles
It is possible to extend Bresenham’s algorithm to generate cir-
cles, but we will look at the (quite similar) midpoint algorithm.
Consider only one 45◦ octant of a circle, the segment from 90◦
to 45◦. The idea is to select which of two pixels is closer to
the circle by comparing the position of the line to the midpoint
between the two pixels.
Label the pixel directly to the right (East) of the current pixel
as E, and the other possibility — to the right and down — as
SE.
The function F (x, y) = x2+y2−R2 is 0 on the circle, negative
for points in the interior, and positive in the exterior.
MM
SEM
E
SE
E
We determine a decision variable d, which is the value of the
function at the midpoint, (xk + 1, yk − 1/2)
dk = F (xk + 1, yk − 1/2) = (xk + 1)2 + (yk − 1/2)2 −R2
84
If dk < 0, E is chosen, xk+1 = xk + 1, and yk+1 = yk, and
dk+1 = F (xk + 2, yk − 1/2) = (xk + 2)2 + (yk − 1/2)2 −R2
dk+1 − dk = 2xk + 3, so ∆E = 2xk + 3
otherwise, SE is chosen, xk+1 = xk +1, and yk+1 = yk − 1, and
dk+1 = F (xk + 2, yk − 3/2) = (xk + 2)2 + (yk − 3/2)2 −R2
dk+1 − dk = 2xk − 2yk + 5, so ∆SE = 2xk − 2yk + 5
Recall that, in the linear case, these were constants, but here
they are (simple) linear functions of xk and yk.
Now all that is necessary is to compute the initial position.
The starting pixel is at (0, R), the first midpoint at (1, R−1/2)
d0 = F (1, R− 1/2) = 1 + (R2 − R + 1/4)− R2 = 5/4−R
Although R is an integer, this expression has a fraction.
This can be eliminated by defining a new decision variable, h
as h = d− 1/4, and substituting h + 1/4 for d
The initial point, h0, is
h = 1−R
The comparison d < 0 becomes h < 1/4, but since h is an
integer initially, and is incremented by an integer (∆ E or ∆SE,
both of which are independent of d) we can use the comparison
h < 0.
85
The algorithm can be made more efficient by using the second
order difference to calculate the values of dk. (Since the circle is
a quadratic function, the second differences should be constant.)
To do this, we find the finite difference between the ∆’s for each
of the two possible choices (pixel at E or SE).
If the pixel is at E, then (xk+1, yk+1) = (xk + 1, yk),
∆E = 2xk + 3 and ∆SE = 2xk − 2yk + 5
∆2E = ∆Ek+1
−∆Ek= [2(xk + 1) + 3]− [2xk + 3] = 2
similarly,
∆2SE = ∆SEk+1
−∆SEk= [2(xk+1)−2yk+5]−[2xk−2yk+5] = 2
If the pixel is at SE, then (xk+1, yk+1) = (xk + 1, yk − 1),
∆E = 2xk + 3 and ∆SE = 2xk − 2yk + 5
∆2E = ∆Ek+1
−∆Ek= [2(xk + 1) + 3]− [2xk − 3] = 2
similarly,
∆2SE = ∆SEk+1
−∆SEk= [2(xk+1)−2(yk−1)+5]−[2xk−2yk+5] = 4
The ∆’s can be evaluated at each iteration k using the ∆2
terms corresponding to the present k (both ∆E and ∆SE must
be incremented with the appropriate ∆2 term at each step.)
86
Following is the midpoint algorithm for a circle, centered at the
origin, and starting from the point (0,R) and continuing for a
45 degree sector:
1. Input the radius, R
2. Set initial ∆E = 3 and ∆SE = 5− 2R, k = 0
3. Plot the first point (0, R) in the frame buffer.
4. if dk < 0 then set dk+1 = dk +∆E, ∆E = ∆E + 2,
∆SE = ∆SE + 2, x = x + 1
else set dk+1 = dk +∆SE, ∆E = ∆E + 2, ∆SE = ∆SE + 4,
x = x + 1, y = y − 1
5. plot (x, y) in the frame buffer
6. Increment k and go to 4 until y = x
The following code implements this version of the algorithm:
87
void raster_circle(int radius, int color)
int x, y, d, dE, dSE;
x = 0; /* initial point */
y = radius;
dE = 3; /* initial deltas */
dSE = 5 - radius * 2;
d = 1 - radius;
plotpoint(x, y, color);
while (y > x) { /* loop for 45 degree sector*/
if (d < 0) { /* chosen point is E */
d = d + dE;
dE = dE + 2;
dSE = dSE + 2;
x++;
} else { /* chosen point is SE */
d = d + dSE;
dE = dE + 2;
dSE = dSE + 4;
x++;
y--;
}
plotpoint(x, y, color);
} }
88
Rasterizing solids — polygons
Two-dimensional filled figures are one of the major advantages
of raster images.
The filling of images is usually broken into two parts — deter-
mining what pixels are interior to the clipped primitive, and
therefore are to be filled, and exactly how the primitive is filled
(e.g., shading, transparency, etc.)
We will look first at how to determine which pixels to fill.
Initially, we will consider an unclipped polygon, to be filled with
a solid color. We will do this by taking successive horizontal
scan lines that intersect the primitive, and filling in the spans
of pixels that are interior to the primitive. (this is similar to
the way pixels are displayed on a CRT.)
CurrentScan line
Span algorithms exploit spatial coherence — the property that
adjacent pixels do not change (unless at a polygon boundary.)
89
This type of spatial coherence is often called span coherence.
Some hardware graphics engines have a “block copy” function
which copies a block of pixels very rapidly.
We will first consider the filling of simple, convex polygons like
triangles. Here, a simple algorithm could be:
1. Draw the polygon boundary and label boundary points
2. On each scan line, color all points between the two bound-
ary points
interior pixel
exterior pixel
Note that there is a problem here; for a polygon, we want to
fill interior pixels only. When a line is drawn, some pixels will
fall on either side of the “true” line. Only those interior to the
polygon should be rendered in a solid figure.
Also, what happens to the fill for horizontal lines?
Recall that OpenGL only guarantees correct rendering of convex
polygons.
90
To extend this to non-convex polygons, we need to keep an
ordered list of intersections. A left-to-right traversal of these
could then be used to determine whether a pixel was interior or
exterior (a parity check on the number of intersections.)
Note again that a filled polygon and a line figure with the same
vertices may have different boundaries.
Exterior polygon boundary (line)Interior polygon pixels
Interior polygon boundary
A
B
E
C
D
Again, there are some unanswered questions:
91
1. What happens when two polygons meet at a pixel?
2. What happens when two polygons share a vertex?
3. How do we treat interior vertices of a non-convex figure
(like vertex B in the previous figure)?
4. What happens when vertices define a horizontal line?
For the first case, arbitrarily, the left and bottom edges (mini-
mum x and y values) are considered to be part of the polygon,
while the right and top edges are not. (This effectively treats
the scan lines in a polygon as left closed, right open intervals.)
Any other consistent choice would work as well.
For shared vertices, the ymin vertex of an edge is counted, but
not the ymax vertex.
This also works for the case of an interior vertex in a non-convex
figure. (Consider vertex B in the previous figure — it is counted
as the ymin of both AB and BC, so act as if it was two vertices.)
Horizontal edges are handled by not counting their vertices.
(They will be dealt with correctly as vertices of other edges.)
92
Example:
Shared vertex
4142434445464748495051
82 84 85 86 87 88 89 90 91 92
Shared edge
83
Note that the shared edge is the upper edge of the red (lower)
rectangle, and the lower edge of the green (upper) rectangle, so
it is colored green.
The lower shared vertex of the triangle is on its left edge, so
it is part of the triangle (blue), not part of the (red) rectangle,
where it is on the right edge.
The upper shared vertex of the triangle is the top, so it is part
of the upper rectangle (green).
93
Scan-line intersections with primitives
So far, we have not considered the problem of computing the
intersection of the scan line with the primitives — there are
many scan lines, so this computation should be done efficiently.
(Some extremely efficient algorithms are available for special
classes of figures; rectangles, for example.)
This is where edge coherence is useful — it is likely that most
edges intersecting scan line i also intersect line i + 1. If we
know the slope of the edge, we can use a method similar to the
line rasterizing algorithms to determine the intersections with
the next scan line. As with the rasterization algorithms, the
arithmetic can be done with integers, for a rational slope, m,
by noting that the pixel in the next scan line is at position
xi+1 = xi + 1/m = xi +xmax − xmin
ymax − ymin= xi +∆x/∆y
The following code snippet (from the text) applies this process
to the left edge of a polygon:
94
void LeftEdgeScan(int xmin,int ymin,int xmax,
int ymax,int color)
{
int x, y, dx, dy, increment;
x = xmin;
dx = xmax - xmin;
dy = ymax - ymin;
increment = dy;
for (y = ymin; y<ymax; y++){
plotpixel(x, y, color);
increment += dx;
if (increment > dy){
/* Overflow, so round up to next pixel
and decrement the increment */
x+= 1;
increment -= dy;
}
}
}
95
We can develop a scan-line algorithm which takes advantage
of this edge coherence. We need to keep track of the sets of
edges that have (or will have) intersections with the present
and upcoming scan lines.
We will create two tables holding information about polygon
edges.
First, we create a global edge table (ET) containing all edges
sorted by their smaller (lower) y coordinate. There may be
several edges with the same lower y coordinate, and those are
sorted by their x coordinates (a bucket sort, where each bucket
contains the values sorted by their x coordinate.)
The table index is ymin and the entries are the maximum y
values ymax for each edge, the minimum x value (xmin), and
the increment for the x value (1/m).
We create a second data table called the active edge table
(AET) to which we will add the edges which will intersect with
the scan line, and delete edges which will not.
96
For the previous figure, redrawn here, the following is the ET
with entries in the form (ymax, xmin, 1/m):
41 (47, 82, 0) → (47, 86, 0) → (47, 86, 1/2) → (47, 91,−1/3)
47 (51, 82, 0) → (51, 92, 0)
Shared vertex
4142434445464748495051
82 84 85 86 87 88 89 90 91 92
Shared edge
83
(The rectangle and triangle begin on scan line 41, and have 2
edges each. The upper rectangle begins on line 47, and has 2
edges. Horizontal edges are not included.)
97
Once the ET has been formed, the following processing steps
for the scan-line algorithm are completed:
1. Set y to the smallest y coordinate that has an entry in the
ET, that is, y for the first nonempty bucket.
2. Initialize the AET to be empty.
3. Repeat until the AET and ET are empty;
(a) Move from ET bucket y to the AET who edges whose
ymin = y (entering edges).
(b) Remove from the AET those entries for which y = ymax
(edges not involved in the next scan line), then sort the
AET on x (made easier because ET is presorted).
(c) Fill in desired pixel values on scan line y by using pairs
of x coordinates from the AET.
(d) Increment y by 1 (to the coordinate of the next scan
line).
(e) For each nonvertical edge remaining in the AET, update
x for the new y.
98
Flood filling
Another, relatively simple, technique for filling a polygonal area
is the flood fill or boundary fill algorithm. This method is often
available in “paint programs.”
A point interior to a polygon is chosen, and the entire polygon is
filled, until a boundary line is reached. (Usually, the boundary
is taken to be a pixel with a color other than the background
color.)
A simple algorithm examines the 4 pixels in the vicinity of the
chosen pixel, and if they are not boundary pixels, they are given
the same color as the chosen pixel. The process then continues.
This could be done, possibly more efficiently, for the 8 nearest
neighbours of a pixel, or for other patterns, as well.
99
The following code implements the algorithm for four neigh-
bours recursively:
void floodfill (int x, int y, int color, int boundary)
{
int current;
current = getpixel(x,y);
if ((current!=boundary) && (current!=color)) then
plotpixel(x, y, color);
floodfill (x + 1, y, color, boundary);
floodfill (x - 1, y, color, boundary);
floodfill (x, y + 1, color, boundary);
floodfill (x, y - 1, color, boundary);
}
}
For the 8 nearest neighbours of a pixel, the modification would
merely be to add function calls for arguments x + 1, y + 1.
etc.
100
Clipping
Since the viewing area, or viewport, may be smaller than the
scene to be displayed, primitives may have to be clipped at the
edges. We will look at the clipping of our primitives.
Points
For a rectangular clipping region with boundaries xmin, xmax,
ymin, and ymax, a point must simply lie within the boundary
region.
Both the following inequalities must hold:
xmin ≤ x ≤ xmax and ymin ≤ y ≤ ymax
A point can therefore be trivially rejected if any of those four
inequalities does not hold.
max
min min
max max
min
viewport
y > y
(x , y )
(x , y )
x < x
101
Lines
Lines can also be trivially accepted if both endpoints lie within
the clipping boundary. If both endpoints are outside the clip-
ping boundary, the line may still pass through the clipping re-
gion.
One approach is to look for an intersection with any of the clip
rectangle edges. If there is such an intersection, the line is partly
within the clipping region.
This requires solving two simultaneous equations for each edge,
and for each line. (For n lines and 4 edges, there are 4n sets of
equations.)
C
E
F
G
H
G’
I’
A H’C’
D
J’
J
B
I
The normal slope-intercept formulation for a line does not work
for vertical lines, and we deal with finite, not infinite, lines.
102
A parametric formulation can solve this problem: x = x0 +
t(x1 − x0) and y = y0 + t(y1 − y0)
This still requires solving two equations for each edge of the
clipping region.
A more efficient algorithm, the Cohen-Sutherland line clipping
algorithm, does simple region checks if a line cannot be triv-
ially accepted. For example, line segment AB could be triv-
ially rejected because both endpoints are < xmin. Line seg-
ment EF is trivially accepted because both endpoints satisfy
xmin ≤ x ≤ xmax and ymin ≤ y ≤ ymax.
C
E
F
G
H
G’
I’
A H’C’
D
J’
J
B
I
If a segment cannot be trivially accepted or rejected, it is divided
into two segments at a clip edge, so one segment can be trivially
rejected (e.g., CD into C’D). A line may require several “clips.”
103
The Cohen-Sutherland line clipping algorithm
This algorithm has a clever way to reject line segments.
To perform the trivial accept/reject tests, the edges of the clip
region are extended, defining nine separate regions. Each region
is labeled by a 4-bit binary number, the outcode, where each bit
(1 = true, 0 = false) corresponds to the following conditions:
bit 1 (MSB) y > ymax above top edge
bit 2 y < ymin below bottom edge
bit 3 x > xmax right of right edge
bit 4 (LSB) x < xmin left of left edge
1000 1010
01000101 0110
1001
00000001 0010
Each bit of the outcode is simply the sign bit of the differences
(ymax − y), (y − ymin), (xmax − x), (x− xmin), respectively.
Endpoints of a line are assigned their outcodes, and if the log-
ical AND of both outcodes is nonzero, the line can be trivially
rejected. The line is trivially accepted if the outcode is 0000.
104
If a line cannot be trivially accepted or rejected, it must be
subdivided into two segments so that one or both can be trivially
rejected. The line is cut using an edge that the line crosses.
The outcode has the useful property that bits set to 1 in the
outcode correspond to edges crossed.
If a line is not trivially rejected, this property can be used to
identify an endpoint which is outside the clipping region, and
an edge which the line crosses. The endpoint is replaced by the
point at which the line crosses the clipping line.
Of course, the order of clipping is fixed; for our example it cor-
responds to the order of the bits in the outcode — top, bottom,
right, left.
The intersection is readily calculated from the slope-point for-
mula:
y − y0 = m(x− x0), where m =y1 − y0x1 − x0
105
Example
1000 1010
01000101 0110
1001
00000001 0010
A
B’’B’
B D
C
Here, for line AB, A is inside the clip region (outcode 0000) and
B is outside, upper right (outcode 1010). Point B is chosen, and
clipped first against the upper boundary at B’. The segment AB’
cannot be trivially accepted or rejected (B’ is outside, right, with
outcode 0010) and must be clipped against the right boundary.
This leaves segment AB” which is trivially accepted (both A
and B” have outcodes 0000).
How many times will line CD be clipped before it is trivially
rejected?
No, two. Why?
106
The Cyrus-Beck algorithm for line clipping
Another line clipping algorithm uses parametric expressions for
lines. A line is represented as
P (t) = P0 + t(P1 − P0) 0 ≤ t ≤ 1
Given a clip region (say, the left edge of a rectangle) as shown,
with edge Ei, and outward normal Ni to the edge, and picking
an arbitrary point PEion the clipping edge, a point can be
determined to be inside or outside the boundary by looking at
the sign of the dot product of the normal Ni and the vector
between PEiand P (t), as follows:
t
t
t
t
t
t
✟✟✟✟✟✟✟✟✟✟✟✟✟
✡✡
✡✡
✡✡
✡✢
PPPPPPq
❄
✛
Ei
PEi
P0
P1
Ni
Ni.[P (t)− PEi] = 0
Ni.[P (t)− PEi] < 0
Ni.[P (t)− PEi] > 0
Clearly, at the clipping boundary,
Ni · [P (t)− PEi] = 0
so we can solve for t after substituting P (t) = P0 + t(P1 − P0)
Ni · [P0 + t(P1 − P0)− PEi] = 0
107
Ni · [P0 − PEi] +Ni · t[P1 − P0] = 0
Letting vector D = (P1 − P0)
t =−Ni · [P0 − PEi
]
Ni ·DThis is applicable if Ni ·D 6= 0.
Similarly, we can find values of t for each of the clipping bound-
aries. (The boundaries do not even need to be rectangular.)
We need to determine which line segments lie within the clip
boundary. Certainly, any values of t > 1 can be discarded,
because they are outside the line P0P1.
Next, we need to know whether the intersection is on the clip
boundary. We can characterize the intersections as potentially
entering (PE) or potentially leaving (PL) the clip region by
the angle between P0P1 and Ni. If the angle is greater than 90◦
then it is PE, otherwise it is PL.
The sign of Ni ·D gives us this:
Ni ·D < 0 ⇒ PE (> 90◦) Ni ·D > 0 ⇒ PL (< 90◦)
We now choose the PE point with the largest t value (if t < 0,
then t is set to 0), and the PL point with the smallest t value
(if t > 1, then t is set to 1).
If the t for PL is larger than t for PE, then the line is entirely
outside the clipping region.
108
P0
PL
PE
PL
P
PPL
PL
0P
1
1
P
PP
0
1
PLPE
PE
PL
PE
PLPE
P1
P0
Note that for the top line, PL < PE, so it is outside the clip
boundary.
The bottom line is also outside the clip boundary, but what
condition could be tested to determine this?
109
Clipping polygons — the Sutherland-Hodgman al-
gorithm
This algorithm clips a polygon against a line (or plane), for each
line (or plane) determining the clipping region.
The algorithm steps from vertex to vertex, adding 0, 1, or 2
vertices to the output list at each step.
Case 2 Case 3 Case 4Case 1
C
Doutsideinside
B
A
C
Doutsideinside
B
A
C
Doutsideinside
B
A
C
Doutsideinside
B
A
C’
D’
Assuming vertex A has already been processed,
Case 1 — vertex B is added to the output
Case 2 — vertex C’ is added to the output (edge BC is clipped)
Case 3 — no vertex added (segment CD clipped)
Case 4 — vertex D’ is added to the output (edge DA is clipped)
Typically, this is coded recursively — the recursive implemen-
tation lends itself to efficient hardware implementation.
The clip region can be any convex polygon.
110
Introduction to modeling
So far, after a brief introduction to OpenGL, we have looked
primarily at the “back end” of a graphics system — the display
devices, and the algorithms used to render the basic primitives
on the display.
Effectively, we have looked at constructing geometric primitives
on a 2-dimensional display.
We have taken as a starting point some data structure contain-
ing sets of vertices, and discussed how to draw the appropriate
primitives associated with those vertices — points, lines, and
convex polygonal surfaces.
We have also looked at how to make the algorithms which render
those primitives efficient, and at how to clip the primitives at
the boundaries of the viewport.
We have not concerned ourselves much with how those prim-
itives are put together to form more complex graphical struc-
tures.
In effect, we have confined ourselves to the 2-dimensional world
of the display, and we want now to look at the 3-dimensional
world in which we will describe the scene to be rendered.
111
Vectors and transformations
Often we want to apply certain transformations to graphical
objects; particularly common transformations are translation,
rotation, scaling, etc. in either 2 or 3 dimensions.
Translation
If we represent graphics primitives using vectors, translation is
easily accomplished by vector addition. The required displace-
ment vector is added to the vertex vectors of the primitives.
Example: Given a triangle with a set of vertex vectors
V = {(2,2), (4,6), (6,2)} and a displacement vector T = (1, 1)
the resultant vertex set for the triangle is V ′ = {(3,3), (5,7), (7,3) }
1 2 3 4 5 6 7 8
12345678
1 2 3 4 5 6 7 8
12345678
Before After
This could be written as V ′ = V + T
112
Scaling
The vectors can be uniformly scaled by simply multiplying each
vector by a scalar constant. Note that the full vector (from the
origin to the point) will be scaled, so the image will change in
both size and position.
1 2 3 4 5 6 7 8
1
2
3
4
5
6
7
8
1 2 3 4 5 6 7 8
1
2
3
4
5
6
7
8
AfterBefore
Scaling by 0.5
A differential scaling is also possible, where the x and y di-
mensions are treated differently. This can be accomplished by
multiplication by a diagonal matrix containing the scale factors.
x′
y′
=
sx 0
0 sy
·
x
y
This would often be written as
V ′ = S · V
where x and y are the components of V.
113
Note that vectors are written as column vectors; the earlier
notation for a vector with its components in parenthesis is also
common, and means the same thing.
The following is the figure seen earlier scaled by the matrix
S =
0.5 0
0 0.75
1 2 3 4 5 6 7 8
12345678
1 2 3 4 5 6 7 8
12345678
AfterBefore
Note that the size, position, and shape of the figure has changed.
114
Rotation
Consider two coordinate systems (x, y) and (x′, y′)
x
x cosy sin
y
x’
y’
θθ
θ
Here x′ = x cos θ − y sin θ
and y′ = x sin θ + y cos θ
In vector form, this is written as:
x′
y′
=
cos θ − sin θ
sin θ cos θ
·
x
y
or V ′ = R(θ) · V
Note that angles are measured in the counterclockwise direction.
The effect of multiple successive rotations can be derived from
the product of the corresponding rotation matrices.
115
The three types of transformations have the form:
Translation V ′ = V + T
Scaling V ′ = S · VRotation V ′ = R · VThe results of successive rotations and scalings can be obtained
by matrix multiplication, but translation is different.
Since we often want to apply the same set of transformations
to many primitives, it would be very useful if we could combine
all the transformations into a single matrix.
Homogeneous coordinates
In fact, using homogeneous coordinateswe can use matrix mul-
tiplication to implement all three of the basic transformations.
In homogeneous coordinates, a third coordinate is added to a
point; point (x, y) is represented as (x, y,W ).
Two homogeneous coordinates represent the same point if they
are multiples of each other. In fact, there are infinitely many
representations of the same point.
If we set W to be 1, (the point would be (x/W, y/W, 1)) then
we have homogenized the point.
116
Now, translation can be performed with matrix multiplication,
so translation by (dx, dy) would be represented as
x′
y′
1
=
1 0 dx
0 1 dy
0 0 1
·
x
y
1
Multiple translations are performed by matrix multiplication.
The transformations for scaling remain the same:
x′
y′
1
=
sx 0 0
0 sy 0
0 0 1
·
x
y
1
The transition matrix for rotation is also unchanged:
x′
y′
1
=
cos θ − sin θ 0
sin θ cos θ 0
0 0 1
·
x
y
1
Successive scalings and rotations can also be implemented with
matrix multiplication.
Remember, though, that if the transformations are mixed, the
order of application of the matrix operators is important. (Ma-
trix multiplication is not commutative — AB 6= BA, in general.)
117
There is another transformation — shearing — which can also
be described by these matrices. Shearing distorts a primitive
by “pushing” it in one direction, as shown:
x directionShearing in the
y directionShearing in the
The matrix for shearing is as follows, where ax and ay are the
factors for shearing in the x and y directions, respectively.
x′
y′
1
=
1 ax 0
ay 1 0
0 0 1
·
x
y
1
The general form for transformations derived from translation,
scaling, rotation, and shearing is:
x′
y′
1
=
r11 r12 tx
r21 r22 ty
0 0 1
·
x
y
1
where the rij correspond to some combination of rotation, scal-
ing, and shearing, and the t’s correspond to translation.
118
Recall again that the order of operations is important in apply-
ing transformations.
For example, if we have a point (or vector) V and wish to apply
translation, then scaling, then rotation, then translation again,
we would perform the operations as T (R(S(T (V ))))
A change in the order of operations may not produce the ex-
pected transformation.
This particular sequence is not uncommon — translate a prim-
itive to the origin, perform scaling and rotation, and translate
it back.
Note that, since the matrix has two zero elements and a con-
stant element (1) on the diagonal, a full matrix multiply is not
necessary.
In fact, the actual operations required are
x′ = r11x + r12y + tx
y′ = r21x + r22y + ty
which is simpler than a 3×3 matrix multiply in that it requires
only 4 multiply and 4 add operations.
119
Three dimensional transformations
All of the transformations we have seen have similar represen-
tations in 3 dimensions.
Translation:
x′
y′
z′
1
=
1 0 0 tx
0 1 0 ty
0 0 1 tz
0 0 0 1
·
x
y
z
1
Scaling:
x′
y′
z′
1
=
sx 0 0 0
0 sy 0 0
0 0 sz 0
0 0 1 1
·
x
y
z
1
Rotation — direct generalization is rotation about the z-axis:
x′
y′
z′
1
=
cos θ − sin θ 0 0
sin θ cos θ 0 0
0 0 1 0
0 0 0 1
·
x
y
z
1
120
Rotation about the x-axis:
x′
y′
z′
1
=
1 0 0 0
0 cos θ − sin θ 0
0 sin θ cos θ 0
0 0 0 1
·
x
y
z
1
Rotation about the y-axis:
x′
y′
z′
1
=
cos θ 0 sin θ 0
0 1 0 0
− sin θ 0 cos θ 0
0 0 0 1
·
x
y
z
1
Any arbitrary 3-dimensional rotation can be constructed from
the three rotation operations Rx, Ry, and Rz.
In fact, all the operations can be combined to give a general
transformation of the form
x′
y′
z′
1
=
r11 r12 r13 tx
r21 r22 r23 ty
r31 r32 r33 tz
0 0 0 1
·
x
y
z
1
121
OpenGL uses homogeneous coordinates in its internal represen-
tation of transformations.
OpenGL has several general purpose transformation functions,
as well as functions specifically for modeling and viewing. The
general purpose functions are:
void glMatrixMode(GLenum mode)
where the mode can be GL_MODELVIEW, GL_PROJECTION, or
GL_TEXTURE. (We will discuss textures later.)
Subsequent transformations affect only the specified matrix (only
one matrix can be modified at a time.)
void glLoadIdentity(void)
sets the current matrix to the identity matrix.
void glLoadMatrix*(const TYPE *matrix)
where *matrix is a pointer to a 16 element matrix of the ap-
propriate type. The function sets a matrix to the given values.
void glMultMatrix*(const TYPE *matrix)
This function multiplies the current matrix by the elements of
*matrix
The later two functions are not frequently used, because OpenGL
also provides easier ways to construct the basic matrices.
122
OpenGL has special functions for viewing and modeling. We
will look at the viewing matrices later; the modeling transfor-
mations are:
void glTranslate*(TYPE x, TYPE y, TYPE z)
which multiplies the current MODELVIEWmatrix by a translation
matrix ( which translates elements by amounts x, y and z).
void glRotate*(TYPE angle, TYPE x, TYPE y, TYPE z)
which multiplies the current matrix by a rotation matrix corre-
sponding to the specified angle (in degrees) about the vector
from the origin through (x,y,z).
void glScale*(TYPE x, TYPE y, TYPE z)
which multiplies the current matrix by the specified scaling ma-
trix.
Note that OpenGL constructs these matrices from their argu-
ments, and successive applications of these functions can be
used to construct quite elaborate transformations.
The tutorial transformation shows the use of the translate,
rotate, and scale transformations.
123
An example illustrating use of rotation matrices
/* Rotating cube, centered at the origin */
/* Demonstration of the use of homogeneous coordinate
* transformations and a simple data structure for
* representing (and animating) a solid figure */
#include <stdlib.h>
#include <GL/glut.h>
/* set up global 2d array of vertices for cube */
GLfloat vertices[][3] = {{-1.0,-1.0,-1.0},
{1.0,-1.0,-1.0}, {1.0,1.0,-1.0}, {-1.0,1.0,-1.0},
{-1.0,-1.0,1.0}, {1.0,-1.0,1.0}, {1.0,1.0,1.0},
{-1.0,1.0,1.0}};
/* define global variables for axis of rotation
* and 1d array of angles wrt. each axis */
static GLint axis = 2;
static GLfloat theta[] = {0.0,0.0,0.0};
124
/* draw a polygon via list of vertices */
void polygon(int a, int b, int c , int d)
{
glBegin(GL_POLYGON);
glColor4f (0.5, 0.0, 0.0,0.2);
glVertex3fv(vertices[a]);
glVertex3fv(vertices[b]);
glVertex3fv(vertices[c]);
glVertex3fv(vertices[d]);
glEnd();
}
void cube(void)
/* map vertices to faces */
{
polygon(0,3,2,1);
polygon(2,3,7,6);
polygon(0,4,7,3);
polygon(1,2,6,5);
polygon(4,5,6,7);
polygon(0,1,5,4);
}
125
void display(void)
{
/* display callback, clear frame buffer and z buffer,
rotate cube and draw, swap buffers */
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
/* set up rotation matrices for rotation about axes */
glRotatef(theta[0], 1.0, 0.0, 0.0);
glRotatef(theta[1], 0.0, 1.0, 0.0);
glRotatef(theta[2], 0.0, 0.0, 1.0);
cube(); /* draw the cube */
glFlush();
glutSwapBuffers();
}
126
void mouse(int btn, int state, int x, int y)
{
/* mouse callback, selects a rotation axis */
if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN)
axis = 0;
if(btn==GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
axis = 1;
if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
axis = 2;
}
void spinCube()
{
/* Idle callback, spin cube 2 degrees about
* selected axis */
theta[axis] += 2.0;
if( theta[axis] > 360.0 ) theta[axis] -= 360.0;
glutPostRedisplay();
}
127
void myReshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-2.0,2.0,-2.0 * (GLfloat) h / (GLfloat) w,
2.0 * (GLfloat) h / (GLfloat) w, -10.0, 10.0);
else
glOrtho(-2.0 * (GLfloat) w / (GLfloat) h,
2.0 * (GLfloat) w / (GLfloat) h, -2.0, 2.0,
-10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
}
128
void main(int argc, char **argv)
{
glutInit(&argc, argv);
/* uses both double buffering and z buffer */
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB |
GLUT_DEPTH);
glutInitWindowSize(500, 500);
glutCreateWindow("rotating cube - mouse changes
rotation axis");
glutReshapeFunc(myReshape);
glClearColor (1.0, 1.0, 1.0, 0.0);
glutDisplayFunc(display);
glutIdleFunc(spinCube);
glutMouseFunc(mouse);
glutMainLoop();
}
129
Changes in coordinate systems
In computer graphics, as in other disciplines (robotics, image
processing, etc.) one of the uses of primitive transformations,
particularly translation and rotation, is to change the coordi-
nate system.
y0x
y1
1
y2
x
x0
2
In this example, coordinate system (x0, y0) would be used for
rotation of the first link of the arm, (x1, y1) would be used for
orienting the second link of the arm, and (x2, y2) would be used
for orienting the final link of the arm.
All of the motions at those joints would eventually have to be
translated into the “world coordinate system” of the device.
The idea of using different coordinate systems depending on the
“point of view” is extremely useful in computer graphics, where
a sub-scene can be described in its own coordinate space, and
then translated and/or rotated to the appropriate place in a
scene.
130
Recall that OpenGL uses homogeneous coordinates to specify
transformations.
The previous example (robot arm) indicated the potential use
of transformations in describing the motion of the arm; in fact,
if the two links shown are identical, then only one instance need
be created in its own “local” coordinate system, and it could be
translated, rotated, (and possibly scaled) to several instances in
the displayed picture.
Such transformations in the “model world” are called modeling
transformations and are extremely useful in model creation.
Suppose, for example, we wanted to take a primitive (say, the
second joint of the robot arm) and rotate it by 45◦, as shown:
0 1 2 3 4 5 6 7 8 9
01
34
2
This could be accomplished by translating the arm so the pivot
at (5, 0) was at the origin, rotating the arm 45◦, and translating
it back.
131
The required operations are:
1. Translate the arm with link from (5, 0) to (0, 0)
2. Rotate the arm 45◦ about (0, 0)
3. Translate the now rotated arm with link from (0, 0) to (5, 0)
The following OpenGL snippet would accomplish this (note the
order of the functions — most recently specified is the first
applied):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity();
glTranslatef(5.0, 0.0, 0.0)
glRotatef(45.0, 0.0, 0.0, 1.0)
glTranslatef(-5.0, 0.0, 0.0)
The functions glTranslate*(x,y,z) behave much as expected,
causing a translation of amounts x, y, and z.
The functions glRotate*(a,x,y,z) rotates an object in a
counterclockwise direction by an angle a measured in degrees
about a vector from the origin to the point (x, y, z). The
example shows a rotation about the z-axis.
132
Note that the previous example, the arms were modeled in three
dimensions.
It is rather tedious to specify a complex 3-dimensional object
using OpenGL primitives directly, because of the small number
of primitives, and because they specify surfaces only. In general,
in 3 dimensions, both a surface “patch” must be specified, and
a normal to the surface.
The function glNormal*(x,y,z) sets a value for a normal
vector; this normal is used for every subsequent vertex until the
function is called again. (Normals are assigned to vertices.)
Normal vectors should have unit length.
Normal vectors are used when surfaces are illuminated, and if
an illumination model is not used, normals are not necessary.
We will discuss this in much more detail later.
Even without normals, it is tedious to construct a figure us-
ing surface patches only. Usually, the vertex array form of the
OpenGL functions are used for complex figures.
133
The following array contains the data required for the normals
for the rotating cube example:
GLfloat normals[][3] = {{-1.0,-1.0,-1.0},{1.0,-1.0,-1.0},
{1.0,1.0,-1.0}, {-1.0,1.0,-1.0}, {-1.0,-1.0,1.0},
{1.0,-1.0,1.0}, {1.0,1.0,1.0}, {-1.0,1.0,1.0}};
The code for constructing the polygons would now contain def-
initions of the normals to each surface as calls of the function
glNormal*()
glBegin(GL_POLYGON);
glNormal3fv(normals[a]);
glVertex3fv(vertices[a]);
glNormal3fv(normals[b]);
glVertex3fv(vertices[b]);
glNormal3fv(normals[c]);
glVertex3fv(vertices[c]);
glNormal3fv(normals[d]);
glVertex3fv(vertices[d]);
glEnd();
134
More OpenGL
We will look at the process of using a set of simple geometric
figures to build a more complex figure, but first we will look at
a few more features of OpenGL.
When using local coordinates to construct a component of a
model, the current model view matrix must often be replaced
temporarily by a local model view matrix.
OpenGL has a stack feature which allows several levels of model
view matrices to be pushed onto the stack, and popped off later.
The commands are:
glPushMatrix() glPopMatrix()
The projection matrix can also be pushed and popped.
In OpenGL, the default viewport is the entire pixel area of the
window that is opened. The command
glViewport(Glint x, Glint y, GLsizei w, GLsizei h)
can be used to change the size of the viewport. This is useful
to display several views of the same object in one window.
Remember that the model coordinate space is right
handed, and the viewing space is left handed.
135
Polygons have two sides — front and back. They may be ren-
dered differently, depending on which side faces the viewer.
Polygons whose vertices appear in counterclockwise order on
the screen are front-facing.
By default, both front and back faces are drawn the same way,
but this can be changed with glPolygonMode(face, mode).
face can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK,
mode can be GL_POINT, GL_LINE, or GL_FILL,
Polygons can be culled, or discarded, with glCullFace(face)
where face is as defined above. Culling must be enabled with
glEnable(GL_CULL_FACE). (There are about 40 possible ar-
guments for glEnable(). There is also a glDisable().)
Reminder: At the time a polygon is created, the normals
should also be calculated. (Although they are not used now,
they will be important when we consider lighting models.)
136
The glu library contains functions for creating quadric surfaces
— spheres, cylinders, disks, etc.
Some primitives are the following (*qobj is a pointer to a
quadric object created by gluNewQuadric()):
gluSphere(*qobj, GLdouble radius, GLint slices, GLint
stacks) draws a sphere of given radius centered on the ori-
gin, with a number of slices around the z-axis, and stacks
along the z-axis.
gluCylinder(*qobj, GLdouble baseRadius, GLdouble topRadius,
GLdouble height, GLint slices, GLint stacks) draws
a cylinder along the z-axis with the base at z = 0 and the top
at z = height.
The quadric surfaces will be discussed in more detail later.
The glu library has a function which reverses a projection,
given a point and all the transformations which affected it. It
takes window coordinates and returns world coordinates.
gluUnProject( GLdouble winx, GLdouble winy, GLdouble
winz, const GLdouble modelMatrix[16], const GLdouble
projMatrix[16], const GLint viewport[4], GLdouble
*objx, GLdouble *objy, GLdouble *objz)
137
The GLUT library has functions to render some geometric solids,
and wireframe (outline) solids as well:
glutSolidSphere(GLdouble radius, GLint slices, GLint
stacks)
and glutWireSphere()
glutSolidCube(GLdouble size)
and glutWireCube()
glutSolidCone(GLdouble radius, GLdouble height, GLint
slices, GLint stacks)
and glutWireCone()
The GLUT library also has functions to create pop-up menus.
These are useful for choice type interaction.
The basic functions are
glutCreateMenu(menu_callback(int id))
which defines the menu (and the callback which implements the
menu functions — id is an identifier for the menu item),
glutAddMenuEntry(char* name, int id)
and glutAttachMenu(int button)
which attaches the menu function to some button.
It is also possible to create submenus. (See the glut documen-
tation.)
138
An example illustrating use of the model view
(From the reference text Interactive Computer Graphics: A
Top-Down Approach with OpenGL, by Edward Angel)
/* Robot program. Cylinder for base, scaled cube for arms*/
/* Uses instance transformation to define parts (symbols)*/
/* The cylinder is a quadric object from the GLU library */
/* The cube is also obtained from GLU */
#include <GL/glut.h>
/* Define the constants (easier to change them) */
#define BASE_HEIGHT 2.0
#define BASE_RADIUS 1.0
#define LOWER_ARM_HEIGHT 5.0
#define LOWER_ARM_WIDTH 0.5
#define UPPER_ARM_HEIGHT 5.0
#define UPPER_ARM_WIDTH 0.5
typedef float point[3];
static GLfloat theta[] = {0.0,0.0,0.0};
static GLint axis = 0;
GLUquadricObj *p; /* pointer to quadric object */
139
/* Define the three parts */
/* Note use of push/pop to return modelview matrix
to its state before functions were entered and use
rotation, translation, and scaling to create instances
of symbols (cube and cylinder) */
void base()
{
glPushMatrix();
/* rotate cylinder to align with y axis */
glRotatef(-90.0, 1.0, 0.0, 0.0);
/* cylinder aligned with z axis, render with
5 slices for base and 5 along length */
gluCylinder(p, BASE_RADIUS, BASE_RADIUS,
BASE_HEIGHT, 5, 5);
glPopMatrix();
}
140
void upper_arm()
{
glPushMatrix();
glTranslatef(0.0, 0.5*UPPER_ARM_HEIGHT, 0.0);
glScalef(UPPER_ARM_WIDTH, UPPER_ARM_HEIGHT,
UPPER_ARM_WIDTH);
glutWireCube(1.0);
glPopMatrix();
}
void lower_arm()
{
glPushMatrix();
glTranslatef(0.0, 0.5*LOWER_ARM_HEIGHT, 0.0);
glScalef(LOWER_ARM_WIDTH, LOWER_ARM_HEIGHT,
LOWER_ARM_WIDTH);
glutWireCube(1.0);
glPopMatrix();
}
141
void display(void)
{
/* Develop ModelView Matrix as we traverse structure */
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
glColor3f(1.0, 0.0, 0.0);
glRotatef(theta[0], 0.0, 1.0, 0.0);
base();
glTranslatef(0.0, BASE_HEIGHT, 0.0);
glRotatef(theta[1], 0.0, 0.0, 1.0);
lower_arm();
glTranslatef(0.0, LOWER_ARM_HEIGHT, 0.0);
glRotatef(theta[2], 0.0, 0.0, 1.0);
upper_arm();
glFlush();
glutSwapBuffers();
}
142
void mouse(int btn, int state, int x, int y)
{
/* left button increase joint angle, right decreases */
if(btn==GLUT_LEFT_BUTTON && state == GLUT_DOWN)
{
theta[axis] += 5.0;
if( theta[axis] > 360.0 ) theta[axis] -= 360.0;
}
if(btn==GLUT_RIGHT_BUTTON && state == GLUT_DOWN)
{
theta[axis] -= 5.0;
if( theta[axis] < 360.0 ) theta[axis] += 360.0;
}
display();
}
void menu(int id)
{
/* menu selects rotation axis or whether to quit*/
if(id == 1 ) axis=0;
if(id == 2) axis=1;
if(id == 3 ) axis=2;
if(id ==4 ) exit();
}
143
void myReshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-10.0,10.0, -5.0 * (GLfloat)h / (GLfloat)w,
15.0 * (GLfloat) h / (GLfloat) w, -10.0, 10.0);
else
glOrtho(-10.0 * (GLfloat)w / (GLfloat)h,
10.0 * (GLfloat)w / (GLfloat)h, -5.0,
15.0, -10.0, 10.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
void myinit()
{
glClearColor(1.0, 1.0, 1.0, 1.0);
glColor3f(1.0, 0.0, 0.0);
p=gluNewQuadric(); /* allocate quadric object */
gluQuadricDrawStyle(p, GLU_LINE); /* draw as wireframe*/
}
144
void
main(int argc, char **argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB
| GLUT_DEPTH);
glutInitWindowSize(500, 500);
glutCreateWindow("robot");
myinit();
glutReshapeFunc(myReshape);
glutDisplayFunc(display);
glutMouseFunc(mouse);
glutCreateMenu(menu);
glutAddMenuEntry("base", 1);
glutAddMenuEntry("lower arm", 2);
glutAddMenuEntry("upper arm", 3);
glutAddMenuEntry("quit", 4);
glutAttachMenu(GLUT_MIDDLE_BUTTON);
glutMainLoop();
}
145
In the previous example, the construction of the robot arm was
a simple hierarchy.
Other structures can be used just as effectively. For example, a
tree structure might be a good representation for a more com-
plex structure.
A robot with two arms might be constructed as a trunk, with
two jointed arms attached, and with a set of fingers.
Finger FingerFingerFinger
LowerArm
ArmUpper
ArmUpper
ArmLower
TRUNK
See the program figuretree.c for a similar example.
146
Projections and their transformations
We have looked a little bit at modeling 3-dimensional figures in
a 3-dimensional world, or view. The final image, however, will
be displayed on a 2-dimensional surface.
The transformation from the 3-dimensional model space to the
2-dimensional viewing space is accomplished by projecting the
model onto a plane.
Here is where our “virtual camera” model offers a good analogy.
scene
view planecamera
We are interested in the projection of the scene unto the view-
plane. This is often called the viewing transformation, and is
analogous to setting the position of the camera (and the type
of lens, etc.)
Things are really a bit different, however, because with com-
puter graphics we have rather more freedom than we have with
a camera.
147
For example, we can define a number of different kinds of pro-
jections. (Very many kinds of projections have been defined,
particularly by mapmakers, since they must project inherently
3-dimensional surfaces on a 2-dimensional surface, and preserve
some sets of useful information.)
There are two basic projections we will discuss — parallel pro-
jections and perspective projections.
Parallel projections
The basic property of a parallel projection is that features are
projected by parallel rays unto the viewing surface.
There are again two types of parallel projections — projections
in which the projection is parallel to the normal of the view-
ing surface orthographic projections and projections where the
projection rays are not normal to the surface oblique projec-
tions.
The most common types of orthographic projections are “archi-
tectural projections” (usually just called orthographic projec-
tions) where the projections are parallel to the principal axes.
Normally, it would be annotated with measurements, etc.
With some practice, it is not difficult to capture all the construc-
tion details of a house, for example, with this type of projection.
148
The following shows two types of orthographic projections —
an isometric projection and an equivalent orthonormal (archi-
tectural) projection:
Front view
Side view
Top view
The isometric projection is a projection in which the object
is at the same angle to all three principal axes. (This has the
property that all 3 axes can be measured on the same scale.)
It is a special case of an axonometric orthographic projection.
The center drawing of the house above is an example of this.
149
Oblique projections
Oblique projections are the most general parallel views. In an
oblique projection, the projectors make some arbitrary angle
with the projection plane.
These views are somewhat unnatural, but can be used to convey
more information than orthographic projections.
The human eye, and most other physical viewing devices, have a
lens fixed with respect to (and usually parallel to) the viewing
plane. This is, in fact, what makes oblique projections seem
unnatural.
The old “press cameras” often had a feature where the film plane
could be tilted relative to the lens. This allowed an oblique
projection, albeit not a parallel projection.
From the point of view of a graphics system, as long as the pro-
jection can be described using primitive transformations, there
is little difference among all the parallel projections.
The primary consideration is how to specify the appropriate
parameters to the graphics system to produce the required view.
150
Perspective projections
In a perspective projection, the projecting rays are not parallel,
but instead pass through one or more vanishing points or points
at infinity.
This is a (somewhat extreme) example of one point perspective.
Note that the lines which should be parallel actually converge
in the “far distance.” This creates an illusion of depth in a
2-dimensional figure.
Two point perspective would use 2 vanishing points, roughly
orthogonal to each other in the “model world.”
In a perspective projection, we need to specify a point as the
center of projection; for parallel projections we need to specify
a direction of projection.
151
The viewing surface
The plane onto which the image is projected is usually called
the view plane. It has coordinates (u, v, n) orthogonal to each
other.
It is defined by the view reference point or VRP, and the view
plane normal or VPN and points along the n axis.
The view plane corresponds to the “film” in our virtual camera.
We also need to describe the orientation of the “film” or view-
port in the view plane. This is done by specifying a vector called
the view up vector (VUP). The projection of the VUP vector
parallel to VPN in the plane defines the v axis in the plane.
This, in effect, orients the camera. The u axis is perpendicular
to v and n.
Defining minimum and maximum values of u and v determines
the viewing area.
VRP
VPNn
u
v
CW
VUP
viewplane
min min(u ,v )
max max(u ,v )
152
In many graphics systems, there are functions which set the
viewing parameters explicitly, using functions like:
set_view_reference_point(x, y, z)
set_view_plane_normal(nx, ny, nz)
set_view_up(vx, vy, vz)
In OpenGL, there is a convenient function which is rather more
direct, and uses the virtual camera model explicitly. Basically,
the position of the camera (or eye), and the position of the
object are specified, These specify the VPN and a VRP. In
fact, these are specified in the GL ModelView transformation.
The orientation of the camera also needs to be specified (VUP).
gluLookAt(eyex, eyey, eyez, atx, aty, atz, upx, upy,
upz)
eyex, eyey, and eyez specify the camera (or eye) position,
atx, aty, and atz specify the object center.
upx, upy, and upz specify VUP.
The glu.. functions are not OpenGL primitives, but are part
of a standard library which forms part of the OpenGL system.
(GLUT is another library which provides user interaction.)
The tutorial projection shows the use of the projection func-
tions for orthogonal and perspective transformations.
153
Clipping
Viewing involves both projection and clipping. In three dimen-
sions, we must clip against a 3-dimensional view volume.
For the parallel projection case, the additions to the 2-dimensional
clipping we discussed earlier are a front clipping plane and a
back clipping plane, forming a clipping rectangular volume.
Backclipping
planeplaneclipping
Front
planeView
VPN
The perspective projection case is a bit different. Here, the front
and back clipping planes are parallel to the view plane, but are
of different sizes.
Back
planeclipping
Front
planeView
VPN
planeclipping
The shape of the bounding figure in this case is a frustum. This
is a pyramid with the point cut off.
154
Normally, the clipping would be done on a figure transformed
so the VPN lies along the Z axis.
Parallel projections in OpenGL
In OpenGL, there is only one parallel viewing function, the
orthographic viewing function
glOrtho(xmin, xmax, ymin, ymax, zmin, zmax), or
glOrtho(left, right, bottom, top, near, far)
where (xmin, ymin, zmin) are the coordinates of the lower front
corner of the rectangular volume, and (xmax, ymax, zmax) are
the coordinates of the back top corner.
(We have already used this function in our 2-dimensional graph-
ics examples.)
For a view volume consisting of a 2x2x2 cube centered at the
origin, the function calls would be
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);
155
Perspective projections in OpenGL
There are two functions for perspective projections.
glFrustum(left, right, bottom, top, near, far)
defines a frustum similar to the rectangular volume for glOrtho(..).
Note that near and far should be positive.
lefttop
bottom
right
near
far
A typical use would be
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glFrustum(-1.0, 1.0, -1.0, 1.0, 1.0, 10.0);
Here the front clipping plane is (-1, -1, 1) to (1, 1, 1). The rear
clipping plane is 9 units beyond the front clipping plane.
While conceptually easy, glFrustum() is difficult to use.
See the tutorial projection for an example of its use.
156
An alternate way to determine the view volume is by specifying
an angular field (in the y direction, in this case), an aspect ratio
(width/height) and the position of the front and back clipping
planes.
This is not an OpenGL primitive, but is available in the glu
library.
gluPerspective(fovy, aspect, near, far)
near
far
fovy h
w
With this function, it is necessary to pick appropriate values for
the field of view, or the view looks distorted.
With both gluPerspective() and glFrustum() rotations
and translations can be applied to change the default orientation
of the viewing volume.
Without such transformations, the viewpoint remains at the
origin, with the line of sight along the negative z-axis.
157
Simple orthogonal projection
Let us consider a projection onto a plane perpendicular to the
z-axis, at z = 0.
in this case, points retain their x and y values, and the equations
of projection are
xp = x, yp = y, and zp = 0,
In homogeneous coordinates,
xp
yp
zp
1
=
1 0 0 0
0 1 0 0
0 0 0 0
0 0 0 1
·
x
y
z
1
We can use the transformation matrices to change the projec-
tion matrix to whatever direction we wish.
Alternately, we can orient the image, scale it, perform clipping,
and apply this projection, then transform it again.
158
Simple perspective projection
We will again consider the case where the projection is on a
plane on the z-axis, at a point (0, 0, zp)
y
z
x
(x,z)
(a) (b)
x
z
p(x ,−d)(x,y,z)
z = −d (x ,y ,z )pp p
Here, figure (b) is an orthogonal projection of (a) looking di-
rectly along the y axis.
By similar triangles, x/z = xp/d
Similarly, y/z = yp/d
So, xp =x
z/dand yp =
y
z/d
This is not an affine transformation, nor is it reversible — all
points along a projector map into the same point.
(We will find another transformation later which is reversible.)
159
It is possible, however, to find a matrix which accomplishes
the perspective projection on the z-axis. We must relax the
condition that W = 1 in our homogeneous coordinate system,
however.
In order to get a point, we must “renormalize” our point repre-
sentation to set W = 1 again.
The transformation matrix
M =
1 0 0 0
0 1 0 0
0 0 −1 0
0 0 1/d 0
Transforms the point (x, y, z, 1) into (x, y,−z, z/d)
Renormalizing, this is
(x
z/d,
y
z/d,−d, 1)
which is the required transformation.
Again, it is possible to transform this matrix to project in an
arbitrary direction using the rotation transformations discussed
earlier.
160
Clipping in homogeneous coordinates
It is important for efficiency not to have to change the repre-
sentation very often, since the view transformations are applied
to each vertex in the image.
Therefore, it would be useful to
1. have a single “combined transformation”
2. clip in a standard, or canonical view volume.
This would be possible if we could transform a perspective pro-
jection into a parallel projection.
In fact, such a perspective-normalization matrix is derivable:
M =
1 0 0 0
0 1 0 0
0 0 11+zmin
zmin1+zmin
0 0 −1 0
0 > zmin > −1
Note that this matrix similar in form to the perspective trans-
formation matrix but is is non-singular.
This transforms a perspective mapping into an orthogonal map-
ping, so that clipping can be done inside a canonical volume.
161
How is a projection matrix constructed?
The following is general, and allows oblique projections:
For parallel projections:
1. Translate the VRP to the origin.
2. Rotate VRC such that the n axis (VPN) maps to the z
axis, the u axis becomes the x axis, and the v axis becomes
the y axis.
3. Shear so the direction of projection is parallel to the z axis.
4. Translate into the canonical view volume.
5. Scale to fit inside the canonical volume.
Clipping can then be applied on the canonical volume.
Each of those steps is characterized by a transformation matrix,
and the required transformation is the product of all of those
simple transformation matrices.
This transformation is often called a normalizing transforma-
tion, and the set of points output (after clipping) are said to be
in normalized device coordinates (NDC).
The NDC are a device independent representation which are
then transformed into the actual points on the display device.
162
For perspective projections:
1. Translate the VRP to the origin.
2. Rotate VRC such that the n axis (VPN) maps to the z
axis, the u axis becomes the x axis, and the v axis becomes
the y axis.
3. Translate so the center of projection (COP) given by the
projection reference point (PRP) is at the origin.
4. Shear so the center line of the view volume is parallel to
the z axis.
5. Scale to fit inside the canonical view volume.
6. Apply the perspective normalization transformation.
Again, each step is characterized by a transformation matrix,
and the required transformation is the product of all of those
simple transformation matrices.
Following the application of this transformation to each vertex,
clipping can be done against the canonical volume for parallel
projections.
163
The viewing pipeline
What happens, in OpenGL, in order to view an image?
eye
Normalizeddevice
coordinates
coordinates
PerspectiveDivision
(Renormalization)
MatrixProjection
coordinatesclip
object
coordinates
coordinates
Window
ModelView
Matrix
TransformationViewport
The objects themselves are described in their own coordinate
space, and placed in the global model world by the appropriate
model view transformation.
The projection matrix maps the model world into the canonical
clipping volume (this transformation is not affine) and then the
points are renormalized, and transformed for display.
Following are some OpenGL examples of the use of transforma-
tions for modeling and viewing.
164
An example illustrating use of both modeling and
viewing transformations
/* planet.c
* Copyright (c) 1993-1997, Silicon Graphics, Inc.
* ALL RIGHTS RESERVED
*
* Program shows composition of modeling transformations
* to draw translated and rotated models.
* Interaction: pressing the d and y keys (day and year)
* alters the rotation of the planet around the sun.
* Modified to change the y viewing height with v and V.
*/
#include <GL/glut.h>
#include <stdlib.h>
static int year = 0, day = 0;
static float viewy = 0.0;
void init(void)
{
glClearColor (1.0, 1.0, 1.0, 0.0);
glShadeModel (GL_FLAT);
}
165
void display(void)
{
glClear (GL_COLOR_BUFFER_BIT);
glColor3f (0.0, 0.0, 0.0);
glPushMatrix();
glutWireSphere(1.0, 20, 16); /* draw sun */
glRotatef ((GLfloat) year, 0.0, 1.0, 0.0);
glTranslatef (2.0, 0.0, 0.0);
glRotatef ((GLfloat) day, 0.0, 1.0, 0.0);
glutWireSphere(0.2, 10, 8); /* draw planet */
glPopMatrix();
glutSwapBuffers();
}
166
void keyboard (unsigned char key, int x, int y)
{
switch (key) {
case ’d’:
day = (day + 10) % 360;
glutPostRedisplay();
break;
case ’D’:
day = (day - 10) % 360;
glutPostRedisplay();
break;
case ’y’:
year = (year + 5) % 360;
glutPostRedisplay();
break;
case ’Y’:
year = (year - 5) % 360;
glutPostRedisplay();
break;
case ’v’:
viewy = (viewy + 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
167
gluLookAt (0.0, viewy, 5.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
glutPostRedisplay();
break;
case ’V’:
viewy = (viewy - 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt (0.0, viewy, 5.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0);
glutPostRedisplay();
break;
case 27:
exit(0);
break;
default:
break;
}
}
168
void reshape (int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective(60.0, (GLfloat) w/(GLfloat) h, 1.0,
20.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt (0.0, viewy, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0,
0.0);
}
169
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize (500, 500);
glutInitWindowPosition (100, 100);
glutCreateWindow (argv[0]);
init ();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMainLoop();
return 0;
}
170
A second example illustrating use of both modeling
and viewing transformations
The program scene.c shows the use of three glut functions
(the cone, torus, and sphere) to construct a simple scene.
It also allows the viewer to interact with the scene to, for ex-
ample, change the view position.
#include <GL/glut.h>
#include <stdlib.h>
static float viewx = 0.0;
int axes = 0, cone = 1, torus = 1, sphere = 1;
static void init (void)
{
// select clearing color
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
}
171
static void display (void)
{
glClear (GL_COLOR_BUFFER_BIT);
if (axes)
{
// draw axes
glBegin(GL_LINES);
glColor3f (1.0, 0.0, 0.0);
glVertex3f(-2.0, 0.0, 0.0);
glVertex3f( 2.0, 0.0, 0.0);
glColor3f (0.0, 1.0, 0.0);
glVertex3f( 0.0,-2.0, 0.0);
glVertex3f( 0.0, 2.0, 0.0);
glColor3f (0.0, 0.0, 1.0);
glVertex3f( 0.0, 0.0,-2.0);
glVertex3f( 0.0, 0.0, 2.0);
glEnd();
}
172
if (torus) // default torus center is on Z axis
{
glPushMatrix ();
glColor3f (1.0, 0.0, 0.0);
glTranslatef (0.0, 0.5, 0.0);
glRotatef (90.0, 1.0, 0.0, 0.0);
glutWireTorus (0.275, 0.85, 30, 30);
glPopMatrix ();
}
if (cone) // default cone is along Z axis
{
glColor3f (0.0, 1.0, 0.0);
glPushMatrix ();
glTranslatef (0.0, -0.5, 0.0);
glRotatef (270.0, 1.0, 0.0, 0.0);
glutWireCone (1.0, 2.0, 30, 30);
glPopMatrix ();
}
173
if (sphere) // default sphere is at origin
{
glColor3f (0.0, 0.0, 1.0);
glPushMatrix ();
glTranslatef (0.75, 0.0, -1.0);
glutWireSphere (1.0, 30, 30);
glPopMatrix ();
}
glutSwapBuffers();
}
static void reshape(int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity();
gluPerspective(60.0,(GLfloat) w/(GLfloat) h,1.0,20.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt (viewx,0.0,4.5,0.0,0.0,0.0,0.0,1.0,0.0);
}
174
static void
key(unsigned char k, int x, int y)
{
switch (k) {
case ’a’:
case ’A’:
axes = !axes;
break;
case ’s’:
case ’S’:
sphere = !sphere;
break;
case ’t’:
case ’T’:
torus = !torus;
break;
case ’c’:
case ’C’:
cone = !cone;
break;
175
case ’v’:
viewx = (viewx + 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt (viewx,0.0,4.5,0.0,0.0,0.0,0.0,1.0,0.0);
break;
case ’V’:
viewx = (viewx - 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt (viewx,0.0,4.5,0.0,0.0,0.0,0.0,1.0,0.0);
break;
case 27: /* Escape */
exit(0);
break;
default:
return;
}
glutPostRedisplay();
}
176
/* Main Loop
* Open window with initial window size, title bar,
* RGBA display mode, and handle input events.
*/
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize (500, 500);
glutCreateWindow (argv[0]);
init ();
glutReshapeFunc (reshape);
glutDisplayFunc(display);
glutKeyboardFunc(key);
glutMainLoop();
return 0; // ANSI C requires main to return int.
}
177
Color
One of the primary advantages of raster graphics devices is the
general availability of color. The present display technologies
(CRT and LCD screens) are capable of a color resolution which
is a very good match to the range of colors visible to the human
eye.
Color printers with good resolution are also available at rela-
tively low cost (color inkjet printers) and other color devices
(laser and dye diffusion printers) are rapidly becoming cheaper
and better.
We have only discussed color very briefly, in the context of 2-D
graphics, and some limited 3-D applications.
OpenGL has two color modes — the RGBA mode we have
already seen briefly, and the color index mode.
In the RGBA mode, a color is specified by three intensities
(for the Red, Green, and Blue components of the color) and
optionally a fourth value, Alpha, which is the transparency.
The function glColor4f(red, green, blue, alpha)
maps available red, green, and blue intensities onto (0.0, 1.0)
where 0.0 means that the color component is absent ((0.0, 0.0,
0.0) is black) and 1.0 is a saturated color ((1.0, 1.0, 1.0) is
white.)
178
The number of bits used to represent each color depends on the
particular computer system (actually, the “graphics board” in
the system.)
Currently, graphics boards usually have several Mbytes of mem-
ory, and using 24 or 32 bits for color (8 to 10 bits per color, R,
G, or B) is common. (This allows 16M to 1G individual colors
to be displayed.)
Not long ago, 8 to 16 bits of color was usual, permitting only
256 to 64K individual colors. In order to increase the range
of colors displayed, a color lookup table was often used. In
the table, colors would be represented by, say, 24 bits, and the
“color” specified by the color word (say, n bits — 8 to 12) would
be an index to this table.
Only 2n colors could be displayed simultaneously, but the color
table could be altered to give a different palate of colors for each
application.
OpenGL also supports this mode, called color index mode.
The command glIndexf(cindex) sets the current color index
to cindex.
Analogous to glClearColor() there is a corresponding
glClearIndex(clearindex) which sets the clearing color to
clearindex.
179
OpenGL has no function to set a color lookup table, but GLUT
has the function
glutSetColor(GLint index, GLfloat red, GLfloat green,
GLfloat blue) which sets the color at index to the intensities
specified by red, green, and blue.
The function getIntegerv() with arguments GL_RED_BITS,
GL_GREEN_BITS, GL_BLUE_BITS, GL_ALPHA_BITS, and
GL_INDEX_BITS returns the number of bits for the identified
entity.
Color-index mode does not support transparency.
Choosing between color modes
To a large extent, this depends on the particular system you are
using. In general, RGBA mode provides more flexibility, and is
well supported by current hardware.
Color index mode may be useful when:
• porting or modifying an existing program using color-index
mode
• only a small number of bitplanes are available (e.g., 8 bits
— possibly 3 for red, 3 for green, and 2 for blue, giving a
very small range for each color)
• drawing layers and color-map animation are used.
180
Shading models
OpenGL has two shading models; primitives are drawn with a
single color (flat shading) or with a smooth color variation from
vertex to vertex (smooth shading, or Gouraud shading).
The shading mode is specified with glShadeModel(mode)where
mode is either GL_SMOOTH or GL_FLAT.
In RGBA mode, the color variation is obtained by interpolating
the RGB values in both directions, (bilinear interpolation) and
is as smooth as possible with the given number of bit planes.
The interpolation is on each of the R, G, and B components,
and is in the horizontal and vertical directions.
In color index mode, the index values are interpolated, so the
color table must be loaded with a set of smoothly changing
colors.
For flat shading, the color of a single vertex determines the color
of the polygon.
For the situation where lighting elements are present, a more
elaborate shading algorithm can be used. (This will be discussed
later.)
181
The rotating cube example shows an interesting example of
smooth shading.
An array of colors can be defined for the vertices, and used
much like the vertex coordinates (the program cubev uses this
code.)
glBegin(GL_POLYGON);
glColor3fv(colors[a]);
glNormal3fv(normals[a]);
glVertex3fv(vertices[a]);
glColor3fv(colors[b]);
glNormal3fv(normals[b]);
glVertex3fv(vertices[b]);
glColor3fv(colors[c]);
glNormal3fv(normals[c]);
glVertex3fv(vertices[c]);
glColor3fv(colors[d]);
glNormal3fv(normals[d]);
glVertex3fv(vertices[d]);
glEnd();
182
Transparency
So far, we have ignored the alpha term.
The natural interpretation of alpha is opacity. When alpha
is large, the polygon is opaque; when small, it is translucent.
For alpha to have any meaning, blending must be set.
This is done with glEnable(GL_BLEND)
Next, the way the blending is to be done is specified. Basically,
the colors of the incoming polygon (the source) are combined
with the currently stored pixel value (the destination) to give a
resultant pixel color as follows:
(RsSr +RdDr, GsSg +GdDg, BsSb +BdDb, AsSa + AdDa)
(Sr, Sg, Sb) and (Dr, Dg, Db) are blending factors for the source
and destination red, green, and blue components, respectively.
They have values between 0 and 1, and the resulting pixel values
are clamped to [0,1].
The blending factors are set with the function
glBlendFunc(GLenum sfactor, GLenum dfactor)
The following table gives values for sfactor and dfactor:
183
Constant factor blend factor
GL_ZERO s or d (0, 0, 0, 0)
GL_ONE s or d (1, 1, 1, 1)
GL_DST_COLOR s (Rd, Gd, Bd, Ad)
GL_SRC_COLOR d (Rs, Gs, Bs, As)
GL_ONE_MINUS_DST_COLOR s (1, 1, 1, 1)− (Rd, Gd, Bd, Ad)
GL_ONE_MINUS_SRC_COLOR d (1, 1, 1, 1)− (Rs, Gs, Bs, As)
GL_SRC_ALPHA s or d (As, As, As, As)
GL_DST_ALPHA s or d (Ad, Ad, Ad, Ad)
GL_ONE_MINUS_SRC_ALPHA s or d (1, 1, 1, 1)− (As, As, As, As)
GL_ONE_MINUS_DST_ALPHA s or d (1, 1, 1, 1)− (Ad, Ad, Ad, Ad)
GL_SRC_ALPHA_SATURATE s (f, f, f, 1),
f = min(As, 1− Ad)
Blending can be disabled with glDisable(GL_BLEND)
The same effect can be achieved by using GL_ONE for the source
and GL_ZERO for the destination.
(In fact, this is the default setting when blending is enabled!)
184
Sample Uses of Blending:
• To draw a picture composed half of one image and half
of another, equally blended, first set the source factor to
GL_ONE and the destination factor to GL_ZERO, and draw
the first image. Then set the source factor to GL_SRC_ALPHA
and destination factor to GL_ONE_MINUS_SRC_ALPHA, and
draw the second image with alpha equal to 0.5.
This probably is the most common blending operation.
• The blending functions using the source or destination col-
ors GL_DST_COLOR or GL_ONE_MINUS_DST_COLOR for the
source factor and GL_SRC_COLOR or GL_ONE_MINUS_SRC_COLOR
for the destination factor effectively allow modification of
each color component individually.
• Suppose we wish to draw a picture with three translucent,
overlapping surfaces, covering a solid background. Assume
the farthest surface transmits 80 % of the color behind it,
the next transmits 50 %, and the closest transmits 90 %.
First draw the solid background, then set the blending fac-
tors to GL_SRC_ALPHA (source) and GL_ONE_MINUS_SRC_ALPHA
(destination). Next, draw the farthest surface with an
alpha of 0.2, then the middle surface with an alpha of
0.5, and finally the closest surface with an alpha of 0.1.
185
The program alpha.c shows a simple application of blending.
Two intersecting triangle are drawn, and the overlap is blended.
The pertinent code for blending is:
static void init(void)
{
glEnable (GL_BLEND);
glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glShadeModel (GL_FLAT);
glClearColor (0.0, 0.0, 0.0, 0.0);
}
Note that only the state of the system is changed; the drawing
functions are used normally.
The code example alpha toggles which of two overlapping tri-
angles is on top.
186
The code to draw one triangle is:
static void drawLeftTriangle(void)
{
/* draw yellow triangle on LHS of screen */
glBegin (GL_TRIANGLES);
glColor4f(1.0, 1.0, 0.0, 0.75);
glVertex3f(0.1, 0.9, 0.0);
glVertex3f(0.1, 0.1, 0.0);
glVertex3f(0.7, 0.5, 0.0);
glEnd();
}
Similar code draws the right (cyan) triangle.
Other effects can be obtained with different blending modes.
187
Hierarchical modeling
OpenGL provides a basic set of primitives for rendering and
displaying 2-D and 3-D graphical output. Standard libraries
(GLU and GLUT, for example) provide additional functionality
including more complex geometrical models, and interaction
with the underlying windowing system.
Generally, a graphics package is used in some application do-
main (e.g., logic circuit layout) in which the graphical entities
have a meaning independent of their graphical properties, and
the various graphical entities may have relations among them
which have graphical or other manifestations.
Different applications may well represent information graphi-
cally, but the underlying graphics system should not be con-
cerned with what the various graphical entities represent in the
applications domain.
For example, in an organizational chart, some relations may be
inferred from the connectivity of components; others by color or
other attributes. (E.g., in the following organizational chart, the
management hierarchy may be inferred from the connectivity
of the graph, the job class by the color.)
Note that the application, not the graphical representation, is
concerned with the meaning of the graphics primitives.
188
VP R&D
President
Manager(Sales)(Accounting)
Manager
VP sales
ManagerDevelopment
EngineeringStaff Accountants Sales staff
Note that there is no inherent meaning in the graphical struc-
tures (colored boxes and interconnections).
It is up to the application how to derive meaning from or use
information obtained from interactions with the graphical struc-
ture.
189
Geometric models
There are, however, some parts of a graphical system which are
generally considered to be part of the graphics system. Those
are the geometric models used to represent entities in the ap-
plications domain. Some of the relations between the entities
may also have graphical representations.
Recall the example of the robot arm. In this case, the vari-
ous angles used to represent the positions of the arm segments,
and the dimensions of the segments themselves, belong to the
graphical domain. (They also have a meaning in the applica-
tions domain.)
We saw that the robot arm could be built in as hierarchical man-
ner. Each arm was constructed from a simple volume primitive
(a cube), then sized and positioned with a set of transforma-
tions. The hierarchy was robot — arm — transformed primi-
tive.
In general, a hierarchical model is often useful for the geometric
modeling of complex figures.
The hierarchy itself may contain other useful information (e.g.,
connectivity).
190
The hierarchy is useful for several reasons:
• modular construction of the object
• storage economy — references to repeated objects, rather
than duplicating object definitions
• propagation of modifications to components — components
are defined once and instantiated by reference
The following shows the relationship between the model, the
program modules which interact with the model, and the graph-
ical display and interaction system (e.g. OpenGL and GLUT).
Traversal for display
MODELdata structuresand procedures
Interactionswith model
modificationBuilding
manipulation
for analysisTraversal
Graphical"front end"
interactionand
interface
Display
191
With reference to the object hierarchy, we often refer to the
instantiation of an object or component of an object as call-
ing the object (analogous to calling a function in a procedural
hierarchy).
Again, analogously, we further refer to the passing of geometric
parameters in reference to the transformations applied when
the component is instantiated.
Some graphics packages include support for hierarchical mod-
eling. PHIGS, for example, supports a kind of database called
the central structure storage (CSS) which allows the storage
and retrieval of structures.
A structure is a sequence of elements — primitives, transfor-
mations, attributes, and other structures.
Generally, the CSS provides information directly to the dis-
play subsystem, and is itself modified and updated by special
functions. (In the previous diagram, the CSS is the MODEL,
generally optimized for display transversal.)
An example of a hierarchical structure contained in a CSS might
be a street of houses. The street structure (position, size, orien-
tation) would be the highest level, with the house structures at
the next level, and the particular house type at the next level.
192
The overall structure network might be diagrammed as follows:
STREET
rotate
HOUSEexecute
execute
execute
translateBUNGALOW
Here the term execute means apply an instance of the entity
to which the structure is pointing. This is called retained mode
graphics. We have used immediate mode graphics, where en-
tities to be displayed are described directly, not by reference.
A CSS database is not necessary, but it does provide certain
advantages. It can provide fast graphical updates, since it is
optimized for graphical traversal.
Graphical input is also facilitated because input pick operations
can readily be associated with components (by traversing the
structure hierarchy.)
If the database requires frequent updating, however, (particu-
larly if the structure hierarchy itself changes) then the database
update operations can become slow.
193
Display lists
OpenGL does not provide a CSS, but does support display lists.
A display list is a way to organize and name a set of OpenGL
commands. The commands may then be “executed” by “call-
ing” the display list.
Display lists are identified by an unsigned integer of type GLuint.
The function glGenlists(n) allocates n contiguous, previ-
ously unallocated display list indices. Zero is returned if the
request cannot be filled, or if the argument is 0.
glNewList(list,mode) specifies the start of the display list
with index list. The mode can be either GL_COMPILE or
GL_COMPILE_AND_EXECUTE. The latter both executes the com-
mands in immediate mode and places them in the display list.
glEndList() indicates the end of the current display list.
The following code fragment illustrates their use:
listIndex = glGenlists(1); /* generate 1 list index */
if (listIndex != 0) {
glNewList(listIndex, GL_COMPILE);
... /* OpenGL commands */
glEndList();
}
194
Executing a display list
Display lists are executed using the function glCallList(list)
where list is the index for the display list to be executed.
A call list can be executed several times. E.g., given a list with
label wheel it could be instantiated twice as follows:
glTranslatef(1.0, 0.0, 0.0);
glCallList(wheel);
glTranslatef(6.0, 0.0, 0.0);
glCallList(wheel);
Display lists can be hierarchical — one list can call another.
Suppose that we want to render a bicycle from component parts:
glNewList(listIndex, GL_COMPILE);
glTranslatef(1.0, 0.0, 0.0);
glCallList(frame);
glTranslatef(0.0, 0.0, 0.0);
glCallList(wheel);
glTranslatef(6.0, 0.0, 0.0);
glCallList(wheel);
glTranslatef(-1.0, 2.0, 0.0);
glCallList(handlebars);
glEndList();
195
What is stored in a display list?
A display list contains only the “current” values of expressions
stored in the list.
For example, if a vertex array is specified and the array ele-
ments are changed subsequent to the list being “compiled” the
new values are not reflected in the display list. Similarly, if a
transformation matrix is specified and subsequently modified,
the value at the time the list is compiled is used.
Some OpenGL commands cannot be stored in a display list.
Generally, things which change a state “out of scope” (e.g., a
global state change) are not allowed. glFlush() for example.
Also, the list commands themselves [glNewList(), glDeleteLists(),
glGenlists()] cannot be used inside the display list.
Other commands requiring feedback (glReadPixels(),
glFeedbackBuffer(), etc.) cannot be used because of the
client-server nature of OpenGL.
Also, commands which alter the state of the client such as
glEnableClientState(), glDisableClientState(), etc.
cannot be used, for the same reason.
The display list can accommodate hierarchy, and there is a limit
to the depth of the hierarchy; the minimum depth is 64.
196
What should be in a display list?
Display lists are created primarily to allow rapid rendering of
complex structures. The “compilation” process can be likened
to creating an image in a non-displayed area, and transforming
this image at instantiation.
The limitation of a display list is that the content is static —
the primitives internal to the list cannot be updated.
The following are guidelines only:
• Components which will be reused in a scene (e.g., the arms
of the robot)
• Components which will be redrawn frequently (e.g., the
cube — redrawn each spin angle change)
• organizing state changes that will be applied many times
The last item will be more useful later, when we discuss lighting
models and material parameters.
Recall that, if a state variable is set in a call list, (e.g., a color
is set with glColor*) the state change will persist after the
display list has been executed. The state variables can be saved
and returned using glPushAttrib() and glPopAttrib().
197
The argument for glPushAttrib() is from the following list:
Mask bit Attribute group
GL_ALL_ATTRIB_BITS all groups
GL_ACCUM_BUFFER_BIT accumulate buffer
GL_COLOR_BUFFER_BIT color buffer
GL_CURRENT_BIT current
GL_DEPTH_BUFFER_BIT depth buffer
GL_ENABLE_BIT enable
GL_EVAL_BIT eval
GL_FOG_BIT fog
GL_HINT_BIT hint
GL_LIGHTING_BIT lighting
GL_LINE_BIT line
GL_LIST_BIT list
GL_PIXEL_MODE_BIT pixel
GL_POINT_BIT point
GL_POLYGON_BIT polygon
GL_POLYGON_STIPPLE_BIT polygon stipple
GL_SCISSOR_BIT scissor
GL_STENSIL_BUFFER_BIT stencil buffer
GL_TEXTURE_BIT texture
GL_TRANSFORM_BIT transform
GL_VIEWPORT_BIT viewport
198
Each bit in the preceding bit masks corresponds to a number of
individual state variables. Exactly which ones are found in the
OpenGL documentation.
Similarly, the commands we have already used for modeling,
glPushMatrix() and glPopMatrix() are useful for localizing
the effect of translations and rotations to the commands in the
display list.
There are a few other interesting functions for use with display
lists:
glDeleteLists(GLuint list GLsize range) deletes range
display lists starting with list.
glIsList(GLuint list) returns GL_TRUE if list is already
in use, otherwise it returns GL_FALSE.
Multiple display lists can be executed with one call. Arguments
include a pointer to an array which contains the list numbers
as offsets from some base.
The base is set with glListBase(GLint base).
glCallLists(GLsizei n, GLenum type, GLvoid *lists)
executes ⁀n display lists computed by adding the current dis-
play list base (set by glListBase()) to the value in the array
pointed to by *lists. The parameter type is an enumerated
type indicating the type of data pointed to by *list.
199
Interaction with the image — selection and picking
Selection
Selection is one of the modes of operation for OpenGL. In selec-
tion, drawing information is returned to the application rather
the framebuffer. The screen remains fixed while OpenGL is in
selection mode.
In selection, the scene is first drawn into the framebuffer, then
selection mode is entered (and, typically, the viewing volume
changed) and the scene redrawn. While in selection mode, the
contents of the framebuffer remain unchanged. When selection
mode exits, a list of the primitives that intersect the viewing
volume is returned.
The list of primitives returned (the hit record) is an array of
integer identifiers corresponding to entries in the name stack.
The name stack is constructed by loading names (integers) onto
it as drawing commands are issued while in selection mode.
The returned list indicates, for example, which primitives might
have been selected on the screen by the user.
200
To use the selection mechanism, the following steps are per-
formed:
1. The array to be used for the returned hit records is specified
with glSelectBuffer().
2. Selection mode is entered by specifying GL_SELECT with
glRenderMode().
3. The name stack is initialized using glInitNames() and
glPushName().
4. The viewing volume to use for selection is defined. Since
this is generally different from the viewing volume originally
used to draw the scene, the current transformation state
should be saved with glPushMatrix() and restored with
glPopMatrix().
5. Drawing commands and commands to manipulate the name
stack are issued so that each primitive of interest has an ap-
propriate name assigned.
6. Exit selection mode and process the returned selection data
(the hit records).
201
void glSelectBuffer(GLsizei size, GLuint *buffer);
Specifies the array to be used for the returned selection data;
buffer is a pointer to an array of unsigned integers into which
the data is put, and size indicates the maximum number of
values that can be stored in the array. glSelectBuffer()
must be called before entering selection mode.
GLint glRenderMode(GLenum mode);
Controls whether the application is in rendering, selection, or
feedback mode. The mode argument can be one of GL_RENDER
(the default), GLSELECT, or GL_FEEDBACK. The program re-
mains in a given mode until glRenderMode() is called again
with a different argument. Before entering selection mode,
glSelectBuffer() must be called to specify the selection ar-
ray. The return value is the number of selection hits or the
number of values placed in the feedback array when either mode
is exited; a negative value means that the selection or feed-
back array has overflowed. GLRENDER_MODE can be used with
glGetlntegerv() to obtain the current mode.
202
Creating the Name Stack
The name stack is the basis for the selection information that
is returned. It is first initialized with glInitNames(), which
simply clears the stack, and then integer names are added to it
while issuing corresponding drawing commands.
glPushName(), and glPopName(), allow pushing and popping
of the stack; glLoadName() replaces the name on the top of
stack with its argument.
The following code shows simple name-stack manipulation:
glInitNames ();
glPushName(0);
glPushMatrix(); /* save the transformation matrix */
/* create your desired viewing volume here */
glLoadName(l);
drawFirstObject(); /* FirstObject has the name 1 */
glLoadName(2);
drawSecondObject(); /* SecondObject and ThirdObject */
drawThirdObject(); /* both have the same name - 2 */
glPopMatrix (); /* restore previous transformation */
203
Note: Multiple objects can share the same name if there is no
need to distinguish between them.
void glPushName(GLuint name)
Pushes name onto the name stack. Pushing beyond the stack
depth generates the GL_STACK_OVERFLOW error.
GL_NAME_STACK_DEPTH can be used with glGetIntegerv()
to obtain the depth of the name stack. The depth of the name
stack is guaranteed to be at least sixty-four names.
void glPopName(void)
Pops one name off the top of the name stack. Popping an empty
stack generates the error GL_STACK_UNDERFLOW.
void glLoadName(GLuint name);
Replaces the value on the top of the name stack with name. If
the stack is empty, (e.g., right after glInitNames() is called)
glLoadName() generates the error GL_INVALID_OPERATION.
glPushName() should be called at least once to put something
on the name stack before calling glLoadName().
Calls to glPushName(), glPopName(), and glLoadName()
are ignored when not in selection mode. This can simplify cod-
ing by using these calls in the drawing code, and then using
the same drawing code in both selection and normal rendering
modes.
204
The Hit Record
In selection mode, a primitive that intersects the viewing vol-
ume causes a selection hit. Whenever a name-stack manip-
ulation command is executed or glRenderMode() is called,
OpenGL writes a hit record into the selection array if there has
been a hit since the last time the stack was manipulated or
glRenderMode() was called. Objects sharing the same name
will not generate multiple hit records. Hit records are may not
be written into the array until glRenderMode() is called.
Note: In addition to primitives, valid coordinates produced by
glRasterPos() can cause a selection hit. Also, in the case of
polygons, no hit occurs if the polygon would have been culled.
Each hit record consists of four items, in order:
• The number of names on the name stack when the hit oc-
curred.
• Both the minimum and maximum window-coordinate z val-
ues of all vertices of the primitives that intersected the view-
ing volume since the last recorded hit. These two values,
lying in the range [0, 1], are each multiplied by 232 − 1 and
rounded to the nearest unsigned integer.
• The contents of the name stack at the time of the hit, with
the bottommost element first.
205
On entering selection mode, OpenGL initializes a pointer to the
beginning of the selection array. Each time a hit record is writ-
ten into the array, the pointer is updated accordingly. If writing
a hit record would cause the number of values in the array to
exceed the size argument specified with glSelectBuffer(),
OpenGL writes as much of the record as fits in the array and
sets an overflow flag. When selection mode is exited with
glRenderMode(), this command returns the number of hit
records written (including a partial record if there was one),
clears the name stack, and resets the stack pointer and overflow
flag. If the overflow flag had been set, the return value is -1.
Remember that the depth information is a scaled integer, rep-
resenting an unsigned number between 0 and 1. Dividing the
returned number by the largest unsigned integer will give the
actual depth in the window. To get the depth in world coor-
dinates, the transformation matrix parameters (e.g., the argu-
ments to glOrtho()) are required.
206
A Selection Example
In the sample program select.c, four triangles (green, red,
and two yellow triangles, created by calling drawTriangle())
and a wireframe box showing the viewing volume (drawViewVolume())
are drawn to the screen. Then the triangles are rendered again
(selectObjects()), in selection mode. The corresponding hit
records are processed in processHits(), where the hit list is
printed out. The first triangle generates a hit, the second does
not, and the third and fourth together generate a single hit.
Look at this code carefully to understand its operation.
/* draw a triangle with vertices at (x1, y1), (x2, y2)
* and (x3, y3) at z units away from the origin.
*/
void drawTriangle (GLfloat x1, GLfloat y1, GLfloat x2,
GLfloat y2, GLfloat x3, GLfloat y3, GLfloat z)
{
glBegin (GL_TRIANGLES);
glVertex3f (x1, y1, z);
glVertex3f (x2, y2, z);
glVertex3f (x3, y3, z);
glEnd ();
}
207
void draw4Triangles (void)
{
glLoadName(1); /* ignored in render mode */
glColor3f (0.0, 1.0, 0.0); /* green triangle */
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -5.0);
glLoadName(2);
glColor3f (1.0, 0.0, 0.0); /* red triangle */
drawTriangle (2.0, 7.0, 3.0, 7.0, 2.5, 8.0, -5.0);
glLoadName(3);
glColor3f (1.0, 1.0, 0.0); /* yellow triangles */
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, 0.0);
drawTriangle (2.0, 2.0, 3.0, 2.0, 2.5, 3.0, -10.0);
}
208
void drawScene (void)
{
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluPerspective (40.0, 4.0/3.0, 1.0, 100.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
gluLookAt (7.5, 7.5, 12.5, 2.5, 2.5, -5.0,
0.0, 1.0, 0.0);
draw4Triangles();
drawViewVolume (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
}
drawViewVolume() draws a view volume which will be used as
the clipping volume in the next function, selectObjects().
#define BUFSIZE 512
209
void selectObjects(void)
{
GLuint selectBuf[BUFSIZE];
GLint hits;
glSelectBuffer (BUFSIZE, selectBuf);
(void) glRenderMode (GL_SELECT); /* set SELECT mode */
glInitNames(); /* initialize name stack */
glPushName(0);
glPushMatrix ();
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrtho (0.0, 5.0, 0.0, 5.0, 0.0, 10.0);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
draw4Triangles();
glPopMatrix ();
glFlush ();
hits = glRenderMode (GL_RENDER); /* no. of hits */
processHits (hits, selectBuf);
}
210
void processHits (GLint hits, GLuint buffer[])
{
unsigned int i, j;
GLuint names, *ptr;
float z1, z2;
printf ("hits = %d\n", hits);
ptr = (GLuint *) buffer;
for (i = 0; i < hits; i++) { /* for each hit */
/* ptr points to hit list - returns no. of names,
min and max z coordinates, and each name in the
name stack, bottommost first */
names = *ptr; ptr++;
z1 = (float) *ptr/0x7fffffff; ptr++; /* scale 0-2 */
z2 = (float) *ptr/0x7fffffff; ptr++;
printf(" number of names for hit = %d\n", names);
printf(" z1 is %g; z2 is %g\n", z1, z2);
printf (" the name is ");
for (j = 0; j < names; j++) /* for each name */
printf ("%d ", *ptr); ptr++;
printf ("\n");
}
}
211
Note that the buffer containing the hit record was defined with
glSelectBuffer(BUFSIZE, selectBuf);
and contains data of type GLuint relating to the hit.
The code shown is “boiler plate” C code for obtaining values
from the hit record. Note that an array index could be used in
place of the pointer (ptr).
The output is basically the contents of the hit record, in the
order in which it was written. Note that in this example, there
is only one name for each item.
The code normalizes the z depth information in the range 0 – 2.
(Dividing the z values returned by the largest unsigned integer
(0xffffffff) would normalize to the range 0–1.)
Usually, the information in the hit record would be used to
trigger some other event; e.g., highlighting the items in the
view volume.
Selection can be used in a number of interesting applications;
for example, to model a “sense of touch” for an object.
212
Sample output for select.c
hits = 2
number of names for hit = 1
z1 is 1; z2 is 1
the name is 1
number of names for hit = 1
z1 is 0; z2 is 2
the name is 3
Recall that the green triangle had label 1, the red triangle had
label 2 and the two yellow triangles had label 3.
Clearly, the select operation found the green and yellow trian-
gles, but did not select the red triangle.
213
Picking
The picking operation is really a special case of selection. A
special picking matrix is used in conjunction with the projection
matrix to restrict drawing to a small region of the viewport,
typically near the cursor.
Some form of input, such as clicking a mouse button, initiates
selection (pick) mode. With selection mode set, together with
the special picking matrix, objects drawn near the cursor cause
selection hits.
Thus, during picking you are typically determining which ob-
jects are drawn near the cursor.
Picking is set up similarly to regular selection mode, with the
following major differences:
• Picking is usually triggered by an input device. In the
following code examples, pressing the left mouse button
invokes a function that performs picking.
• The utility routine gluPickMatrix() is used to multiply
a special picking matrix onto the current projection matrix.
214
gluPickMatrix() should be called before setting a standard
projection matrix (such as gluPerspective() or glOrtho()).
Normally, the contents of the projection matrix are first saved,
so the sequence of operations may look like this:
glMatrixMode (GL_PROJECTION);
glPushMatrix ();
glLoadIdentity ();
gluPickMatrix (...);
gluPerspective, glOrtho, glOrtho2D, or glFrustum
/* ... draw scene for picking */
/* ... perform picking ... */
glPopMatrix();
void gluPickMatrix(GLdouble x, GLdouble y, GLdouble
width, GLdouble height, GLint viewport[4]);
Creates a projection matrix that restricts drawing to a small
region of the viewport and multiplies that matrix onto the cur-
rent matrix stack. (x, y) is the center of the picking region in
window coordinates, typically the cursor location. width and
height define the size of the picking region in screen coordi-
nates. viewport[] indicates the current viewport boundaries,
obtained by calling
glGetIntegerv(GL_VIEWPORT, GLint *viewport);
215
The net result of the matrix created by gluPickMatrix() is
to transform the pick volume into the canonical clipping re-
gion −1 < (xyz) < 1 (or −w < (wx,wy, wz) < w). Since
the transformation is arbitrary, you can make picking work for
different sorts of regions—for example, for rotated rectangular
portions of the window.
In certain situations, you might find it easier to specify addi-
tional clipping planes to define the picking region.
The program pickdepth.c illustrates simple picking of over-
lapping elements.
In this example, when the left mouse button is pressed, the
function pickRects() is called to identify which rectangles
were picked by the mouse.
Note that for one press of the mouse key, several hits (corre-
sponding to the different rectangles under the cursor) may be
returned.
The individual rectangles can be distinguished by their depth
values.
This code should be examined carefully.
216
Sample code from pickdepth.c (slightly simplified)
void myinit(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glDepthFunc(GL_LESS);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
glDepthRange(0.0, 1.0); /* The default z mapping */
}
void
display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawRects();
glutSwapBuffers();
}
The function drawRects() draws 3 overlapping rectangles,
with colors yellow, cyan, and magenta named 1, 2, and 3, re-
spectively.
217
void drawRects()
/* slightly simplified from the original */
{
glLoadName(1);
glBegin(GL_QUADS);
glColor3f(1.0, 1.0, 0.0); /* yellow */
glVertex3i(2, 0, 0); glVertex3i(2, 6, 0);
glVertex3i(6, 6, 0); glVertex3i(6, 0, 0);
glEnd();
glLoadName(2);
glBegin(GL_QUADS);
glColor3f(0.0, 1.0, 1.0); /* cyan */
glVertex3i(3, 2, -1); glVertex3i(3, 7, -1);
glVertex3i(8, 7, -1); glVertex3i(8, 2, -1);
glEnd();
glLoadName(3);
glBegin(GL_QUADS);
glColor3f(1.0, 0.0, 1.0); /* magenta */
glVertex3i(0, 2, -2); glVertex3i(0, 8, -2);
glVertex3i(5, 8, -2); glVertex3i(5, 2, -2);
glEnd();
}
218
/* pickRects() is the mouse callback which sets up
* selection mode, name stack, and projection matrix
* for picking. Then the objects are drawn.
*/
#define BUFSIZE 512
void pickRects(int button, int state, int x, int y)
{
GLuint selectBuf[BUFSIZE];
GLint hits;
GLint viewport[4];
if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
return;
glGetIntegerv(GL_VIEWPORT, viewport);
glSelectBuffer(BUFSIZE, selectBuf);
(void) glRenderMode(GL_SELECT);
glInitNames();
glPushName(-1);
219
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
/* create 5x5 pixel pick region near cursor location */
gluPickMatrix((GLdouble) x, (GLdouble) (viewport[3]-y),
5.0, 5.0, viewport);
glOrtho(0.0, 8.0, 0.0, 8.0, -0.5, 2.5);
drawRects();
glPopMatrix();
glFlush();
hits = glRenderMode(GL_RENDER);
processHits(hits, selectBuf);
}
220
void processHits(GLint hits, GLuint buffer[])
/* this is the same "boiler plate" as before */
{
unsigned int i, j;
GLuint names, *ptr;
float z1, z2;
printf("hits = %d\n", hits);
ptr = (GLuint *) buffer;
for (i = 0; i < hits; i++) { /* for each hit */
names = *ptr; ptr++;
z1 = (float) *ptr/0xffffffff; ptr++; /* scale 0-1 */
z2 = (float) *ptr/0xffffffff; ptr++;
printf(" number of names for hit = %d\n", names);
printf(" z1 is %g; z2 is %g\n", z1, z2);
printf(" the name is ");
for (j = 0; j < names; j++) { /* for each name */
printf("%d ", *ptr); ptr++;
}
printf("\n");
}
}
221
sample output from pickdepth.c
For a cursor click in the center:
hits = 3
number of names for hit = 1
z1 is 0.166667; z2 is 0.166667
the name is 1
number of names for hit = 1
z1 is 0.5; z2 is 0.5
the name is 2
number of names for hit = 1
z1 is 0.833333; z2 is 0.833333
the name is 3
222
Using Multiple Names in a Hierarchical Model
Multiple names can also be used to distinguish between parts
of a hierarchical object in a scene.
For example, if you were rendering an assembly line of automo-
biles, you might want the ability to move the mouse to pick the
third bolt on the left front tire of the third car in line. Since the
bolts are identical, it is logical to have the same name for each
bolt, and to establish the particular bolt picked using a name
hierarchy. A different name can be used to identify each level
of hierarchy: which car, which tire, and finally which bolt.
As another example, one name could be used to describe a
single molecule among other molecules, and additional names
can differentiate individual atoms within that molecule.
The following example draws an automobile with four identical
wheels, each of which has five identical bolts. Code has been
added to manipulate the name stack with the object hierarchy.
223
/* Example showing the creation of multiple names */
draw_wheel_and_bolts()
{
long i;
draw_wheel_body();
for (i = 0; i < 5; i++) {
glPushMatrix();
glRotate(72.O*i, 0.0, 0.0, 1.0);
glTranslatef(3.0, 0.0, 0.0);
glPushName(i);
draw_bolt_body();
glPopName ();
glPopMatrix();
}
}
224
draw_body_and_wheel_and_bolts()
{
drawcarbody();
glPushMatrix();
glTranslate(4O, 0, 20); /* first wheel position */
glPushName(l); /* name of wheel number 1 */
draw_wheel_and_bolts();
glPopName ();
glPopMatrix();
glPushMatrix();
glTranslate(4O, 0, 20); /* second wheel position */
glPushName(2); /* name of wheel number 2 */
draw_wheel_and_bolts();
glPopName ();
glPopMatrix ();
/* draw last two wheels similarly */
}
The following example uses the previous routines in to draw
three different cars, numbered 1, 2, and 3:
225
draw_three_cars ()
{ glInitNames ();
glPushMatrix();
translate_to_first_car_position();
glPushName(l);
draw_body_and_wheel_and_bolts ();
glPopName();
glPopMatrix();
glPushMatrix();
translate_to_secondcar_position ();
glPushName(2);
draw_body_and_wheel_and_bolts ();
glPopName ();
glPopMatrix();
glPushMatrix ()
translate_to_third_car_position();
glPushName(3);
draw_body_and_wheel_and_bolts();
glPopName ();
glPopMatrix();
}
226
Assuming that picking is performed, the following are some
possible name-stack return values and their interpretations. In
these examples, at most one hit record is returned; also, d1 and
d2 are depth values.
2 d1 d2 2 1 Car 2, wheel 1
1 d1 d2 3 Car 3 body
3 d1 d2 1 1 0 Bolt 0 on wheel 1 on car I
empty The pick was outside all cars
The last interpretation assumes that the bolt and wheel don’t
occupy the same picking region. A user might well pick both
the wheel and the bolt, yielding two hits. If you receive multiple
hits, you have to decide which hit to process, perhaps by using
the depth values to determine which picked object is closest to
the viewpoint.
227
The program picksquare.c illustrates picking with a name
hierarchy.
It demonstrates how to use multiple names to identify different
components of a primitive, in this case the row and column
positions of a selected object.
A 3 × 3 grid of squares is drawn, with each square a different
color. The board[3][3] array maintains the current amount
of blue for each square. When the left mouse button is pressed,
the picksquare() routine is called to identify which squares
were picked by the mouse.
Two names (integers) identify each square in the grid; one iden-
tifies the row, and the other the column.
Also, in this example, when the left mouse button is pressed,
the color of all squares under the cursor position changes.
Again, this code should be examined carefully.
228
Sample code from picksquare.c
void drawSquares(GLenum mode)
{
GLuint i, j;
for (i = 0; i < 3; i++) {
if (mode == GL_SELECT)
glLoadName (i);
for (j = 0; j < 3; j ++) {
if (mode == GL_SELECT)
glPushName (j); /* note name hierarchy */
glColor3f ((GLfloat) i/3.0, (GLfloat) j/3.0,
(GLfloat) board[i][j]/3.0);
glRecti (i, j, i+1, j+1);
if (mode == GL_SELECT)
glPopName ();
}
}
}
229
/* pickSquares() sets up selection mode, name stack,
* and projection matrix for picking. Then the
* objects are drawn.
*/
#define BUFSIZE 512
void pickSquares(int button, int state, int x, int y)
{
GLuint selectBuf[BUFSIZE];
GLint hits;
GLint viewport[4];
if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)
return;
glGetIntegerv (GL_VIEWPORT, viewport);
glSelectBuffer (BUFSIZE, selectBuf);
(void) glRenderMode (GL_SELECT);
glInitNames();
glPushName(0);
230
glMatrixMode (GL_PROJECTION);
glPushMatrix ();
glLoadIdentity ();
/* create 5x5 pixel pick region near cursor location */
gluPickMatrix ((GLdouble)x, (GLdouble)(viewport[3]-y),
5.0, 5.0, viewport);
gluOrtho2D (0.0, 3.0, 0.0, 3.0);
drawSquares (GL_SELECT);
glMatrixMode (GL_PROJECTION);
glPopMatrix ();
glFlush ();
hits = glRenderMode (GL_RENDER);
processHits (hits, selectBuf);
glutPostRedisplay();
}
231
void processHits (GLint hits, GLuint buffer[])
{
unsigned int i, j;
GLuint ii, jj, names, *ptr;
float z1, z2;
printf ("hits = %d\n", hits);
ptr = (GLuint *) buffer;
for (i = 0; i < hits; i++) { /* for each hit */
names = *ptr; ptr++;
z1 = (float) *ptr/0x7fffffff; ptr++;
z2 = (float) *ptr/0x7fffffff; ptr++;
printf (" number of names for hit = %d\n", names);
printf(" z1 is %g; z2 is %g\n", z1, z2);
printf (" names are ");
for (j = 0; j < names; j++) { /* for each name */
printf ("%d ", *ptr);
if (j == 0) ii = *ptr; /* set row and column */
else if (j == 1) jj = *ptr; ptr++;
}
printf ("\n");
board[ii][jj] = (board[ii][jj] + 1) % 3;
}
}
232
Sample output for picksquare.c
Output when selecting the lower left corner square:
hits = 1
number of names for this hit = 2
z1 is 1; z2 is 1
names are 0 0
Output when selecting the top center square:
hits = 1
number of names for this hit = 2
z1 is 1; z2 is 1
names are 1 2
233
Hints for Writing a Program That Uses Selection
Most programs that allow a user to interactively edit some ge-
ometry provide a mechanism for the user to pick items or groups
of items for editing. For two-dimensional drawing programs (for
example, text editors, circuit-layout programs, etc.), it might
be easier to do the picking calculations in the user code rather
than using the OpenGL pick mechanism. Often, it is easy to
find bounding boxes for two-dimensional objects and to organize
them in some hierarchical data structure to speed up searches.
For example, picking that uses the OpenGL style in a VLSI
layout program containing millions of rectangles can be quite
slow. However, with a good data structure, and using sim-
ple bounding-box information and other geometry constraints,
(e.g., only rectangular primitives aligned with the screen) could
make picking in such a program extremely fast. (The code may
also be simpler, as well.)
If you decide to use OpenGL picking, organize your program
and its data structures so that it is easy to draw appropriate lists
of objects in either selection or normal drawing mode. Then,
when the user picks something, the same data structures can
be used for the pick operation.
234
A possible interface design for a two-dimensional picking:
• All selection is done by pointing with the mouse cursor and
using the left mouse button.
• Clicking on an item selects it and deselects all other cur-
rently selected items. If the cursor is on top of multiple
items, the smallest is selected.
• Holding the button down while dragging the cursor, and
then releasing the button, selects all the items in a screen-
aligned rectangle with corners determined by the cursor
positions of the end points where the button changed state.
This is called a sweep selection.
• Holding the Shift key down when the user clicks on an item
not currently selected, adds that item to the select list. If
it was already selected, it is deleted.
• A sweep selection performed with the Shift key pressed adds
the items swept to the select list.
• If the user clicks on multiple (stacked) items, select just one
of them. If the cursor isn’t moved, and the user clicks again
in the same place, deselect the item originally selected, and
select a different item under the cursor. Use repeated clicks
at the same point to cycle through all the possibilities.
235
Of course, different situations require different approaches. For
example, in a text editor, you should not have to be concerned
with overlapping characters.
In three-dimensional editors, you might consider providing ways
to rotate and zoom between selections, or use the three ortho-
graphic projections, so complex methods for cycling through
the possible overlapping selections might be unnecessary.
In general, however, selection in three dimensions is difficult
because the position of the cursor on the screen usually gives
no indication of its depth.
The pick function has many other uses, as well.
Consider the case of a robot with “thumbs” used to pick up
an object in a 3-dimensional scene. How could we determine
whether the thumbs actually “contacted” the object?
One approach would be to construct a pick matrix selecting an
area about the thumbs, and look for “hits” of the thumb and
the object.
236
Other common interactions
Graphics editors generally have methods related to pick and
select for picking and selecting a whole area or a whole higher
level structure.
We have already seen briefly the use of sweep selection. This is
generally implemented with a rubberbanding style — the sweep
rectangle is drawn as an outline which follows the cursor until,
say, a mouse button is released. (In sweep selection, normally
the rectangle disappears.)
This is a useful idea for other applications. For example, it is
often used to let the user set the size of various simple graphics
primitives (e.g., rectangles, circles, ellipses, polygons in pro-
grams like xfig.)
Another common, useful, facility is to place handles on com-
plex components. These handles are points at which the whole
component can be selected so that some operation could be per-
formed on it (repositioning is the most typical, as is resizing or
deletion — xfig uses handles for these.)
Often programs using such constructs have several modes; say,
drawing and editing. The handles may only be displayed in
editing mode.
237
Modelling curves and curved surfaces
So far, we have been largely satisfied with surfaces constructed
from polygonal patches. In fact, we have used models mostly
composed of rectangular or triangular solids.
Of course, OpenGL only renders simple polygonal surfaces, so
any other surfaces or lines must eventually be modelled using
those primitives.
We have briefly looked at some of the glu and glut functions
for a cylinder and a cube. In fact, the glu library has func-
tions for a number of interesting curves and surfaces. A set of
functions for quadric surfaces is available.
A quadric surface is defined by the general quadratic equation
in 3 dimensions:
a1x2+a2y
2+a3z2+a4xy+a5yz+a6xz+a7x+a8y+a9z+a10 = 0
The quadric surfaces available in glu are not the fully general
quadric surfaces, but include the most commonly used func-
tions.
238
The following steps are required to use a quadrics object:
1. Call gluNewQuadric() to create a quadrics object.
2. Specify the rendering attributes for the quadrics object (or
use the default values).
(a) Use gluQuadricOrientation() to differentiate the
interior from the exterior.
(b) Use gluQuadricDrawStyle() to determine whether
the object is rendered as points, lines, or filled polygons.
(c) Use gluQuadricNormals() to specify one normal per
vertex or one normal per face, if normals are required.
(The default is that no normals are generated.)
3. Define error-handling routines with gluQuadricCallback().
Then, if an error occurs during rendering, the specified rou-
tine is invoked.
4. Invoke the rendering routine for the desired type of quadrics
object: gluSphere(), gluCylinder(), gluDisk(), or
gluPartialDisk(). For static data, encapsulate the quadrics
object in a display list for faster rendering.
5. When completely finished with the quadric, delete it with
gluDeleteQuadric(). If another quadric is needed, it is
better to reuse the quadrics object.
239
Managing Quadrics Objects
A quadrics object consists of parameters, attributes, and call-
backs that are stored in a data structure of type GLUquadricObj.
A quadrics object may generate vertices, normals, texture coor-
dinates, and other data, all of which may be used immediately
or stored in a display list. The following routines create, destroy,
and report errors in a quadrics object:
GLUquadricObj* gluNewQuadric(void);
Creates a new quadrics object and returns a pointer to it. A
null pointer is returned if the routine fails.
void gluDeleteQuadric(GLUquadricObj *qobj);
Destroys the quadrics object qobj and frees up any memory
used by it.
void gluQuadricCallback(GLUquadricObj *qobj,
GLenum w, void (*fn)());
Defines a function fn() to be called when an error occurs.
(GLU_ERROR is the only legal value for w.) If fn() is NULL,
any existing callback is erased.
For GLU_ERROR, fn() is called with one parameter, the error
code. gluErrorString() can be used to convert the error
code into an ASCII string.
240
Controlling Quadrics Attributes
The following routines affect the kinds of data generated by the
quadrics routines. They must be called before the primitives
are specified.
gluQuadricDrawStyle(GLUquadricObj *qobj,
GLenum drawStyle)
drawStyle controls the rendering style for qobj.
Possible values for drawStyle are GLU_POINT, GLU_LINE,
GLU_SILHOUETTE, and GLU_FILL.
GLU_POINT and GLU_LINE specify that primitives be rendered
as a point at every vertex or a line between each pair of con-
nected vertices.
GLU_SILHOUETTE specifies that primitives are rendered as lines,
but edges separating coplanar faces are not drawn. (This is most
often used with gluDisk() and gluPartialDisk().)
GLU_FILL specifies rendering by filled polygons, where the poly-
gons are drawn in a counterclockwise fashion with respect to
their normals.
This may be altered with gluQuadricOrientation().
241
void gluQuadricOrientation(GLUquadricObj *qobj,
GLenum orientation);
For the quadrics object qobj, orientation is either GLU_OUTSIDE
(the default) or GLU_lNSIDE, which controls the direction in
which normals are pointing.
For gluSphere() and gluCylinder(), the definitions of out-
side and inside are obvious. For gluDisk() and gluPartialDisk(),
the positive z side of the disk is considered to be outside.
void gluQuadricNormals(GLUquadricObj *qobj,
GLenum normals);
For the quadrics object qobj, normals is GLU_NONE (the de-
fault), GLU_FLAT, or GLU_SMOOTH.
gluQuadricNormals() is used to specify when to generate
normal vectors. GLU_NONE means that no normals are gener-
ated and is intended for use without lighting.
GLU_FLAT generates one normal for each facet; generally best
for lighting with flat shading.
GLU_SMOOTH generates one normal for every vertex of the quadric;
usually best for lighting with smooth shading.
242
Quadrics Primitives
The following routines actually generate the vertices and other
data that constitute a quadrics object. In each case, qobj refers
to a quadrics object created by gluNewQuadric().
void gluSphere(GLUquadricObj *qobj, GLdouble radius,
GLint slices, GLint stacks);
Draws a sphere of the given radius, centered around the origin,
(0, 0, 0). The sphere is subdivided around the z-axis into a
number of slices (similar to longitude) and along the z-axis
into a number of stacks (latitude).
The following shows a sphere with 15 slices and 10 stacks, drawn
in GLU_FILL mode with GLU_SMOOTH shading:
243
void gluCylinder(GLUquadricObj *qobj,
GLdouble baseRadius, GLdouble topRadius,
GLdouble height, GLint slices, GLint stacks);
Draws a cylinder oriented along the z-axis, with the base of the
cylinder at z = 0 and the top at z = height. The cylinder is
also subdivided into a number of slices around the z-axis and
number of stacks along the z-axis. baseRadius is the radius of
the cylinder at z = 0. topRadius is the radius of the cylinder at
z = height. If topRadius is set to 0, then a cone is generated.
Note: The cylinder is not closed at the top or bottom — no
disks are drawn at the base or the top.
The following shows a cylinder with 15 slices and 5 stacks, drawn
in GLU_FILL mode with GLU_FLAT shading:
244
void gluDisk(GLUquadricObj *qobj, GLdouble innerRadius,
GLdouble outerRadius, GLint slices, GLint rings);
Draws a disk on the z = 0 plane, with a radius of outerRadius
and a concentric circular hole with a radius of innerRadius.
If innerRadius is 0, then no hole is created. The disk is sub-
divided around the z-axis into a number of slices (like slices of
pie) and also about the z-axis into a number of concentric rings.
With respect to orientation, the +z side of the disk is considered
to be “outside;” that is, any normals generated point along the
+z axis. Otherwise, the normals point along the −z axis.
The following shows a disk with 20 slices and 4 rings, drawn in
GLU_LINE mode without shading (GLU_NONE):
245
void gluPartialDisk(GLUquadricObj *qobj, GLdouble
innerRadius, GLdouble outerRadius, GLint slices, GLint
rings, GLdouble startAngle, GLdouble sweepAngle);
The difference between a partial disk and a disk is that only a
portion of a partial disk is drawn, starting from startAngle
through startAngle + sweepAngle (where startAngle and
sweepAngle are measured in degrees; 0◦ is along the +y axis,
90◦ degrees is along the +x axis, 180◦ is along the −y axis, and
270◦ is along the −x axis).
The following shows a partial disk with 20 slices and 4 rings,
drawn in GLU_SILHOUETTE mode without shading (GLU_NONE)
drawn from 0◦ to 225◦:
246
The code for each of those quadric figures was of the form:
GLUquadricObj *qobj;
.
.
qobj = gluNewQuadric();
.
.
gluQuadricDrawStyle(qobj, GLU_FILL); /* solid figure */
gluQuadricNormals(qobj, GLU_FLAT); /* flat shaded */
glNewList(startlist, GL_COMPILE);
gluCylinder(qobj, 0.5, 0.3, 1.0, 15, 5);
glEndList();
Note: For all quadrics objects, it’s better to use the *Radius,
height, and similar arguments to scale the object rather than
the glScale*() command, so that the unit-length normals
that are generated do not have to be renormalized.
Setting the rings and stacks arguments to values other than
1 forces lighting calculations to be done at a finer granularity,
especially if the material specularity is high.
In this example, all the quadrics used the same object, qobj.
The program quadric.c shows code which draws these figures.
247
Other curved objects
For objects without the regularity of quadrics, the shape has to
be represented with other functions. For example, consider a
simple curve like the following, which was drawn freely.
It can be approximated to any degree of accuracy we like using
straight line segments. Of course, for very high accuracy, we
need very many straight line segments. This is an example of a
piecewise linear approximation to the curve.
It is also possible to make either global or local polynomial ap-
proximations to a curve. In fact, given n points, it is possible
to exactly fit an n − 1 order polynomial. If the points are not
exact (say, with measurement uncertainties) then an approxi-
mate polynomial of lower order may be fitted, using a number
of techniques.
248
Of course, OpenGL must eventually construct a curve from line
segments, but a curve or curved surface may be a more natural
representation from a modelling point of view.
Polynomial approximations to curves are particularly useful,
because they are relatively easy to determine. There are two
possibilities:
• A single polynomial which approximates the full curve by
interpolating or approximating all the points
• A set of polynomials each interpolating or approximating a
number of the points, in a piecewise manner
The previous figure showed a linear piecewise approximation to
the function.
In general, if the number of points is even moderately large, a
single polynomial will be of a high order, and introduce spurious
“wiggles” in the approximating function.
Using a piecewise polynomial approximation, lower order poly-
nomials can be used, and the curve may looks “smoother.”
249
The following shows an example of a set of points fitted with
a high order polynomial and a piecewise linear polynomial of
order 2:
2nd order polynomials, piecewise6th order polynomial, continuous
The classical way for a draftsman to draw a smooth curve was
to use a “mechanical spline” — a thin piece of flexible material
which was anchored to the drawing at a set of points called
knots.
The flexibility of the spline means that the curve will be smooth,
and the changes in curvature will be gradual.
There is a mathematical analogue to this process, which also
produces a low order piecewise polynomial approximation to a
set of points. The most commonly used polynomial is a cubic
polynomial, and it is the cubic spline which best matches the
mechanical spline.
250
The cubic spline
The following describes how we may find a function called cu-
bic spline that interpolates a given set of data.
The mathematical model for this can be derived as follows. Let
(xi, fi) with f(xi) ≡ fi, i = 1 : n be distinct pairs of points
with a = x1 < x2 < · · · < xn = b.
The cubic spline that interpolates these points is a function s(x)
defined on [a, b] with the following properties:
1. s(xi) = fi, i = 1 : n, (The spline passes through the
knots).
2. s(x), s′(x), s′′(x) are continuous on [a, b] , (The spline is
continuous, as are its first and second derivatives. There-
fore, it has no sharp angles.)
3. In each subinterval [xi, xi+1], i = 1 : n− 1 s(x) is a cubic
polynomial (but s(x) may be a different cubic polynomial
in each subinterval.)
4. The spline s(x) minimizes its potential energy. Since the
potential energy is approximately proportional to the sec-
ond derivative. then, if g(x) is any other function that
satisfies (1), (2), (3), then
∫ b
as′′(x)2dx ≤
∫ b
ag′′(x)2dx.
251
Property (4) is difficult to model, but it can be shown that
it holds provided that
s′′(a) = s′′(b) = 0, which will be used instead of (4).
It can be shown that there is precisely one function s(x) satis-
fying those conditions.
Computing cubic splines
Since s(x) is a cubic polynomial for x ∈ [xi, xi+1] then s′′(x)
must be linear, so:
s′′(x) = s′′(xi)x− xi+1
xi − xi+1
+ s′′(xi+1)x− xi
xi+1 − xi
= s′′(xi)x− xi + xi − xi+1
xi − xi+1
+ s′′(xi+1)x− xi
xi+1 − xi⇐⇒
s′′(x) = s′′(xi) +x− xi
xi+1 − xi[s′′(xi+1)− s′′(xi)] , (1)
To find s(x), x ∈ [xi, xi+1] this can be integrated twice; that is:
∫ x
xis′′(t)dt = [s′(t)]xxi = s′(x)− s′(xi). (2)
252
∫ x
xi
s′′(xi) +
t− xixi+1 − xi
[s′′(xi+1)− s′′(xi)]
dt
= s′′(xi)∫ x
xidt +
s′′(xi+1)− s′′(xi)
xi+1 − xi
∫ x
xi(t− xi)dt
= s′′(xi)(x− xi) +s′′(xi+1)− s′′(xi)
2(xi+1 − xi)(x− xi)
2. (3)
Relations (2) and (3) imply
s′(x) = s′(xi)+s′′(xi)(x−xi)+s′′(xi+1)− s′′(xi)
2(xi+1 − xi)(x−xi)
2 (4)
Integrating (4) gives
s(x) = s(xi) + s′(xi)(x− xi) +1
2s′′(xi)(x− xi)
2+
1
6
s′′(xi+1)− s′′(xi)
xi+1 − xi(x−xi)
3, x ∈ [xi, xi+1], i = 1 : n−1.
(5)
Denoting s′′i ≡ s′′(xi), and hi ≡ xi+1−xi and setting x ≡ xi+1
in (5) and solving for s′(xi) we get
fi+1 = fi + s′(xi)hi +1
2s′′i h
2i +
s′′i+1 − s′′i6hi
h3i ⇐⇒
s′(xi) =fi+1 − fi
hi− s′′i+1
hi
6− s′′i
hi
3(6)
253
From (5) and (6) it is obvious that if s′′i is known for i = 1 : n
then s(x) can be evaluated for any value of x ∈ [a, b].
The aim therefore is to find a way for the computation of s′′i ,
i = 1 : n. This can be done using (4); setting i ≡ i − 1 and
x ≡ xi obtains
s′(xi) = s′(xi−1) +1
2(s′′i + s′′i−1)hi−1. (7)
Relations (6) and (7) imply
s′(xi−1) +1
2(s′′i + s′′i−1)hi−1 =
fi+1 − fihi
− s′′i+1
hi
6− s′′i
hi
3. (8)
In order to eliminate s′(xi−1) from (8) set i ≡ i− 1 in (6) and
substitute the resulting expression for s′(xi−1) into (8).
After simplification,
hi−1s′′i−1+2(hi−1+hi)s
′′i+his
′′i+1 = 6
fi+1 − fi
hi− fi − fi−1
hi−1
︸ ︷︷ ︸
di
, i = 2 : n−1 .
(9)
A system of n − 2 linear equations can be constructed from
(9) to solve for the unknowns s′′i , i = 2 : n − 1. Note that
s′′1 = s′′n = 0. The system is as follows:
254
2(h1 + h2) h2
. . . . . . . . .
hn−3 2(hn−3 + hn−2) hn−2
hn−2 2(hn−2 + hn−1)
︸ ︷︷ ︸
H
s′′2...
s′′n−2
s′′n−1
︸ ︷︷ ︸
x
=
d2...
dn−2
dn−1
︸ ︷︷ ︸
d
H is symmetric tridiagonal and diagonally dominant. Using
Gaussian elimination to solve this system, we get
a2 h2
a3 h3
. . . . . .
an−2 hn−2
an−1
︸ ︷︷ ︸
A
s′′2s′′3...
s′′n−2
s′′n−1
︸ ︷︷ ︸
x
=
b2
b3...
bn−2
bn−1
︸ ︷︷ ︸
b
, (10)
where
a2 = 2(h1 + h2), ai = 2(hi−1 + hi)−h2i−1
ai−1
;
255
b2 = d2, bi = di −hi−1bi−1
ai−1
; for i = 3 : n− 1 .
Equation (10) can be solved by the recursion
s′′n−1 =bn−1
an−1
, s′′i =bi − his
′′i+1
ai, i = n− 2 : −1 : 2 .
Having computed s′′i for i = 2 : n− 2, s′i for i = 1 : n− 1 may
now be computed from (6).
Once this information is available s(x) may be computed for
any x ∈ [a, b] from (5).
This process may seem involved, but it is really the solution of
a set of linear equations.
The cubic spline is not often used for graphical applications,
even though it has the desirable properties of continuity in the
function values and the first and second derivatives.
For graphics, the major drawback of the cubic spline is the fact
that it is non-local — a change in the position of any knot affects
the whole curve.
In many graphical applications, the ability to distort a curve
locally but leaving far points unchanged is important.
256
Parametric cubic curves
Cubic polynomials are frequently used for piecewise interpola-
tion of sets of points because they are the lowest order polyno-
mials which can guarantee continuity of both the function and
the first derivative when interpolating two points.
It is possible to represent polynomial functions in parametric
form [x = Px(t), y = Py(t), z = Pz(t)], rather than in the more
familiar implicit form [P (x) = 0].
A parametric cubic polynomial curve segment
Q(t) = [x(t) y(t) z(t)], (0 ≤ t ≤ 1) has the following form:
x(t) = axt3 + bxt
2 + cxt + dx
y(t) = ayt3 + byt
2 + cyt + dy
z(t) = azt3 + bzt
2 + czt + dz
=[
t3 t2 t 1]
·
ax ay az
bx by bz
cx cy cz
dx dy dz
Defining T as the vector T = [t3 t2 t 1], the parametric equa-
tions can be rewritten as:
Q(t) = [x(t) y(t) z(t)] = T · Cwhere C is the parametric coefficient matrix.
257
Without loss of generality, the parameter t can be restricted to
the interval [0, 1].
In this form, it is easy to calculate the derivatives of Q(t). For
the first derivative,
ddtQ(t) = Q′(t) = d
dt[T · C] = [3t2 2t 1 0] · C
=[
3axt2 + 2bxt + cx 3ayt
2 + 2byt + cy 3azt2 + 2bzt + cz
]
If two curve segments joined at a point (knot) have the same
value for their first derivatives, they are said to be C1 continu-
ous, (If their first n derivatives have the same value at a knot,
the two curve segments are Cn continuous.)
If two curve segments joined at a point (knot) have first deriva-
tives with the same direction, but different magnitudes, they
are said to be G1 continuous; Cn ⇒ Gn, but not conversely.
Although G1 continuity is not as strong a constraint as C1 con-
tinuity, both types of constraints produce curves which appear
smooth. (The G refers to “geometry.”)
258
Constraints
A curve segment Q(t) is defined by constraints on such param-
eters as the end points, tangent vectors, continuity, at knots,
etc.
Cubic polynomials have four coefficients, so four constraints are
required. The constraints can be made explicit by rewriting the
coefficient matrix C as C = M · G where G is a 4-element
matrix of geometric constraints called the geometry matrix.
G and M are constant matrices, and
Q(t) =[
x(t) y(t) z(t)]
= T ·M ·G
=[
t3 t2 t 1]
·
m11 m12 m13 m14
m21 m22 m23 m24
m31 m32 m33 m24
m41 m42 m43 m44
·
G1
G2
G3
G4
Looking at only the x component, x(t) = Gx ·M · T ,x(t) = (t3m11 + t2m21 + tm31 +m41)g1x +
(t3m12 + t2m22 + tm32 +m42)g2x +
(t3m13 + t2m23 + tm33 +m43)g3x +
(t3m14 + t2m24 + tm34 +m44)g4x
The y and z components are obtained similarly.
259
This is really a weighted sum of the components of the geometry
matrix, where the weights (called blending functions, B =
T ·M) are cubic polynomials.
This is similar to the piecewise linear approximation (with linear
blending functions) where
x(t) = (1− t)g1x + tg2x
y(t) = (1− t)g1y + tg2y
z(t) = (1− t)g1z + tg2z
Here the blending functions are linear, but there are many pos-
sible generalizations.
The matrices G and M specify the geometric constraints and
the blending functions, B = T ·M , (also called basis polyno-
mials) respectively. There are many possible forms for the basis
polynomials.
In the following, we will see how to determine the basis matrix,
M , for various parametric cubic curves.
260
Hermite curves
The first parametric cubic curve we will look at is the Hermite
curve.
The Hermite form of the cubic polynomial is specified by the
two endpoints (P1 and P4) and their tangents (R1 and R4).
To find the Hermite basis matrixMH , we can solve the 4 equa-
tions relating the four constraints to the polynomial coefficients.
For the x component only, the Hermite geometry vector is
GHx = [P1x P4x R1x R4x ]T and
x(t) = axt3 + bxt
2 + cxt + dx = T · MH · GHx· =[
t3 t2 t 1] ·
MH ·GHx
so, substituting for the constraints (t = 0 and t = 1) directly,
x(0) = P1x = [0 0 0 1] ·MH ·GHx
x(1) = P4x = [1 1 1 1] ·MH ·GHx
Differentiating the expression for x(t) gives
x′(t) =[
3t2 2t 1 0]
·MH ·GHx
and substituting for t
x′(0) = R1x = [0 0 1 0] ·MH ·GHx
x′(1) = R4x = [3 2 1 0] ·MH ·GHx
Similar results are obvious for the y and z components.
261
Combining the four constraints in matrix form,
P1
P4
R1
R4
x
= GHx =
0 0 0 1
1 1 1 1
0 0 1 0
3 2 1 0
·MH ·GHx
For this to be true, MH must be the inverse of the constraint
matrix, so
MH =
0 0 0 1
1 1 1 1
0 0 1 0
3 2 1 0
−1
=
2 −2 1 1
−3 3 −2 −1
0 0 1 0
1 0 0 0
Recalling that the blending function is B = T ·M , and
Q(t) = T ·M ·G, the Hermite blending functions BH can be
calculated as the weights of the geometry elements
Q(t) = BH ·GH = T ·MH ·GH
= (2t3 − 3t2 + 1)P1 + (−2t3 + 3t2)P4 +
(t3 − 2t2 + t)R1 + (t3 − t2)R4
Since the Hermite curves are linear combinations of the elements
of the geometry vector, they can be transformed by applying
the blending functions to the transformed geometry vector.
Therefore, the Hermite polynomials are invariant under transla-
tion, rotation, and scaling (but not perspective transformation.)
262
Following is a plot of the Hermite blending functions which are
multiplied by each of the geometry elements:
HP1 HP4
HR1
HR4
Hermite blending functions
-0.2
0
0.2
0.4
0.6
0.8
1
0.40.20 0.6 0.8 1
Note that the weighting functions have a certain symmetry.
Also, the weighting for one of the derivatives (R4) is negative.
263
Bezier curves
The Hermite form of the cubic polynomial requires the deriva-
tives at the endpoints. The Bezier form specifies the endpoint
vectors using two intermediate points (P2 and P3) which do not
lie on the curve.
The end point tangent vectors are defined by
Q′(0) = 3(P2 − P1) = R1 and Q′(1) = 3(P4 − P3) = R4.
The Bezier geometry matrix GB is GB = [P1 P2 P3 P4]T
This can be written as a Hermite geometry matrix as follows:
GH =
P1
P4
R1
R4
=
1 0 0 0
0 0 0 1
−3 3 0 0
0 0 −3 3
·
P1
P2
P3
P4
= MHB · GB
To find the Bezier basis matrix, MB, from the Hermite form,
Q(t) = T ·MH ·GH = T ·MH · (MHB ·GB)
= T · (MH ·MHB) ·GB = T ·MB ·GB
So, MB = MH ·MHB =
−1 3 −3 1
3 −6 3 0
−3 3 0 0
1 0 0 0
264
Q(t) = T ·MB ·GB is
Q(t) = (1− t)3P1 + 3t(1− t)2P2 + 3t2(1− t)P3 + t3P4
The four blending polynomials BB = T · MB are called the
Bernstein polynomials.
0.2
0.4
0.6
0.8
1
0.4 0.6 10
B
B BB
2
1
34
0.2 0.8
Cubic Bernstein polynomials
They have the property that they are non-negative and sum to
1 at any point in [0, 1], so Q(t) is really a weighted average of
the control points. This has the consequence that the curve is
bounded by the four control points; the control points form a
convex hull for the curve segment. (This is useful for clipping
the curves.)
265
Joining curve segments
Both the Hermite and Bezier curves can be joined at a knot.
C1 or G1 continuity can be assured only if made a specific
constraint. For Hermite polynomials, the first derivatives at
the knot (R4 and R5) must be equal for C1 continuity, and
have the same direction for G1 continuity.
P
P
P
R
1
14
7
R4
R
7
R5
For Bezier curves, for G1 continuity, points P3, P4, and P5 must
be collinear. For C1 continuity, P4 − P3 must equal P5 − P4.
P
P
P1
4
7
P
P
P
2
3
5P6
Higher order Hermite and Bezier curves can be derived in a
similar manner, but are rarely used.
266
Cubic B-splines
These are curve segments which approximate a series of m+ 1
control points P0, P1, . . . , Pm where m ≥ 3 by a set of m − 2
cubic polynomial segments Q3, Q4, . . . , Qm.
For each i > 3 there is a knot between the value Qi−1 and Qi
at parameter value ti. Including the endpoints at parameters
t3 and tm+1, there are m− 1 knots.
If the spacing of the knots is uniform (equal separations in t),
then the spline is said to be a uniform B-spline.
Each curve segment is defined by four of the control points;
Qi(t), ti ≤ t < ti+1 is defined by Pi−3, Pi−2, Pi−1 and Pi.
P0
P7
t3
t4
t5 t6
t7t8
t9
t10
P6P5
P2
P1 P3
P8
P9
P4
Q3
Q4
Q5
Q6
Q7
Q8
Q9
267
The B-spline geometry matrix for segment Qi is
GBsi =[
Pi−3 Pi−2 Pi−1 Pi
]T
Defining Ti =[
(t− ti)3 (t− ti)
2 (t− ti) 1]
then Qi(t) = Ti ·MBs ·GBsi, ti ≤ t < ti+1
The B-spline basis matrix MBs is the following:
MBs = 1/6
−1 3 −3 1
3 −6 3 0
−3 0 3 0
1 4 1 0
The B-spline blending functions BBs are obtained as Ti ·MBs.
This gives BBs =[
BBs−3BBs−2
BBs−1BBs0
]
where
BBs−3= 1/6(−(t− ti)
3 + 3(t− ti)2 − 3(t− ti) + 1)
simplifying,
BBs−3= 1/6(1− (t− ti)
3)
Similarly,
BBs−2= 1/6(3(t− ti)
3 − 6(t− ti)2 + 4)
BBs−1= 1/6(−3(t− ti)
3 + 3(t− ti)2 + 3(t− ti) + 1)
BBs0 = 1/6(t− ti)3
268
Replacing t− ti by t and replacing the interval [ti, ti+1] by [0, 1]
BBs =[
BBs−3BBs−2
BBs−1BBs0
]
=1
6
[
(1− t)3 (3t3 − 6t2 + 4) (−3t3 + 3t2 + 3t + 1) t3]
, 0 ≤ t < 1
03
2 1
1/6
2/6
3/6
4/6
5/6
10.2 0.4 0.6 0.80
B B
B
Bs-
B
Bs-
B-spline blending functions
Bs-Bs-
The uniform B-spline is C2 continuous at the knots.
Note that the blending functions are non-negative and sum to
1, so B-splines also have the convex hull property.
Because three functions are non-zero at the endpoints, it does
not interpolate any of its control points, unless the points are
replicated. Then, however, the order of the polynomial segment
is reduced.
269
Non-uniform B-Splines
A non-uniform spline has knot values which are not uniformly
distributed in the parameter t. In this case, the blending func-
tions are not fixed, but vary from curve segment to segment.
For the non-uniform cubic B-spline, with control points P0 to
Pm, the knot value sequence is a non-decreasing sequence of
values t0 to tm+4. (Note that there are 4 more knots than control
points.)
The fact that the interval is non-decreasing allows successive
knots to be equal; knots may have a multiplicity > 1. This
implies that there may be a curve segment which reduces to a
point.
A curve segmentQi is defined by control points Pi−3, Pi−2,Pi−1,Pi
and by blending functions Bi−3,4(t),Bi−2,4(t),Bi−1,4(t),Bi,4(t),
as the weighted sum
Qi(t) = Pi−3Bi−3,4(t)+Pi−2Bi−2,4(t)+Pi−1Bi−1,4(t)+PiBi,4(t)
(the subscript 4 refers to the order of the curve, and is 1 plus
the degree — 4 for a cubic curve.)
The blending functions are defined recursively in terms of the
lower order blending functions.
270
In general, the blending functions Bi,k(t) are defined as follows:
Bi,1(t) =
1, ti ≤ t < ti+1
0, otherwise
and if k > 1, and tk−1 ≤ t < tn+1
Bi,k(t) =t− ti
ti+k−1 − tiBi,k−1(t) +
ti+k − t
ti+k − ti+1
Bi+1,k−1(t)
For a cubic B-spline, the recurrence follows:
Bi,1(t) =
1, ti ≤ t < ti+1
0, otherwise
Bi,2(t) =t− ti
ti+1 − tiBi,1(t) +
ti+2 − t
ti+2 − ti+1
Bi+1,1(t),
Bi,3(t) =t− ti
ti+2 − tiBi,2(t) +
ti+3 − t
ti+3 − ti+1
Bi+1,2(t),
Bi,4(t) =t− ti
ti+3 − tiBi,3(t) +
ti+4 − t
ti+4 − ti+1
Bi+1,3(t).
Again, the blending functions are non-negative and sum to 1 so
the convex hull property holds.
It is possible for the denominators above to be 0 if the multi-
plicity of a knot is > 1; in this case the value is defined to be
0, (i.e., division by 0 is defined to yield 0.)
271
Non-uniform rational B-splines (NURBS)
It is possible to define all of the curves seen so far using ra-
tional polynomials. They can be thought of as polynomials in
homogeneous coordinates:
x(t) =X(t)
W (t), y(t) =
Y (t)
W (t), z(t) =
X(t)
W (t)
Here, X(t), Y (t), and Z(t) are polynomials with control points
defined in homogeneous coordinates.
Any non-rational polynomial can be transformed to a rational
polynomial by setting W (t) = 1.
NURBS, being defined in a homogeneous coordinate system, are
invariant under rotation, translation, scaling, and perspective
transformation (subject to renormalization).
Unlike other splines, NURBS can also represent conic sections
exactly.
272
Parametric bicubic surfaces
Cubic polynomials are also frequently used for piecewise inter-
polation of sets of points in a surface, for reasons similar to
those for curves.
In this case, the polynomials are functions of two parameters,
say, s and t.
For a parametric curve, the general equation is of the form
Q(s) = S · M · G, where the geometry matrix G is constant.
(Here we have used s as the parameter, for notational conve-
nience.)
If G is allowed to vary, say as a function of t,
Q(s, t) = S ·M · [G1(t) G2(t) G3(t) G4(t)]T
This would describe a family of curves, one for each value of t.
If each of the Gi(t) functions is cubic, then the resulting poly-
nomial can be represented as Gi(t) = T ·M ·Gi where
Gi = [gi,1 gi,2 gi,3 gi,4]T (i.e., each can be represented as a
vector with four components, corresponding to coefficients a, b,
c, and d of the polynomial.
Transposing the equation Gi(t) = T ·M ·Gi yields
Gi(t) = GTi ·MT · T T = [gi,1 gi,2 gi,3 gi,4] ·MT · T T
273
Substituting this in the original expression
Q(s, t) = S ·M · [G1(t) G2(t) G3(t) G4(t)]T ,
Q(s, t) = S ·M ·
g11 g12 g13 g14
g21 g22 g23 g24
g31 g32 g33 g34
g41 g42 g43 g44
·MT · T T
or
Q(s, t) = S ·M ·G ·MT · T T 0 ≤ s, t ≤ 1
Considering the x component only,
x(s, t) = S ·M ·Gx ·MT · T T
and the y and z components are similar.
274
Hermite surfaces
Like Hermite curves, Hermite surfaces are completely specified
by their geometry matrix — in this case, the 4× 4 matrix GH .
Considering only the x component, for the Hermite curve, we
had x(s) = S ·MH ·GHx
Rewriting (and noting that GHx is a function of t)
x(s, t) = S·MH ·GHx(t) = S·MH ·[
P1x(t) P4x(t) R1x(t) R4x(t)]T
P1x(t), and P4x(t) are the x components of the start and end
points of the curve over parameter s; R1x(t) and R4x(t) are the
derivatives at those points. (Note that 0 ≤ t ≤ 1, so there in a
continuum of those end points.)
Rewriting these constraints in Hermite form,
P1x(t) = T ·MH ·[
g11 g12 g13 g14
]T
x,
P4x(t) = T ·MH ·[
g21 g22 g23 g24
]T
x,
R1x(t) = T ·MH ·[
g31 g32 g33 g34
]T
x,
R4x(t) = T ·MH ·[
g41 g42 g43 g44
]T
x.
They can be written as a single equation[
P1x(t) P4x(t) R1x(t) R4x(t)]
= T ·MH ·GTHx
275
GHx =
g11 g12 g13 g14
g21 g22 g23 g24
g31 g32 g33 g34
g41 g42 g43 g44
x
Transposing both sides of the previous equation gives[
P1x(t) P4x(t) R1x(t) R4x(t)]T
= GHx ·MTH · T T
Substituting this in the expression for x(s, t) gives
x(s, t) = S ·MH ·GHx ·MTH · T T .
Similarly, for y(s, t) and z(s, t)
y(s, t) = S ·MH ·GHy ·MTH · T T
z(s, t) = S ·MH ·GHz ·MTH · T T
The matrix GHx can be calculated as:
GHx =
x(0, 0) x(0, 1) ∂∂sx(0, 0) ∂
∂sx(0, 1)
x(1, 0) x(1, 1) ∂∂sx(1, 0)
∂∂sx(1, 1)
∂∂tx(0, 0)
∂∂tx(0, 1)
∂2
∂s∂tx(0, 0)∂2
∂s∂tx(0, 1)∂∂tx(1, 0)
∂∂tx(1, 1)
∂2
∂s∂tx(1, 0)∂2
∂s∂tx(1, 1)
and similarly for GHy and GHz .
276
Bezier surfaces
In a similar manner to that used for Hermite surfaces, the bicu-
bic Bezier surfaces can be shown to be:
x(s, t) = S ·MB ·GBx ·MTB · T T
y(s, t) = S ·MB ·GBy ·MTB · T T
z(s, t) = S ·MB ·GBz ·MTB · T T
B-spline surfaces
Again similarly, for B-splines,
x(s, t) = S ·MBs ·GBsx ·MTBs · T T
y(s, t) = S ·MBs ·GBsy ·MTBs · T T
z(s, t) = S ·MBs ·GBsz ·MTBs · T T
Bicubic B-splines are also C2 continuous.
Other B-spline curves can also extended to surfaces similarly.
277
Normals to surfaces
Normals to surfaces can readily be found as the cross product
of the two tangent vectors to the surface. (The tangent vectors∂∂sQ(s, t) and ∂
∂tQ(s, t) are parallel to the surface at point (s, t).)
The tangent vectors can be calculated as
∂
∂sQ(s, t) =
∂
∂s(S ·M ·G ·MT · T T ) = ds(S) ·M ·G ·MT · T T
=[
3s2 2s 1 0]
·M ·G ·MT · ST
Similarly,
∂
∂tQ(s, t) = S ·M ·G ·MT ·
[
3t2 2t 1 0]T
and the normal is
∂
∂sQ(s, t)× ∂
∂tQ(s, t) = [yszt − ytzs zsxt − ztxs xsyt − xtys]
where xs is the x-component of the s tangent vector, . . .
The surface normal is a fifth degree polynomial of the two vari-
ables s and t.
278
Curves in OpenGL — Evaluators
OpenGL directly supports the drawing of curved surfaces di-
rectly through the use of evaluators.
Evaluators can construct curves and surfaces based on the Bern-
stein basis polynomials. (This includes Bezier curves and patches,
and B-splines.)
In order to draw curves and surfaces using other basis polyno-
mials (e.g., Hermite polynomials) the user program must trans-
form that basis to a Bernstein basis.
Evaluators are not difficult to use. Looking at the case of one
dimensional curves first, the following steps are performed:
• The function is defined with glMap1*()
• The function is enabled with glEnable()
(Both functions are usually called as part of the initializa-
tion.)
• The function is evaluated at a series of points using glEvalCoord1()
between a glBegin() and glEnd() block in the display()
function. [This is similar to using glVertex*().]
glEvalCoord1() is usually called repeatedly.
279
The function glMap1() defines a one-dimensional evaluator
that evaluates the Bernstein polynomial of order n + 1, where
n is the degree of the polynomial.
void glMap1fd(GLenum target, TYPE t1, TYPE t2, GLint
stride, GLint order, const TYPE *points);
target specifies what the control points represent (see the ta-
ble following), t1 and t2 specify the range for the variable t,
stride specifies the number of floats or doubles between the
beginning of one control point and the beginning of the next
one in the data structure referenced in points. This allows con-
trol points to be embedded in arbitrary data structures. (See
the man pages for more information.) order is the order of the
polynomial. *points is a pointer to the list of control points.
Parameter Meaning
GL_MAP1_VERTEX_3 x, y, z vertex coordinates
GL_MAP1_VERTEX_4 x, y, z, w vertex coordinates
GL_MAP1_INDEX color index
GL_MAP1_COLOR_4 R, G, B, A
GL_MAP1_NORMAL normal coordinates
GL_MAP1_TEXTURE_COORD_1 s texture coordinates
GL_MAP1_TEXTURE_COORD_2 s, t texture coordinates
GL_MAP1_TEXTURE_COORD_3 s, t, r texture coordinates
GL_MAP1_TEXTURE_COORD_4 s, t, r, q texture coordinates
280
void glEvalCoord1{fd}(TYPE t);
Evaluates the enabled one-dimensional maps. The argument t
is the value of the domain coordinate.
For evaluated vertices, generation of values for color, color in-
dex, normal vectors, and texture coordinates is done by evalu-
ation. Calls to glEvalCoord1() do not use the current values
for color, color index, normal vectors, and texture coordinates,
and leaves those values unchanged.
The following code is from the program bezcurve.c:
GLfloat ctrlpts[4][3] = { /* define control points */
{ -4.0, -4.0, 0.0}, { -2.0, 4.0, 0.0},
{2.0, -4.0, 0.0}, {4.0, 4.0, 0.0}};
int MAX_T = 35;
void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glShadeModel(GL_FLAT);
glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,4, &ctrlpts[0][0]);
glEnable(GL_MAP1_VERTEX_3);
}
281
void display(void)
{
int i, t;
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINE_STRIP);
for (t = 0; t <= MAX_T; t++)
glEvalCoord1f((GLfloat) t/ (float)MAX_T);
glEnd();
/* Display the control points as dots. */
glPointSize(5.0);
glColor3f(0.0, 0.0, 1.0);
glBegin(GL_POINTS);
for (i = 0; i < 4; i++)
glVertex3fv(&ctrlpts[i][0]);
glEnd();
glFlush();
}
282
It is common for coordinate values to be evenly spaced, as they
were in the previous example.
In this case, the functions glMapGrid1() and glEvalMesh1()
are useful.
void glMapGrid1{fd}(GLint n, TYPE t1, TYPE t2);
Defines a grid that goes from t1 to t2 in n evenly spaced steps.
void glEvalMesh1(GLenum mode, GLint p1, GLint p2);
Applies the currently defined map grid to all enabled evaluators.
The mode can be either GL_POINT or GL_LINE, depending on
whether points or a connected line is required. The call has
exactly the same effect as issuing a glEvalCoord1() for each
of the steps from p1 to p2, inclusive.
283
The previous code could be rewritten as:
void init(void)
{
glClearColor(1.0, 1.0, 1.0, 0.0);
glShadeModel(GL_FLAT);
glMap1f(GL_MAP1_VERTEX_3,0.0,1.0,3,4,&ctrlpts[0][0]);
glMapGrid1f(MAX_T, 0.0, 1.0);
glEnable(GL_MAP1_VERTEX_3);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 0.0, 0.0);
glEvalMesh1(GL_LINE, 0, MAX_T);
/* Display control points as dots. */
.
.
.
glFlush();
}
284
Two dimensional evaluators
For two dimensional evaluators, the process is similar, except
that there are two parameters, s and t:
• Define the evaluator(s) with glMap2*()
• Enable them by passing the appropriate value to glEnable()
• Invoke them either by calling glEvalCoord2() between a
glBegin and glEnd pair or by specifying and applying a
mesh with glMapGrid2() and glEvalMesh2().
void glMap2{fd}(GLenum target, TYPE s1, TYPE s2, GLint
sstride, GLint sorder, TYPE t1, TYPE t2, GLint tstride,
GLint torder, TYPE *points)
The target parameter is as shown in the earlier table, except
MAP1 is replaced by MAP2. s1, s2, t1, and t2 are the ranges
for s and t. sstride and tstride are the “distance” to the
next s and t values in the array of control points. sorder and
torder are the order parameters, which need not be the same.
For example, to use the 4 × 4 subset starting at [20][20] of
the following array
GLfloat ctrlpts[100][100][3];
tstride should be 3 and sstride should be 100.
*points should be set to &ctrlpts[20][20][0].
285
void glEvalCoord2{fd} (TYPE s, TYPE t);
Causes evaluation of the enabled two-dimensional maps. The
arguments s and t are values for the domain coordinates. If
either of the vertex evaluators is enabled (GL_MAP2_VERTEX_3
or GL_MAP2_VERTEX_4), then the normal to the surface is com-
puted analytically. This normal is associated with the gener-
ated vertex if automatic normal generation has been enabled
using glEnable(GL_AUTO_NORMAL). If it is disabled, the cor-
responding enabled normal map is used. If no such map exists,
the current normal is used.
void glMapGrid2{fd}(GLint ns, TYPE s1, TYPE s2,
GLint nt, TYPE t1, TYPE t2);
void glEvalMesh2(GLenum mode, GLint i1, GLint i2,
GLint j1, GLint j2);
Defines a two-dimensional map grid that goes from s1 to s2
in ns evenly spaced steps, goes from t1 to t2 in nt steps
(glMapGrid2*()), and then applies this grid to all enabled
evaluators (glEvalMesh2()). In glEvalMesh2()the mode pa-
rameter can be GL_FILL as well as GL_POINT or GL_LINE.
GL_FILL generates filled polygons.
286
The following examples are from bezmesh.c:
GLfloat ctrlpts[4][4][3] = {
{ {-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
{ 0.5, -1.5, -1.0}, { 1.5, -1.5, 2.0} },
{ {-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
{ 0.5, -0.5, 0.0}, { 1.5, -0.5, -1.0} },
{ {-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
{ 0.5, 0.5, 3.0}, { 1.5, 0.5, 4.0} },
{ {-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
{ 0.5, 1.5, 0.0}, { 1.5, 1.5, -1.0} }
};
void myinit(void)
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glEnable(GL_DEPTH_TEST);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
0, 1, 12, 4, &ctrlpts[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
initlights(); /* for lighted version only */
}
287
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(85.0, 1.0, 1.0, 1.0);
glEvalMesh2(GL_FILL, 0, 20, 0, 20);
glPopMatrix();
glFlush();
}
288
Using NURBS objects
Using NURBS objects is relatively straightforward (the hard
part is actually capturing the object to be rendered):
• If lighting is to be used with a NURBS surface, call glEnable()
with argument GL_AUTO_NORMAL to generate surface nor-
mals automatically.
• Use gluNewNurbsRenderer() to create a pointer to a
NURBS object. which is referred to when creating the
NURBS curve or surface.
• If desired, call gluNurbsProperty() to choose rendering
values such as the maximum size of lines or polygons that
are used to render the NURBS object. gluNurbsProperty()
can also enable a mode where the tessellated geometric data
can be retrieved through a user-defined callback function.
• Call gluNurbsCallback() to be notified when an error is
encountered. (Error checking may slightly degrade perfor-
mance but is highly recommended.) gluNurbsCallback()
may also be used to register the functions to call to retrieve
the tessellated geometric data.
• Start the curve or surface by calling gluBeginCurve() or
gluBeginSurface().
289
• Generate and render the curve or surface. Call gluNurbsCurve()
or gluNurbsSurface() at least once with the control points
(rational or nonrational), knot sequence, and order of the
polynomial basis function for the NURBS object. These
functions may be called again to specify surface normals
and/or texture coordinates.
• Call gluEndCurve() or glulEndSurface() to complete
the curve or surface.
These functions are detailed in the man pages.
The following program (surface.c) shows an example of the
use of a NURBS object to render a surface. The surface is a
small symmetrical hill with control points ranging from -3.0 to
3.0. The knot sequence is non-uniform, with a multiplicity of 4
at each endpoint (making it equivalent to a Bezier patch):
/* Program surface.c - using NURBS */
GLfloat ctrlpts[4][4][3];
GLUnurbsObj *theNurb;
290
/* Initialize the control points */
void init_surface(void)
{
int s, t;
for (s = 0; s < 4; s++) {
for (t = 0; t < 4; t++) {
ctrlpts[s][t][0] = 2.0*((GLfloat)s - 1.5);
ctrlpts[s][t][1] = 2.0*((GLfloat)t - 1.5);
if ((s == 1 || s == 2) && (t == 1 || t == 2))
ctrlpts[s][t][2] = 3.0;
else
ctrlpts[s][t][2] = -3.0;
}
}
}
void CALLBACK nurbsError(GLenum errorCode)
{
const GLubyte *estring;
estring = gluErrorString(errorCode);
fprintf(stderr, "NURBS ERROR: %s\n", estring);
exit(0);
}
291
void myinit(void)
{
/* Initialize lighting and material properties */
GLfloat mat_diffuse[] = { 0.7, 0.7, 0.7, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 100.0 };
.
.
glEnable(GL_AUTO_NORMAL);
glEnable(GL_NORMALIZE);
init_surface();
theNurb = gluNewNurbsRenderer();
gluNurbsProperty(theNurb,GLU_SAMPLING_TOLERANCE,25.0);
gluNurbsProperty(theNurb, GLU_DISPLAY_MODE, GLU_FILL);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTranslatef (0.0, 0.0, -4.0);
}
292
void display(void)
{
GLfloat knots[8] = {0.0,0.0,0.0,0.0,1.0,1.0,1.0,1.0};
int i, j;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(330.0, 1.,0.,0.);
glScalef (0.25, 0.25, 0.25);
gluBeginSurface(theNurb);
gluNurbsSurface(theNurb, 8, knots, 8, knots,
4 * 3, 3, &ctrlpts[0][0][0],
4, 4, GL_MAP2_VERTEX_3);
gluEndSurface(theNurb);
glEnable(GL_LIGHTING);
glPopMatrix();
glutSwapBuffers();
293
294
Lighting and color
Light and vision are intimately related. The way in which our
eyes “see” light is fundamental to the reproduction of color (and
monochrome) images.
Intensity resolution
The eye is sensitive to relative differences in the intensity of
light, implying that the eye has a logarithmic response to light
intensity. Small relative intensity changes are not detectable;
the threshold for detection in the range of a ratio of 1.01.
The eye has a very wide dynamic range, and for low light levels
the response is different than at high light levels. (At the lowest
light levels, our vision is black and white only; there is no color
sensation.)
A natural question is “how many intensity levels can be seen?”
It makes sense to look at the dynamic range of the medium
to answer this. The best films have a dynamic range of around
1000, as does a very good monitor. (The eye itself has a dynamic
range of about 106.)
Solving (1.01)n = 1000 gives n ≈ 700 for film.
(1.01)n = 106 gives n ≈ 1400 for the eye.
295
256 intensity values, distributed logarithmically, gives a dy-
namic range of (1.01)256 = 12.8, which is about the dynamic
range for newsprint.
Generally, then, about 10 bits of intensity are required to pro-
duce a very high quality dynamic range.
Spatial resolution
The eye also has a minimum spatial resolution, below which two
distinct points “blur together” and become indistinguishable.
This is useful in the printing process, and in the design of CRT’s,
because it allows the eye to be “fooled” into seeing different
intensities by displaying small filled patterns.
For example, to display 5 levels of gray on a device capable of
only the solid colors black and white (a bitonal device), a grid
of 4 “pixels” might be used, as follows:
Devices like laser printers are bitonal. Photographs and CRT
displays are capable of displaying “shades of gray” directly, and
can give much better picture quality with larger pixels.
296
297
Color
Color perception is often referred to in terms of three quantities
— hue, saturation, and lightness (or brightness).
Hue is the actual color (red, yellow, green, purple, etc.)
Saturation is the intensity of the color — how far it is from a
gray of equal intensity. (Pink is red with low saturation.)
Lightness (or brightness, for a luminous object) is the perceived
intensity of the light from the object, regardless of the color.
This model is usually called the HSV model, where V (for
Value) is the lightness component, Hue is measured in degrees,
−1 ≤ S ≤ 1 and 0 ≤ V ≤ 1.
HS
yellow
red (0)
magenta
green (120)
cyan white
0.0
1.0
V
black
Blue (240)
298
In 1931, theCommission Internationale de l’ Eclairage (CIE)
defined a standard for the mixing of colors which is still in use.
0.1 0.2 0.3 0.4 0.5 0.6 0.7
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
400
500
520
560
580
600
700
x
y
B
A
CD
E
G
F480
540
The dominant color of the point at A is that of color B. The
dominant color of F is the complement of the color B. D and E
are complementary colors.
A line in this diagram represents the colors obtainable by mixing
together the colors at its endpoints. The interior of a triangle
represents all the colors attainable by mixing together the colors
at the vertices.
299
The human eye
The structure of the human eye is shown in the following dia-
gram. Note that it is a relatively simple optical system consist-
ing of a lens, and a “screen” (the retina) onto which an image is
projected. The center of the field of view, the macula or fovea,
has the best visual acuity.
The retina is made up of two types of cells — rods and cones.
The rods are more sensitive than the cones, but do not detect
color. The macula has a high density of cones, and the cones
here are smaller (accounting for the visual acuity in this area).
The rods are more predominant in the periphery.
300
There are 3 types of cones, each sensitive to a different part of
the spectrum. Combinations of the different spectral sensitivi-
ties of the cones are what allows us to see in color. (Color blind
people are deficient in one or more of the cone types.)
.02
.04
.06
.08
.10
.12
.14
.18
400 440 480 520 560 600 720680640
.20
in the human eyeColor response of the 3 types of cones
Wavelength (nm)
A mixture of the three primary colors red, green, and blue in
varying intensities allows us to produce visually the sensation
of almost any color.
301
Physical properties of light
Light consists of photons — “particles” with no mass which
travel at the speed of light. They have energy, and one measure
of this energy is the “wavelength” of the light.
To a very good approximation, light travels in straight lines,
and behaves much like a particle. (In some circumstances, light
has wave properties — interference and diffraction effects —
but these are rarely important in computer graphics.)
Light may be “bent” or refracted in transparent substances,
and the degree of bending, or refraction, is related to a quantity
called the refractive index.
When light is incident on a shiny, flat surface, it is reflected.
The reflected light leaves the surface at an angle such that the
angle between the incident light and a normal to the surface is
equal to the same angle for the reflected light.
normal
incident light reflected light
refracted lightincident light
302
Light can also be absorbed by materials. This gives the material
color — light which is not absorbed is reflected or transmitted,
giving the material its color. For example if a transparent mate-
rial, say glass, has chromium added to it, it appears red, because
the red light is transmitted but other colors are absorbed.
On the other hand, if an opaque material absorbs red, it will
appear to have the color cyan. It it absorbs blue, it will appear
yellow; if it absorbs green, then magenta. (Yellow, cyan, and
magenta dyes are often used in printing instead of red, green,
and blue.)
A black body absorbs all the light that it receives.
Materials may also emit light. Generally, light is emitted when
material is heated to a high enough temperature (“black body
radiation”) e.g., an incandescent light.
Light is also emitted when some of its atoms or molecules are
“excited” — energy is added by, say, an electric field. Examples
of this are fluorescent lights, and LEDs.
303
Physical properties of surfaces
Surfaces have different characteristics with their interaction with
light. Some surfaces are “shiny” and reflect most of the light
incident on the surface. Others are dull and reflect little light
at all.
Surfaces have “color” — the color comes from the light incident
on the surface, which may reflect some spectral components,
and absorb others. A red painted cube reflects red light, but
absorbs green and blue light. If illuminated by pure green or
blue light, the cube would appear black.
Some surfaces, like the CRT screen, for example, emit their own
light, in varying colors and intensities.
Characteristics like “roughness” are also important in determin-
ing the interaction between light and a surface. Some materials
are translucent, in varying degrees, or may have a transparent
coating which changes the appearance. (Often a wet rock is
more colorful than a dry rock, for example.)
Transparency may be wavelength dependent. Filters may trans-
mit red light, say, and absorb green and blue light.
304
Light sources — illumination
The light by which we see can come from a number of sources,
with many different properties. The normal background light
in an indoor scene is white light (ignoring such characteristics
as “color temperature” which relate to the spectral properties
of the light), and is generally not very directional, since it is
scattered from many surfaces — walls, floor, etc.
This is usually called ambient light, and its main property is
that it seems to come from all directions. When ambient light
interacts with a surface, it is scattered in all directions.
Light which is strongly directional (bright sunlight, or a laser
beam) is said to be specular. When interacting with a shiny
surface, most of this light is reflected as from a mirror. (Specu-
larity is really a material property, but in graphics the term is
also applied to light sources.)
The diffuse component of light comes from one direction, so it
is brighter if it shines directly on a surface than if it meets a
surface at an angle, but it is scattered at all angles from the
surface.
305
DiffuseAmbient Specular
ambient — rays come from and scatter into all directions
diffuse — rays come from one direction, scatter into all direc-
tions
specular — rays come from one direction, reflect into one di-
rection
For specular light, the normal to the surface is used to calculate
the direction into which the reflected light is scattered.
Light also has the property that it reduces in intensity with
distance from the source. In particular, light from a point source
decreased in intensity proportional to the square of the distance
from the source.
For a long linear light source, the intensity decreases propor-
tional to the distance from the source.
Other light sources (spotlights, and other lights containing re-
flecting or lens elements) may have more complex functions.
306
Rendering lit scenes realistically
We require a simple model for lighting which allows us to model
both light sources and surface properties in a reasonable way.
Ideally, we would assign the appropriate physical properties to
each surface in the scene, and calculate the interaction of every
visible ray of light from the source to the viewer’s eye position.
This, of course, would produce a very realistic image (if all the
properties of a surface — specularity, roughness, transparency,
etc.) were well modelled, but would be very computationally
expensive.
This can be done, in fact, at least for relatively simple scenes,
and the method is called ray tracing. It can produce very
realistic images, including refraction, shadows and multiple re-
flections, but at very high computational cost.
OpenGL uses a relatively simple model for lighting. It defines
a set of properties for materials, a set of light sources, and a
lighting model. This model cannot model secondary effects like
shadows or reflected illumination, but it produces reasonable
illumination effects and is computationally efficient.
307
Lighting models in OpenGL
In order to add lighting to a scene, the following steps are re-
quired:
• Define normals at each vertex for every object.
• Create and position one or more light sources with glLight*().
(OpenGL supports at least 8 light sources).
• Select a lighting model; with glLightModel*(). This de-
fines the level of (global) ambient light, and the effective
location of the viewpoint (for the lighting calculations).
• Define the material properties for each object in the scene
with glMaterial*().
void glLight{if}(GLenum light, GLenum pname,
TYPE param);
void glLight{[if}v(GLenum light, GLenum pname,
TYPE *param);
create the light specified by GL_LIGHT0, GL_LIGHT1, . . . with
properties given by pname; param specifies the set of parameters
for the properties pname. (The scalar version can only set single
value parameters.)
308
Parameter name Default Value Meaning
GL_AMBIENT (0.0, 0.0, 0.0, 1.0) ambient light
intensity
GL_DIFFUSE (1.0, 1.0, 1.0, 1.0) diffuse light in-
tensity
GL_SPECULAR (1.0, 1.0, 1.0, 1.0) specular light
intensity
GL_POSITION (0.0, 0.0, 1.0, 0.0) (x, y, z, w) po-
sition of light
GL_SPOT_DIRECTION (0.0, 0.0, -1.0) (x, y, z) direc-
tion of spotlight
GL_SPOT_EXPONENT 0.0 spotlight expo-
nent
GL_SPOT_CUTOFF 180.0 spotlight cutoff
angle
GL_CONSTANT_ATTENUATION 1.0 see equation
following
GL_LINEAR_ATTENUATION 0.0
GL_QUADRATIC_ATTENUATION 0.0
The default values for GL_DIFFUSE and GL_SPECULAR are for
GL_LIGHT0 only. Other lights default to black (0.0, 0.0, 0.0, 1.0)
The tutorial lightposition shows a simple use of lighting and
the interaction with viewing position.
309
OpenGL treats a light source like a geometric primitive; it is af-
fected by the modelview matrix, and stored in eye coordinates.
The light source can be moved by changing the modelview ma-
trix. The projection matrix has no effect on a light source.
The attenuation is calculated from the expression
attenuation factor =1
kc + kld + kqd2
where d is the distance from the light source, and kc, kl, and
kq are the constant, linear, and quadratic attenuation terms,
respectively.
The OpenGL lighting model has four components:
1. The global ambient light intensity
2. The viewpoint position (local or at infinity)
3. Whether front and back surfaces should have different light-
ing calculations
4. Whether or not the specular color should be separated from
ambient and diffuse colors and applied after texturing.
All those properties can be set individually with the function
glLightModel*().
310
void glLightModel{if}(GLenum pname, TYPE param);
void glLightModel{if}v(GLenum pname, TYPE *param);
Again, pname specifies the property; param specifies the set of
parameters for the property pname.
In the following, GL_LIGHT_MODEL precedes the parameter name:
Parameter name Default Value Meaning
_AMBIENT (0.2, 0.2, 0.2, 1.0) ambient intensity for
scene
_LOCAL_VIEWER 0.0 how specular reflec-
tion angles are calcu-
lated
_TWO_SIDE 0.0 one sides or two sided
lighting
_COLOR_CONTROL GL_SINGLE_COLOR whether specular
color is calculated
separately
The argument GL_SEPARATE_SPECULAR_COLOR causes the spec-
ular color component to be calculated separately, and added af-
ter texturing. This can enhance highlights, and make a picture
look more realistic. The function call is:
glLightModeli{GL_LIGHT_COLOR_CONTROL,
GL_SEPARATE_SPECULAR_COLOR}
311
The following simple program light.c shows a lighted sphere:
/* Initialize material property, light source,
* lighting model, and depth buffer. */
void init(void)
{
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
GLfloat light_position[] = { 1.0, 1.0, 1.0, 0.0 };
GLfloat white_light_[] = { 1.0, 1.0, 1.0, 0.0 };
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_SMOOTH);
glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess);
glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_DIFFUSE, white_light);
glLightfv(GL_LIGHT0, GL_SPECULAR, white_light);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_DEPTH_TEST);
}
312
void display(void)
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glutSolidSphere (1.0, 20, 16);
glFlush ();
}
313
Recall that light sources are treated as geometric primitives,
so a light source can be moved by transforming the modelview
matrix. The movelight program shows the effect of moving
the light source and keeping the object and viewpoint fixed:
314
Material properties
Material properties are set with the function glMaterial*().
It has the form:
void glMaterial{if}(GLenum face, GLenum pname, TYPE
param);
void glMaterial{if}v(GLenum face, GLenum pname, TYPE
*param);
where face can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK,
and pname and param are defined in the following table:
Parameter name Default Value Meaning
GL_AMBIENT (0.2, 0.2, 0.2, 1.0) ambient color of material
GL_DIFFUSE (0.8, 0.8, 0.8, 1.0) diffuse color of material
GL_SPECULAR (0.0, 0.0, 0.0, 1.0) specular color of material
GL_EMISSION (0.0, 0.0, 0.0, 1.0) emissive color of material
GL_SHININESS 0.0 specular exponent
See the man pages for color index parameters.
The parameter GL_EMISSION which allows a body to emit light
(for modelling lamps, etc.) Light from this source does not
illuminate any part of the scene.
The tutorial lightmaterial shows interaction between light
sources and material properties.
315
The program material.c shows several material properties:
The spheres in the first row have materials with no ambient
reflection, the second row has significant ambient reflection, and
the third row has colored ambient reflection.
The first column has blue, diffuse reflection only. The second
and third add specular reflection with a low and high shininess
exponent, respectively. The fourth includes emission.
316
How a vertex’s color is calculated in OpenGL
The color of a vertex under lighting in OpenGL is:
color = emission at the vertex +
global ambient light scaled by the ambient co-
efficient of the material +
the ambient, diffuse, and specular components
from all light sources, properly attenuated
The color components are calculated as:
color = emissionmaterial+
ambientlight model × ambientmaterial +∑n−1i=0
(
1kc+kld+kqd2
)
× (spotlight effect)i ×[ambientlight × ambientmaterial +
(max{Li · n, 0})× diffuselight × diffusematerial +
(max{s · n, 0})shininess × specularlight × specularmaterial
]
i
where n = (nx,ny,nz) is the unit normal vector at the vertex,
L = (Lx,Ly,Lz) is the unit vector pointing from the vertex
to the light, and s = (sx, sy, sz) is the unit vector obtained by
normalizing the sum of the unit vectors between the vertex and
the light position, and the vertex and the viewpoint.
The result is clamped to [0, 1].
317
Polygon shading— what happens between vertices?
Clearly, the color and intensity of light at interior points in the
polygon can be calculated similarly. This would be computa-
tionally expensive, however, so other methods are used.
The most common approach is to shade each polygon patch a
constant color, or to apply some kind of interpolation. Con-
stant shading tends to look poor — the edges of the polygon
are apparent even when the polygon patches are small. This is
because of the Mach effect. This is due to an intensity depen-
dent lateral inhibition of the receptors in the eye. This tends
to accentuate an edge where there is a discontinuous change in
the intensity.
Even interpolated shading is somewhat subject to this effect.
Gouraud shading
OpenGL directly supports Gouraud shading. Here, the inten-
sity (and color) is linearly interpolated from the values at the
vertices. In fact, this interpolation can be incorporated in a
scan line algorithm.
OpenGL directly supports only flat shading and Gouraud shad-
ing. Recall, however, that many transformations can also be
applied to the color vector.
318
Phong shading
Instead of interpolating over the intensity, Phong shading inter-
polates over the normal vectors at the vertices. This generally
produces a better specular component, because a better approx-
imation is used to the normal at each point. (Gouraud shading
tends to “average out” specular highlights.)
This is computationally expensive, however, because the in-
terpolated normal vector must be normalized at each point.
Some techniques can reduce this calculation (e.g., approximat-
ing Phong shading by a Taylor series expansion) but it is still
computationally expensive.
Other interpolated shading models have also been used.
There are some problems with interpolated shading, however.
One of the most interesting is the problem of shared vertices.
In the following, unless vertex B is added to the polygon on the
left, there may be a discontinuity in intensity and color at that
point.
A
C
B
319
Surface detail
So far, the surfaces we have seen are “mathematically smooth.”
This simply does not occur in the real world; all surfaces have
some characteristics — roughness, color variation, etc.
One such “surface realism enhancement” supported by OpenGL
is textures.
Textures are rectangular arrays of data — bitmaps, which are
applied to a surface. Individual elements are called texels. They
are transformed as part of the surface, so they generally behave
as part of the scene. For example, a texture might be a brick
wall, scanned from a picture of a real wall. If the texture were
applied to a surface, then the perspective and other transfor-
mations would apply to the components of the texture on the
wall — they would scale properly.
Many textures are scanned images, partly because it is tedious
to “program” a bit image. Textures are interesting because they
are often transformed to non-rectangular regions. Doing this in
a reasonable way can be challenging.
Textures can be one, two, or three dimensional. They can be
used in several different ways — as “wallpaper” covering a sur-
face; as a means to modulate the color a surface would have
otherwise had, or to blend a texture color with the surface color.
320
Texture mapping
The following are the basic steps to use texture mapping:
• Create a texture object.
• Specify a texture for the object.
• Indicate how the texture is to be applied to each pixel.
• Enable texture mapping.
• Draw the scene, specifying both texture and geometric co-
ordinates.
Create a texture object and specify a texture
The data describing a texture may have one, two, three, or four
elements per texel, It usually represents an (R,G,B,A) value.
They are often scanned pictures, but the example following
shows a simple checkerboard constructed with code.
Indicate how the texture is to be applied to each
pixel
There are several possible ways to use the texture data at a
pixel: the texture can replace the color value, the texture can
modulate (scale) the color, or a constant color can be blended
with the pixel, based on the texture value.
321
Enable texture mapping
Texturing is enabled with the functionglEnable(), with argu-
ments GL_TEXTURE_1D, GL_TEXTURE_2D, or GL_TEXTURE_3D
It is disabled with glDisable().
Draw the scene, specifying both texture and geo-
metric coordinates
The texture must be aligned with the primitive to which it is to
be applied. Therefore, both texture and geometric coordinates
must be specified. For a 2-dimensional texture map, the texture
range is 0.0 to 1.0 in both dimensions, but the geometric coor-
dinates can be anything. Also, if the texture values are outside
[0.0, 1.0] what happens? (Are they repeated or set to a fixed
value?)
The following program (checker.c) shows the use of simple
texturing on two surfaces:
322
/* Create checkerboard texture */
#define checkImageWidth 64
#define checkImageHeight 64
GLubyte checkImage[checkImageWidth][checkImageHeight][4];
static GLuint texName;
void makeCheckImage(void)
{
int i, j, c;
for (i = 0; i < checkImageWidth; i++) {
for (j = 0; j < checkImageHeight; j++) {
c = ((((i&0x8)==0)^((j&0x8))==0))*255;
checkImage[i][j][0] = (GLubyte) c;
checkImage[i][j][1] = (GLubyte) c;
checkImage[i][j][2] = (GLubyte) c;
checkImage[i][j][3] = (GLubyte) 255;
}
}
}
323
void myinit(void)
{ glClearColor (1.0, 1.0, 1.0, 0.0);
glShadeModel(GL_FLAT);
glEnable(GL_DEPTH_TEST);
makeCheckImage();
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glGenTextures(1, &texName);
glBindTexture(GL_TEXTURE_2D, texName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
GL_DECAL);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
checkImageWidth, checkImageHeight, 0, GL_RGBA,
GL_UNSIGNED_BYTE, checkImage);
}
324
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_TEXTURE_2D);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
GL_REPLACE);
glBindTexture(GL_TEXTURE_2D, texName);
glBegin(GL_QUADS);
glTexCoord2f(0.0, 0.0); glVertex3f(-2.0, -1.0, 0.0);
glTexCoord2f(0.0, 1.0); glVertex3f(-2.0, 1.0, 0.0);
glTexCoord2f(1.0, 1.0); glVertex3f(0.0, 1.0, 0.0);
glTexCoord2f(1.0, 0.0); glVertex3f(0.0, -1.0, 0.0);
glTexCoord2f(0.0, 0.0); glVertex3f(1.0, -1.0, 0.0);
glTexCoord2f(0.0, 1.0); glVertex3f(1.0, 1.0, 0.0);
glTexCoord2f(1.0, 1.0); glVertex3f(2.414, 1.0,-1.414);
glTexCoord2f(1.0, 0.0); glVertex3f(2.414,-1.0,-1.414);
glEnd();
glFlush();
glutSwapBuffers();
glDisable(GL_TEXTURE_2D);
}
325
Using textures
We will look at two-dimensional textures. One- and three-
dimensional textures are specified similarly, and details can be
found in the man pages.
The function
void glTexImage2D(Glenum target, GLint level, GLint
internalFormat, GLsizei width, GLsizei height, GLint
border, GLenum format, GLenum type, const GLvoid *texels);
defines a two-dimensional texture. The parameter target is
set to GL_TEXTURE_2D or GL_PROXY_TEXTURE_2D; level is
normally set to 0, internalFormat has 38 possibilities, but
GL_RGBA is common (see the man pages for others), width and
height are the dimensions of the texture image, border is the
width of the border, 0 (no border) or 1. The minimum size
of a texture map is at least 64 × 64 (66 × 66 with borders).
format and type define the way the pixels are represented (see
man pages for glDrawPixels()). The parameter *texels is
a pointer to the texture data image.
The number of texels for both width and height must be a power
of 2.
326
glTexParameter{if}(GLenum target, GLenum pname, GLenum
param);
glTexParameter{if}v(GLenum target, GLenum pname, GLenum
*param);
set various parameters that control how a texture object is
treated as it is applied to a fragment, or stored in a texture
object. target is GL_TEXTURE_2D, and pname and param can
have the following values:
Parameter Values
GL_TEXTURE_WRAP_S GL_CLAMP, GL_CLAMP_TO_EDGE,
GL_REPEAT
GL_TEXTURE_WRAP_T GL_CLAMP, GL_CLAMP_TO_EDGE,
GL_REPEAT
GL_TEXTURE_WRAP_R GL_CLAMP, GL_CLAMP_TO_EDGE,
GL_REPEAT
GL_TEXTURE_MAG_FILTER GL_NEAREST, GL_LINEAR
GL_TEXTURE_BORDER_COLOR GL_NEAREST, GL_LINEAR
GL_TEXTURE_PRIORITY [0.0, 1.0] for the current texture
object
GL_TEXTURE_MIN_LOD any real number
GL_TEXTURE_MAX_LOD any real number
GL_TEXTURE_BASE_LEVEL any non-negative integer
GL_TEXTURE_MAX_LEVEL any non-negative integer
327
So far, we have looked at textures as “wallpaper” to be painted
on a surface. The function
glTexEnv{if}(GLenum target, GLenum pname, TYPE param);
glTexEnv{if}v(GLenum target, GLenum pname, TYPE *param);
sets the current texturing function. targetmust be GL_TEXTURE_ENV.
pname can be GL_TEXTURE_ENV_MODE or GL_TEXTURE_ENV_COLOR.
For GL_TEXTURE_ENV_MODE, param can be GL_REPLACE, GL_DECAL,
GL_MODULATE, or GL_BLEND.
GL_REPLACE replaces the color of the fragment with the tex-
ture, GL_DECAL linearly interpolates the texture color with the
fragment color using the A value of the texture. The A value
of the pixel is the value from the fragment.
The other two parameters depend on the base internal format
of the texture.
For GL_TEXTURE_ENV_COLOR, param is an array of 4 real num-
bers representing R, B, G, and A.
There are 6 base internal formats, GL_ALPHA, GL_LUMINANCE,
GL_LUMINANCE_ALPHA, GL_INTENSITY, GL_RGB, and GL_RGBA.
For details, see the man pages.
The tutorial on texturing by Nate Robbins shows much of the
functionality of textures.
328
Textures can be mapped on surfaces of virtually any shape.
The program texsurf.c shows a (colorful) texture mapped
onto a curved surface:
GLfloat ctlpts[4][4][3] = {
{{ -1.5, -1.5, 4.0}, { -0.5, -1.5, 2.0},
{0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
{{ -1.5, -0.5, 1.0}, { -0.5, -0.5, 3.0},
{0.5, -0.5, 0.0}, {1.5, -0.5, -1.0}},
{{ -1.5, 0.5, 4.0}, { -0.5, 0.5, 0.0},
{0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
{{ -1.5, 1.5, -2.0}, { -0.5, 1.5, -2.0},
{0.5, 1.5, 0.0}, {1.5, 1.5, -1.0}}
};
GLfloat texpts[2][2][2] = {{{0.0, 0.0}, {0.0, 1.0}},
{{1.0, 0.0}, {1.0, 1.0}}};
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glEvalMesh2(GL_FILL, 0, 20, 0, 20);
glFlush();
329
}
#define imageWidth 64
#define imageHeight 64
GLubyte image[3*imageWidth*imageHeight];
void makeImage(void)
{
int i, j;
float ti, tj;
for (i = 0; i < imageWidth; i++) {
ti = 2.0*3.14159265*i/imageWidth;
for (j = 0; j < imageHeight; j++) {
tj = 2.0*3.14159265*j/imageHeight;
image[3*(imageHeight*i+j)] = (GLubyte) 127*
(1.0+sin(ti));
image[3*(imageHeight*i+j)+1] = (GLubyte) 127*
(1.0+cos(2*tj));
image[3*(imageHeight*i+j)+2] = (GLubyte) 127*
(1.0+cos(ti+tj));
}
}
}
330
void myinit(void)
{
glClearColor(1.0, 1.0, 1.0, 0.0);
glMap2f(GL_MAP2_VERTEX_3,0,1,3,4,0,1,12,4, ctlpts);
glMap2f(GL_MAP2_TEXTURE_COORD_2, 0, 1, 2, 2, 0, 1,
4, 2, texpts);
glEnable(GL_MAP2_TEXTURE_COORD_2);
glEnable(GL_MAP2_VERTEX_3);
glMapGrid2f(20, 0.0, 1.0, 20, 0.0, 1.0);
makeImage();
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
GL_DECAL);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,
GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,
GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
331
glTexImage2D(GL_TEXTURE_2D, 0, 3, imageWidth,
imageHeight, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glEnable(GL_TEXTURE_2D);
glEnable(GL_DEPTH_TEST);
glEnable(GL_NORMALIZE);
glShadeModel (GL_FLAT);
}
332
Depth perception — ordering of primitives
One of the considerations which must be made when rendering
3-dimensional figures is the perception of depth — depending on
the point of view, objects may obscure each other. Clearly, an
object “further away” from the viewpoint should be obscured
by a closer object. There are a number of ways to accomplish
this.
One of the most obvious is to sort objects by distance from the
viewpoint. The objects are then rendered in order of distance,
starting with the furthest. For simple, non-intersecting convex
objects, this is satisfactory, but problems can arise if the objects
are more complex.
What object is in front here? (Looking from page bottom.)
333
It is always possible to decompose a scene into non-intersecting
convex objects, but the computational effort may be large, and
there may be many small fragments. The required data struc-
ture for the objects would be complex.
Another possibility is to sort the surface primitives in depth
order. Note that, in either case, the sorting has to be done for
each line of view.
A number of optimizations can be applied to reduce the over-
all complexity; for example, objects or sets of objects can be
contained within a simple bounding volume and, if they do not
intersect, the whole volume can be ordered. This can be applied
recursively, using data structures like the oct-tree structure, or
other spatial partitioning structures.
Sometimes, structures can be constructed hierarchically, so that
a higher level in the hierarchy provides a bounding volume for
lower levels in the hierarchy.
One advantage of this type of algorithm is that effects like trans-
parency can be handled easily.
334
The z-buffer algorithm
One of the most common methods for determining visible sur-
faces is the z-buffer algorithm. Essentially, the depth of the
point represented by a pixel is recorded in a memory location
(the z-buffer), with one z-buffer location for each pixel. The
color of the pixel is overwritten only if the current depth value
is less than that presently written in the buffer.
With this algorithm, the order of rendering of primitives is ir-
relevant, if all primitives have solid colors.
This algorithm is often implemented in hardware — it requires
another buffer (memory location) for each pixel. (Most current
graphics boards have sufficient memory for this function.)
OpenGL implements a depth buffer algorithm. The function
void glDepthFunc(GLenum func);
is the appropriate function call.
The parameter func can have values: GL_NEVER, GL_ALWAYS,
GL_LESS, GL_LEQUAL, GL_EQUAL, GL_GEQUAL, GL_GREATER,
or GL_NOTEQUAL.
Whether or not the function is actually implemented in hard-
ware depends primarily on the driver provided with the graphics
card.
335
Ray tracing
Ray tracing uses a simple physical model for the interaction
between light and matter. It automatically incorporates hidden
surface removal, and recursive ray tracing handles transparency,
reflections, and shadows in a physically realistic way.
The basic algorithm is simple. A ray is traced (backward) from
the eye through the image plane until it interacts with an object.
The pixel in the image plane is then given the color of the object
with the ray intersects. Simple ray tracing follows a single ray
from the eye to a light source.
Light source
Pixels
planeImage
336
Recursive ray tracing allows a ray to split into components at
each surface, and each ray can be traced to a source. In the
following, the cube is transparent, while the sphere and cylinder
are solid.
Imageplane
Pixels
Light source
Here there are direct and indirect rays from the eye to the source
— direct rays are reflected from one surface; indirect rays are
reflected from or refracted by multiple surfaces.
337
Note that, in naive ray tracing, each ray must be checked for
intersection with each primitive. In practice, it is usually pos-
sible to design a hierarchical data structure which can reduce
the number of intersection tests. Also, multiple rays should be
projected through each pixel.
The multiple reflections and refractions can be represented in a
tree structure; the following shows such a tree for the previous
example:
sphere
cube
cylinder
eye
One of the keys to ray tracing is the ability to calculate in-
tersections between rays and primitives. For many types of
curved surfaces, this calculation is non-trivial. For polygons
and spheres it is not difficult, and a closed form is available for
general quadric surfaces.
338
Ray/circle intersections
Intersections between a sphere and a ray can be calculated effi-
ciently (consequently almost every ray trace picture contains a
number of solid or transparent spheres).
Given a ray with endpoints (x1, y1, z1) and (x2, y2, z2) the ray
can be expressed parametrically as
x = x1 + t(x2 − x1) = x1 + it
y = y1 + y(y2 − y1) = y1 + jt
z = z1 + t(z2 − z1) = z1 + kt
A sphere with centre (l,m, n) of radius r is given by
(x− l)2 + (y −m)2 + (z − n)2 = r2
t = 0
t = 1
Ray start (x ,y ,z )
Ray end (x ,y ,z )
1
2 2
1 1
2
339
substituting for x, y, and z gives a quadric in t
at2 + bt + c = 0 where
a = i2 + j2 + k2
b = 2i(x1 − l) + 2j(y1 −m) + 2k(z1 − n)
c = l2 +m2 + n2 + x21 + y21 + z21 − 2(lx1 +my1 + nz1 + r2)
If the roots are not real, then the line does not intersect the
sphere. Otherwise, the roots give the front and back entry
points of the ray.
Since both the front and back entry points are readily calculated
for a sphere, transparent spheres are also oftem modelled in ray
traced images.
The normal to the surface at the intersection point can be cal-
culated as
N =
xi − l
r,yi −m
r,zi − n
r
340
Ray/polygon intersections
Here, a straightforward approach is to:
1. Find an equation for the plane containing the polygon
2. Check for intersection between the ray and the plane
3. Check whether the point is interior to the polygon
If the plane is expressed in standard form
ax + by + cz + d = 0
and the ray defined parametrically, then the intersection is
t = −ax1 + by1 + cz1 + d
ai + bj + ck
A test for the point being interior is to sum the angles between
lines drawn to the vertices — it is 360◦ for an interior point.
Ray/box intersections
Here, the clipping algorithms discussed earlier are effective.
Boxes are important because they are used as bounding volumes
for other primitives in a hierarchical data structure.
341
Ray/quadric intersections
The general equation for a quadric is
Ax2+2Bxy+2Cxz+2Dx+Ey2+2Fyz+2Gy+Hz2+2Iz+J = 0
which is equivalent to
[x, y, z, 1] ·
A B C D
B E F G
C F H I
D G I J
·
x
y
z
1
= 0
Substituting for x, y, and z from the parametric equation for
the ray again gives a quadratic equation with coefficients
a = Ax2d + 2Bxdyd + 2Cxdzd + Ey2d + 2Fydzd +Hz2d
b = 2(Ax1xd+B(x1yd+xdy1)+C(x1zd+xdz1)+dxd+Ey1yd+
F (y1zd + ydz1) +Gyd +Hz1zd + Izd)
c = Ax21+2Bx1y1+2Cx1z1+2Dx1+Ey21 +2Fy1z1+2Gy1+
Hz21 + 2Iz1 + J
where (xd, yd, zd) is the normalized ray direction.
342
One useful optimization is to treat the ray as a cone of rays
subtending some small spherical angle.
This may be feasible, but the computation of the intersection
of a surface with a cone, and the subsequent reflections, is more
complex.
Ray tracing produces images of amazing clarity, but they are
generally not realistic. Again, there is the problem of interac-
tion with a mathematically perfect surface. In the “real world”
rays are not perfectly reflected and refracted, and there is some
intensity loss at every surface.
There are also second order effects not modelled by this sim-
ple recursive ray tracing. For example, specular reflection of
ambient light can cause a dimunition of shadows.
343
Modelling solids
We have been looking at ways of representing the surfaces of an
object, without any discussion of its interior. For visualization
applications only, this is sufficient.
Many applications require a more complete representation of
a solid. For example, mechanical drawing systems may want
to determine if two components overlap, or satisfy some other
physical constraint.
OpenGL does not support the representation of solid bodies; if
required, this is done with a higher level of software.
There are many ways of representing solids; in general, repre-
sentations which allow the combination of objects to form other
objects are desirable.
Note that the combination operations should be guaranteed to
produce solid objects themselves.
A further desirable constraint on a representation is that the
representation not admit an invalid representation.
OpenGL represents solids as a set of surfaces. There is no guar-
antee that all surfaces meet at the edges, or that a “solid” is
completely covered with surfaces — not a desirable representa-
tion for solids.
344
Regularized Boolean set operations
The most elementary operations for combining primitives are
the Boolean set operations — union, intersection, and differ-
ence.
In the case of solids, the regular Boolean operations may not
always leave a solid as the result — e.g., the intersection of two
solids touching only at a vertex would leave a point.
The regularized Boolean set operators, denoted as ∪∗, ∩∗,and −∗ always yield solids when applied to solids. (e.g., the
intersection of two solids touching only at a vertex would yield
a null object.)
Basically, regularized Boolean set operators are ordinary Boolean
set operators applied to the interior of solids. Formally, the
regularized operations can be defined as
A op∗ B = closure(interior(A op B))
where op is one of the set operations ∪, ∩, or −.
Consider what happens when objects relate as follows:
3 421 5 6
345
Primitive instancing
This is a common way of modelling solids in 3-D CAD pack-
ages in specific application areas. The primitives are parame-
terized with a small number of parameters. In microelectronics,
transistors, resistors, and capacitors might be some of the pa-
rameterized items. Parameters could be physical such as size,
number of connections, etc., or electrical; e.g. current capacity,
voltage rating.
In mechanical systems, such things as bolts, screws, gears, etc.
could be parameterized with quantities like size, pitch, etc.
The primitives are defined in code, and there is no consistent
method for combining the primitives to make more complex
objects.
Pitch
size
346
Boundary representations
Here, solids are represented as closed solid figures. A common
representation primitive is the polyhedron.
Simple polyhedra satisfy Euler’s formula, V −E+F = 2, where
V is the number of vertices E is the number of edges, and F is
the number of faces.
For 2-manifolds (surfaces which can be covered locally by a
small disc — generally, non-intersecting surfaces) which have
faces with holes, this generalizes to V −E+F −H = 2(C−G)
where H is the number of holes in the faces, G is the number
of holes that pass through the object, and C is the number of
separate components.
Not a 2-manifold V - E + F - H = 2(C - G)24 36 15 1 13
A set of Euler operators can be defined to transform solids by
adding and removing edges, vertices, etc. within this constraint.
347
Spatial partitioning
Spatial-occupancy enumeration — voxels
Like pixels in a plane, voxels are rectangular solid volumes ar-
ranged in a 3-dimensional grid. Much of our discussion of the
advantages and disadvantages of a pixel (raster) representation
carries over into 3 dimensions. In fact, matters are more com-
plex, because the number of voxels grows as n3, rather than
n2.
This representation is normally used when a small, fixed volume
is to be modelled. For example, it is often used in medical
imaging, and for numerical modelling of some physical processes
(fluid flow, etc.)
Given a fine enough resolution, it is capable of showing fine
spatial variations in such things as density or flow rate.
348
Octrees
These are merely tree data structures recursively subdividing a
region until the variation of interest is “small enough.”
The initial volume is divided into 8 identical sub-volumes, and
this is repeated recursively. It is an extension of the 2-dimensional
quadtree representation for surfaces.
Subdivision is performed until there is no additional information
added by the next subdivision.
This “divide and conquer” structure is efficient, and has been
applied in many domains. The n log n nature of the spatial
representation has, for example, been used in the evaluation of
pairwise interactions of long-range force interactions (gravity),
reducing calculations from O(n2) to O(n log n).
A primary advantage of the octree representation is the body
of knowledge (and code) relating to tree manipulation. In fact,
the regularized Boolean set operations can be implemented with
little difficulty.
349
Binary Space-Partitioning Trees
It is also possible to divide a volume into irregular sized subvol-
umes, simply using a plane at arbitrary orientation and position.
This can be done recursively, much like the octree representa-
tion, and can be much more efficient.
Considering the previous (planar) example, three lines are suf-
ficient to achieve a full partitioning. This may well work far
better for other examples with non-rectangular objects.
Constructive Solid Geometry
Here, simple geometric primitives are used to construct more
complex structures. In general, the structure forms a hierarchy
(a tree) similar to the hierarchical structures to construct an
image. Of course, the primitives here are solids, not surfaces.
Again, the regularized Boolean set operators are used for com-
position. The utility of this representation depends on the do-
main of interest and the richness of the primitive set.
350