Drawing Lines
Last time we discussed the basic elements of polygon construction under OpenGL.
OpenGL only supports a few basic primitive geometrical objects: points, lines,
polygons, and surfaces described by arrays of small quadrilaterals or triangles.
The main idea behind OpenGL's simplicity is that it is up to the developer to
implement from this simple objects more complex geometrical models. OpenGL contains
a number of commands to control the details of points, lines and polygons.
For example the size of points can be specified in pixels using
glPointSize:
void glPointSize(GLfloat size)
By the default the size of points is 1.0 and size must always be
greater than zero. Notice that the size of a point is specified by a float number;
fractional point and line sizes are allowed. OpenGL interprets fractional pixel
sizes according to the rendering context. If the anti-aliasing mode is enable then
OpenGL modifies the neighboring pixels to the line in question in order to
give the appearance of a fractional width. Anti-aliasing is a technique also
used to eliminate the ugly stars the straight lines show on computer screens
at low monitor resolution. If anti-aliasing is not enable then glPointSize
will round off size to the closets integer.
The physical size of a pixel actually is device dependent. So for example
at low monitor resolution a pixel appears wider. Similarly on very high resolution
devices, like a plotter, the default 1 pixel line can appear almost invisible.
To estimate the real width of your lines you must know the actual physical
dimensions of pixels on the output device.
The width of lines is specified by the glLineWidth function, that
must be invoked before the glBegin() - glEnd() pair that draws the line(s).
Here is the full syntax of the command:
void glLineWidth(GLfloat width)
OpenGL implementations may limit the width of nonantialiased lines
to its maximum antialiased line width, rounded to the nearest integer value.
Also keep in mind that line widths are measured not perpendicularly to the line but
in the y-direction if the absolute value of the slope is less than 1; if greater
in the x-direction.
This month we have prepared another simple but hopefully useful 2D animation
that shows you how to use various kinds of line widths in your OpenGL applications
(../../common/March1998/example2.c, ../../common/March1998/Makefile).
I chose an example from Quantum Physics, a quantum particle trapped in a double
well potential. Why? Umm actually I forget. Anyway I figure that it would be
useful for physics and engineering students to see how to integrate the time
dependent Schroedinger equation, others may just enjoy watching the non-intuitive
nature of quantum mechanics. A particle in QM is not represented by a position and
a velocity but by a "wave" a quantum wave (solid purple line in our animation)
whose absolute square value represents the probability of observing the particle
at a given position (dash white line):
Figure 1. Quantum Simulation Snapshot
For those with some course work in Ordinary Differential Equations I can tell you
that the wave equation is integrated using a FFT (Fast Fourier Transform) Split-Operator
method. This method is far more accurate and rapid than any finite difference method.
It is applicable to nonlinear wave propagation; the time evolution operator is split
to second (or higher order) into operators only dependent on either position and
momentum (frequency), then the wavefunction is evolved in time by successively applying
these operators switching back and forth between the position and momentum (frequency)
space.
The body of the source code can be used for many other applications. You can swap my
quantum simulation with your own time dependent function and get a nice animation
of your system. You could also try to write a simplified OpenGL-based gnuplot for
plotting functions and data files.
If the reader has followed the previous articles on GLUT and OpenGL this source
code will appear too simple and easy to understand (of course, quantum mechanics aside).
There is nothing extraordinary going here. In the main() we open a single
window in double-buffer mode, then we pass a display() and idle()
callback functions that take care of plotting the wavefunction and integrating the
wave equation respectively. Again, do mind what goes on in the idle() function,
although it is a very beautiful trick is not necessary to fully understand it
to grasp the content of this article. The really new OpenGL stuff is in the
display callback function:
void
display (void)
{
static char label[100];
float xtmp;
/* Clean drawing board */
glClear (GL_COLOR_BUFFER_BIT);
/* Write Footnote */
glColor3f (0.0F, 1.0F, 1.0F);
sprintf (label, "(c)Miguel Angel Sepulveda 1998");
glRasterPos2f (-1.1, -1.1);
drawString (label);
/* Draw fine grid */
glLineWidth (0.5);
glColor3f (0.5F, 0.5F, 0.5F);
glBegin (GL_LINES);
for (xtmp = -1.0F; xtmp < 1.0F; xtmp += 0.05)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Draw Outsite box */
glColor3f (0.1F, 0.80F, 0.1F);
glLineWidth (3);
glBegin (GL_LINE_LOOP);
glVertex2f (-1.0F, -1.0F);
glVertex2f (1.0F, -1.0F);
glVertex2f (1.0F, 1.0F);
glVertex2f (-1.0F, 1.0F);
glEnd ();
/* Draw Grid */
glLineWidth (1);
glColor3f (1.0F, 1.0F, 1.0F);
glBegin (GL_LINES);
for (xtmp = -0.5; xtmp < 1.0; xtmp += 0.50)
{
glVertex2f (xtmp, -1.0);
glVertex2f (xtmp, 1.0);
glVertex2f (-1.0, xtmp);
glVertex2f (1.0, xtmp);
};
glEnd ();
/* Draw Coordinate Axis */
glLineWidth (2);
glBegin (GL_LINES);
glVertex2f (-1.0, 0.0);
glVertex2f (1.0, 0.0);
glVertex2f (0.0, -1.0);
glVertex2f (0.0, 1.0);
glEnd ();
/* Axis Labels */
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, "Position");
glRasterPos2f (0.80F, 0.025F);
drawString (label);
glColor3f (1.0F, 0.0F, 1.0F);
sprintf (label, " Quantum Probability ");
glRasterPos2f (0.025F, 0.90F);
drawString (label);
glColor3f (1.0F, 1.0F, 1.0F);
sprintf (label, " Real(Psi) ");
glRasterPos2f (0.025F, 0.85F);
drawString (label);
/* Draw Wavefunction */
psiDraw (NR_POINTS, psi, x);
/* Draw potential Function */
potentialDraw (NR_POINTS, potential, x);
glutSwapBuffers ();
};
The first thing done is to clear the color buffer bit, this gives us a clean (black)
drawing board. Then we add a footnote using glRasterPos and
glutBitmapCharacter (drawstring is nothing but a wrapper for the clut utility).
In future lessons glRasterPos will appear again as an auxiliary function
for texture rendering. Neither OpenGL nor GLUT offer a simple and powerful way for
rendering text onto a graphic window. The glutBitmapCharacter basically rasters a
font bitmap onto the color buffer.
Following the footnote comes a number of lines: the outside box, the background grid,
the coordinate axis, and of course the current curves drawn with psiDraw and
potentialDraw. Before every line rendered is a glLineWidth that
specifies the number of pixels of width to be given to the line. Figure 1 shows
the output on an Xwindow System (Linux Alpha). For some unknown reason to me the
Windows 95 output of the same program looks very crappy, it appears as if the antialiasing
feature is not well supported by the SGI OpenGL driver; it is hard ti differentiate lines
that in principle should have different widths, and the background grid of lines
also appears very uniform. These defects appear when the display is set at high resolution
so it is not an artifact of a low resolution monitor setup. I am happy to say that
Linux X window system defeats by large win95/NT once more.
There are two types of line rendering in the display() function,
GL_LINES mode which joins vertices with a continuous open line and GL_LINE_LOOP mode
that at the end closes the loop.
Antialiasing Lines
I have enabled antialiasing for the lines in the reshape() callback function,
void
reshape (int w, int h)
{
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
glViewport (0, 0, w, h);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
gluOrtho2D (-1.2, 1.2, -1.2, 1.2);
glEnable (GL_LINE_SMOOTH); /* Enable Antialiased lines */
glEnable (GL_LINE_STIPPLE);
};
What is GL_LINE_STIPPLE for? OpenGL let us control not only the width of a
line but also its pattern. By enabling GL_LINE_STIPPLE we are able to draw
dash or any other pattern of lines. The only stippled line in the animation appears
in the psiDraw() function:
glLineWidth (1);
glPushAttrib (GL_LINE_BIT);
glLineStipple (3, 0xAAAA);
glBegin (GL_LINE_STRIP);
for (i = 0; i < nx; i++)
{
xs = ratio1 * (x[i] - XMIN) - 1.0;
ys = ratio2 * (psi[2 * i] - YMIN) - 1.0;
glVertex2d (xs, ys);
};
glEnd ();
glPopAttrib ();
Line Stippling
The glLineStipple specifies the pattern used for stippling, in our
example we used the pattern 0xAAAA. In binary this numbers read as
0000100010001000 and OpenGL interprets this drawing 3 bits off, 1 bit on, 3 bits off,
1 bit on, 3 bits off, 1 bit on and finally 4 bits off. Yes the pattern is read
backwards because the low order bits are used first. Now glLineStipple
gets two parameters, the stippled pattern which should be an hexadecimal number and
and integer factor which serve to scale the pattern, so with a factor of 3
our stippled line will show 9 bits off, 3 bits on, 9 bits off, 3 bits on,
9 bits off, 3 bits on and finally 12 bits off. Playing with factors and binary
patterns one can draw all sort of complicated stippled lines.
One more detail: I have enclosed the stippled line rendering between a push
and pop Attribute statement. Remeber when in our first article we mentioned that
OpenGL is a state machine? Well in future articles we will see in more detail
these push and pop operations, but in short what we are doing with the first
glPushAttrib (GL_LINE_BIT) is to push in a stack the current value of the
GL_LINE_BIT state variable (this variables decides the stippling pattern), then
we can alter GL_LINE_BIT with our glLineStipple statement and when we
are done we call a glPopAttrib that brings back the old GL_LINE_BIT
variable. This mechanism is an effective way to modified the state variables of
the OpenGL machine locally. If we didn't do this then all lines draw after
our glLineStipple would have the same stippling pattern and we would
be forced to declare a glLineStipple patter for every line we ever
render in our application. Push & Pop saves us this annoying work.
Next Time ....
OpenGL is famous for is wonderful 3D API interface. So far we have explored
some elemental possibilities of 2D rendering with OpenGL. Next time we will
examine the 3D OpenGL scene, how to set a perspective, system of coordinates,
clipping planes and projection matrixes.
Till then have fun with OGL......
|