|
|
|
|
|
|||||||||||||||||||||||||||||||||||||||||
|
|
|
|||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
![]()
![]()
Author: Johan Schön <js@roxen.com>
Last modified: 2000-09-15 16:49:37
This article will teach you how to make a simple OpenGL application using Pike. Note that it doesn't aim to teach OpenGL (there's a good book for that). What this article will teach you however, is to get started with OpenGL programming under Pike.
OpenGL overview
One of the most dominant 3D graphics API's is called OpenGL ('Open Graphics Library'). It was introduced by Silicon Graphics in 1992, and today it's the number one industry standard in 3D visualization.
Pike supports OpenGL since release 7.0. (Both GL, GLU and GLUT is available. In addition, the Pike GTK interface supports the gtkglarea widget, which this article does not cover.)
The author, Johan Schön
<js@roxen.com>Getting a Pike with OpenGL support
In order to build a Pike with OpenGL support you need to first install a GL engine on your system, and then recompile Pike. The Pike distributions inside the Roxen 2.x distributions are unfortunately currently compiled with --without-GL and --without-GLUT.
$ cd Pike/7.1 $ make clean ... $ make ...      
Monitor the output to see if configure finds the OpenGL include files.
When you're done compiling, you can verify that your Pike contains GL bindings by doing this:
$ bin/pike Pike v7.1 release 7 running Hilfe v2.0 (Incremental Pike Frontend) > sizeof(indices(GL)); Result: 742 > sizeof(indices(GLUT)); Result: 270
This tells us that the GL and GLUT modules have 742 and 270 global identifiers respectively, and that the compilation went well. Importing the GL modules
The GL, GLU and GLUT modules contain automatically generated "glue code" interfaces to the C APIs. The names differ a bit in some cases though. For instance, the C function glVertex4f is in Pike called GL.glVertex. In cases where the C functions containtype information, the corresponding Pike function has removed the type information from the function, and instead uses runtime type checking of the arguments.It is often convenient to import the namespaces of the GL modules into your program. This lets you simply write glVertex instead of GL.glVertex.
// Make the GL, GLUT and GLU namespaces available locally import GL; import GLUT; import GLU;
Setting up a GL window
There's a few different ways to get a OpenGL window to draw in. The easiest, and most portable way is to use the GLUT toolkit. It provides a platform independent way of creating OpenGL windows, and accessing keyboards and mice.
First off, lets make a handy function that opens a GL window using the GLUT toolkit:
void setup_GL() { glutInit(); glutInitWindowSize(640, 480); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutCreateWindow("Rolling R"); glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_SMOOTH); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_DEPTH_TEST); }
The first four lines set up a 640*480 pixels big double buffered window with a Z buffer. The title of the window is set to "Rolling R". The next few lines sets the clear color to black, the shading model to "smooth" (gouraud shaded polygons), and enables the use of the Z buffer for depth tests. Projection
To be able to watch something at all we need to set up a "camera". The function belows uses the GLU utility functions gluPerspective to configure the properties of a virtual camera, and gluLookAt to direct the camera at the object we want to show.
void setup_projection() { // Make the projection matrix the current matrix glMatrixMode(GL_PROJECTION); gluPerspective( /* field of view in degrees */ 16.0, /* aspect ratio */ 1.44, /* Z near */ 1.0, /* Z far */ 40.0); // Make the modelview matrix the current matrix glMatrixMode(GL_MODELVIEW); gluLookAt(0.0, 0.0, 20.0, // eye is at (0,0,20) 0.0, 0.0, 0.0, // center is at (0,0,0) 0.0, 1.0, 0.0); // up is in positivie Y direction }
Adding an object
Our three dimensional world would not be very entertaining if it didn't contain any objects to look at. To go with the theme of this web site, we decided to use the now ubiquitous orange Roxen 'R'.
// Normalized coordinates for the different intersections // in the Roxen 'orange R' logo. constant rox_x=({ -0.500000, -0.235849, -0.084906, 0.009434, 0.254717, 0.405660, 0.500000 }); constant rox_y=({ 0.415000, 0.226321, 0.075377, -0.113302, -0.320849, -0.415189}); // Create a "side" of a box. Use the 'dir' argument to create // a correct normal. void make_side(float x0, float y0, float x1, float y1, float z, string dir) { glBegin(GL_QUADS); switch(dir) { case "up": glNormal(0.0,1.0,0.0); break; case "right": glNormal(1.0,0.0,0.0); break; case "down": glNormal(0.0,-1.0,0.0); break; case "left": glNormal(-1.0,0.0,0.0); break; } glVertex(x0, y0, z); glVertex(x1, y1, z); glVertex(x1, y1, -z); glVertex(x0, y0, -z); glEnd(); } // Creates an (orange) box, complete with normals. void make_box(float x0, float y0, float x1, float y1) { float z=0.09434/2; glColor(1.0,0.5, 0.0); glBegin(GL_QUADS); glNormal(0.0, 0.0, 1.0); glVertex(x0, y0, z); glVertex(x1, y0, z); glVertex(x1, y1, z); glVertex(x0, y1, z); glEnd(); glBegin(GL_QUADS); glNormal(0.0, 0.0, -1.0); glVertex(x0, y0, -z); glVertex(x0, y1, -z); glVertex(x1, y1, -z); glVertex(x1, y0, -z); glEnd(); make_side(x1, y0, x0, y0, z, "up"); make_side(x1, y1, x1, y0, z, "right"); make_side(x0, y1, x1, y1, z, "down"); make_side(x0, y0, x0, y1, z, "left"); } // Create a Roxen 'R' logo by combining several 'boxes' void make_the_roxen_r() { glNewList(ROXEN_DISPLAYLIST, GL_COMPILE); glMaterial(GL_FRONT, GL_DIFFUSE, ({1.0, 1.0, 1.0, 1.0}) ); make_box(rox_x[0],rox_y[0], rox_x[1],rox_y[5]); make_box(rox_x[1],rox_y[0], rox_x[6],rox_y[1]); make_box(rox_x[4],rox_y[1], rox_x[6],rox_y[2]); make_box(rox_x[2],rox_y[2], rox_x[6],rox_y[3]); make_box(rox_x[3],rox_y[3], rox_x[4],rox_y[5]); make_box(rox_x[5],rox_y[4], rox_x[6],rox_y[5]); glEndList(); }
The last function defines a 'display list'. This simply means that all the GL commands in the list will be stored in the GL context. This means we don't have to run all this code for every frame display, but can instead use glCallList to execute the list commands. Another advantage is that certain hardware accellerated OpenGL engines can work more effectively with this list. Putting things together
Ok, so far we have opened a window, set up the projection and defined an object. We now create a main() function that calls the functions we've defined above:
int main(int argc, array(string) argv) { setup_GL(); setup_projection(); make_the_roxen_r(); glutDisplayFunc(animate); glutMainLoop(); return 0; }
The first three lines are obvious, but the second last two needs explaining. glutDisplayFunc() sets the callback function to call for each frame. The function animate is defined below. glutMainLoop() gives control to the GLUT main loop.
void animate() { print_stats(); // Clear the RGB buffer and the depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Set the modelview matrix to be the identity matrix glLoadIdentity(); glTranslate(0.0, 0.0, -5.0); // Draw Roxen logo glCallList(ROXEN_DISPLAYLIST); // Make GL flush the buffer glFlush(); glutSwapBuffers(); glutPostRedisplay(); }
The print_stats() call calculates and prints the current framerate to stderr. Then we clear the framebuffer and the Z (depth) buffer. After that we modify the modelview matrix so that everything that's displayed is translated 5 units in the Z direction, i.e. "into the screen". We then call the Roxen logo display list to display our orange Roxen logo. When we're done executing OpenGL commands we need to call glFlush() to make sure that the GL engine executes our commands (otherwise they might get buffered). Then we call glutSwapBuffers() to switch to the next buffer in and lastly we call glutPostRedisplay(), which notifies GLUT that it can call proceed with the next frame when it's ready.
The results is showed to the right. We get a big orange R. Unfortunately it's very static, and not moving at all.
It's a first try..
Adding action to the scene
Let's try to rotate the the R a bit:
float v=0.0; void rotate_object() { // Rotate the object glRotate(v+=0.5, 0.0, 0.0, 0.25); glRotate(v, 0.0, 0.5, 0.0); glRotate(v, 0.25, 0.0, 0.0); }
The rotate_object() is called from animate() every time it us run. Testing this gives us a nice rotation. See the screenshot to the right.
A rotating R
Lights As we noted above, the scene looked rather dull without anything but static, ambient lighting. Let's take care of that by adding a single "point" light:
void setup_light() { array specular = ({ 0.003, 0.003, 0.003, 1.0 }); array diffuse = ({ 0.002, 0.002, 0.002, 1.0 }); array ambient = ({ 0.0, 0.0, 0.0, 1.0 }); float shininess = 0.0; array position = ({ 0.0, 0.0, 120.0, 0.0 }); // Define material properties of specular color and degree of // shininess. Since this is only done once in this particular // example, it applies to all objects. Material properties can // be set for individual objects, individual faces of the objects, // individual vertices of the faces, etc... glMaterial(GL_FRONT_AND_BACK, GL_SPECULAR, specular); glMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse); glMaterial(GL_FRONT_AND_BACK, GL_AMBIENT, diffuse); glMaterial(GL_FRONT_AND_BACK, GL_SHININESS, shininess); // Set the GL_AMBIENT_AND_DIFFUSE color state variable to be the // one referred to by all following calls to glColor glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE); glEnable(GL_COLOR_MATERIAL); // Create a Directional Light Source glLight(GL_LIGHT0, GL_POSITION, position); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); }
We call this function in main(), right after running setup_GL():
int main(int argc, array(string) argv) { setup_GL(); setup_light(); setup_projection(); make_the_roxen_r(); glutDisplayFunc(animate); glutMainLoop(); return 0; }
There we go! A lighter, rotating nice Roxen 'R'.
A lighted, rotating R
Download the source code
You can download the source code for the small demo program we've built so far: roxen_gl.pike.
Other example programs
Per Hedbor has made a 'Lain' screensaver in OpenGL and Pike: lainsaver-2000-09-14.tar.gz.
Resources