Found at: http://publish.ez.no/article/articleprint/49/

Building 3D worlds with OpenGL



All the major game developers do it, why can't you? Using OpenGL is not simple, but it's not impossible either. (Some experience in C++ and/or Qt may be required to follow this article.)

Disclaimer


The author (that's me) knows next to nothing about OpenGL. I started coding my first OpenGL application a few days ago, and discovered that good tutorials were hard to find. Sure, I found lots of tutorials, but most of them where either poorly documented or too advanced. So, I decided to write a tutorial about what I've learned so far. I hope that my lack of knowledge will lead to an easy to understand, "OpenGL for dummies" style article. (If you know any good beginners' tutorials, or if you find any errors in this tutorial, please post them in a comment.)


Preface


Quoting opengl.org: OpenGL is a cross-platform standard for 3D rendering and 3D hardware acceleration. It is developed by SGI. The runtime library ships with most common operating systems, including MacOS, Windows, and most versions of Unix/Linux. I'll be using Qt to implement the application, but the actual OpenGL commands are the same for any toolkit. (Some of the code in my example is from the Qt gear example.)

Resources


Some sites that might be of interest to OpenGL coders:
opengl.org
SGI's OpenGL page
OpenGL 1.1 specification
OpenGL index page
OpenGL index page (different location)
Mesa - an open source OpenGL implementation
OpenGL in Qt
Flipcode programming tutorials

The Qt bits


To use OpenGL in a Qt application, subclass QGLWidget and add it to your application like any other widget. You should reimplement at least these three virtual functions:

initializeGL(), which should set up the OpenGL rendering context, define display lists, set up static lights etc. It gets called once before the first time resizeGL() or paintGL() is called.

resizeGL(), which should do the calculations regarding OpenGL viewport and projection. It gets called whenever the widget has been resized, and once when it shown for the first time.

paintGL(), which renders the OpenGL scene whenever necessary.

Inside your subclassed QGLWidget you can call all OpenGL functions as you normally would.
In my example I have placed my QGLWidget inside a QMainWindow, in order to get a normal application window with menus. A warning though: OpenGL applications on some systems using current version of Qt (2.2.3) will cause a segmentation fault on exit.


A 3D coordinate system. Z points away from you.

The concept of 3D


When working in 3D you must understand the concept of a three-dimensional coordinate system. A 3D coordinate system has 3 directions, the x, y and z directions, which describe the right, up, and forwards directions. The left, down and backwards directions are described by negative x, y and z values.

A point in three dimensional space can be described using these three values. The statement ( 2, 4, -1 ) means a point that lies 2 units to the right, 4 units up, and 1 unit towards us. All points are relative to the point (0,0,0). A point in OpenGL is called a vertex, plural vertices. A line that connects two vertices is an edge.

My first approach to generating 3D graphics with a computer was the Persistence of Vision Raytracer (POV-Ray). POV-Ray requires at least three types of elements to make up an image: One or more objects to look at, one ore more light sources, and a camera. In OpenGL the objects and light sources are roughly the same, but instead of a camera (located at a point) OpenGL uses a viewport. The viewport is like a square window through which we can look into our 3D world.

The OpenGL way of doing things


OpenGL is a low-level API. In POV-Ray you can describe complex objects by declaring primitives (basic, built-in objects) such as 'sphere' and 'box', and add or subtract them from each other. In OpenGL you have to build everything from the ground and up. You start with vertices, and connect them to make triangles, which in turn can be combined to build e.g. a sphere. The OpenGL primitives are more basic, such as 'triangles' and 'triangle-strips'.
GLU, the OpenGL Utilities, adds some higher-level functionality, such as 'sphere' and 'cylinder' primitives, but it is still a lot of work to build complex shapes.

Note: OpenGL can be useful for 2D graphics, too. By specifying that objects far away should not look smaller than close objects, you'll get a flat 2D world. This is used in some 2D games.

OpenGL is a state machine. This means that when you set any properties, they will stay that way, independent of scopes and function calls, until the program stops or you change them. If you are not aware of this, you will, like I did, experience some weird bugs.


Rotating landscape with building

A red house on a red island in a red sea


While this does sound kind of poetic, it does not show much of what you can do with OpenGL. It has no textures, and only one light source. But for a newbie OpenGL program, it does look kind of cool, doesn't it?

Basically, what I wanted to do was to build a landscape from a height field. The height field is just a 2D array in map.hpp. (In a real world application you would of course use an image instead.) Each point in the height field corresponds to the height of a vertex in my landscape, and four points make up a square. To make my landscape more detailed, I split the squares into triangles when necessary. I actually use a second 2D array to determine how to split each square, upper-left-to-lower-right or upper-right-to-lower-left. Then I make the whole scene rotate, with an option to pause the rotation.

If you think this is a somewhat cumbersome and ineffective way of doing things, you are completely right. One example of this is that all my triangles are evenly distributed. The world would be both faster to render and better looking if I had used large triangles for flat surfaces, and small triangles for uneven surfaces. But I am, as I must have pointed out a few times by now, just a newbie.


The code


I use only two classes, MainWindow, which manages the menus and the timer used for rotation, and GameWidget, which does all the interesting stuff. GameWidget contains the three re-implemented QGLWidget functions, initializeGL(), resizeGL() and paintGL(). You should also take a look at the buildLandscape() function, this one does all the real work in my case.

initializeGL()


To be short: I declare a position array for the light source, and a nice red color. Then lighting is enabled, and a few states regarding lighting are set, and finally I build a display list. This is a list of OpenGL instructions, that are saved so I can call them later without doing all the heavy calculations in the buildLandscape() function once more.


void GameWidget::initializeGL()
{
    // lighting settings
    static GLfloat pos[4] = { 5.0, 5.0, 5.0, 1.0 };
    glLightfv( GL_LIGHT0, GL_POSITION, pos );
    glEnable( GL_CULL_FACE );
    glEnable( GL_LIGHTING );
    glEnable( GL_LIGHT0 );
    glEnable( GL_DEPTH_TEST );

    static GLfloat ared[4] = { 0.8, 0.1, 0.0, 1.0 };
    Landscape = glGenLists(1);     // start building a display list
    glNewList( Landscape, GL_COMPILE );
    glMaterialfv( GL_FRONT, GL_AMBIENT_AND_DIFFUSE, ared );
    buildLandscape();
    glEndList();                   // done with display list

    glEnable( GL_NORMALIZE );
}



resizeGL()


This function sets up the viewport for my scene. It is pretty standard stuff, in many cases you can simply copy this into your own program.


void GameWidget::resizeGL( int width, int height )
{
    GLfloat w = (float) width / (float) height;
    GLfloat h = 1.0;

    glViewport( 0, 0, width, height );
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glFrustum( -w, w, -h, h, 5.0, 60.0 );
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef( 0.0, 0.0, -40.0 );
}


paintGL()


This function draws the scene whenever necessary. It also does the rotation. As you can see, it calls the display list I made in initializeGL().


void GameWidget::paintGL()
{
    if ( Animate )
        Angle += 2.0;

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glPushMatrix();
    glTranslatef( 0.0, -5.0, 0.0 );
    glRotatef( Angle, 0.0, 1.0, 0.0 );
    glCallList( Landscape );             // call display list
    glPopMatrix();
}



buildLandscape()


This function is to long to be shown in detail here, but I'll explain its main points: It uses a double for loop to loop through each point in the height field. For each point, it checks the surrounding points to see if it should split the square into two triangles, and if so, which of the two possible diagonals it should use. It must also calculate the surface normal for each square or triangle, this is set with the glNormal3f() function. The surface normal is a vector at 90 degrees to the square/triangle. It is used to calculate how light reflects off surfaces.

Grab the source


This is all you need to build OpenGL worlds with Qt. Now, if you do a search for "OpenGL" and "tutorial" on the net, hopefully you'll be able to follow some of the more advanced tutorials out there. Oh, and by the way, here is the source:

qgl-demo.tar.gz (11 kb)
Released under the GPL.
Requires: Qt and tmake, available from TrollTech.
Qt must be compiled with OpenGL enabled.

Build instructions:

tar -zxf qgl-demo.tar.gz
cd qgl-demo
tmake -o Makefile qgl-demo.pro
make
./qgl-demo


If you have any questions, please post a comment to this article!


| Back to normal page view |