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......
|