An Introduction to OpenGL Programming

OpenGL is a well-known standard for generating 3-D as well as 2-D graphics that is extremely powerful and has many capabilities. OpenGL is defined and released by the OpenGL Architecture Review Board (ARB).

This article is a gentle introduction to OpenGL that will help you understand drawing using OpenGL.

The latest version of OpenGL at the time of this writing is 4.4, which uses a different technique for drawing from the one presented here. Nevertheless, the purpose of this article is to help you understand OpenGL philosophy, not teach you how to code using the latest OpenGL version. Therefore, the presented source code can be run on Linux machines that have an older OpenGL version installed.

Installing OpenGL

If you run the following command on a Debian 7 system to find all packages that contain the word "opengl", you'll get plenty of output (Figure 1):


$ apt-cache search opengl

Figure 1. Running apt-cache search opengl

There are many free OpenGL implementations for Linux, but you need only one. I installed FreeGLUT, because it is the most up to date. FreeGLUT is an open-source alternative to the OpenGL Utility Toolkit (GLUT) library:


root@mail:~# apt-get install freeglut3 freeglut3-dev libglew-dev
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following package was automatically installed 
 and is no longer required:
  fonts-liberation
Use 'apt-get autoremove' to remove it.
The following extra packages will be installed:
  libgl1-mesa-dev libglu1-mesa-dev libice-dev libpthread-stubs0
  libpthread-stubs0-dev libsm-dev libx11-dev libx11-doc 
  libxau-dev libxcb1-dev libxdmcp-dev libxext-dev libxt-dev 
  mesa-common-dev x11proto-core-dev x11proto-input-dev 
  x11proto-kb-dev x11proto-xext-dev xorg-sgml-doctools xtrans-dev
Suggested packages:
  libice-doc libsm-doc libxcb-doc libxext-doc libxt-doc
The following NEW packages will be installed:
  freeglut3 freeglut3-dev libgl1-mesa-dev libglu1-mesa-dev 
  libice-dev libpthread-stubs0 libpthread-stubs0-dev libsm-dev 
  libx11-dev libx11-doc libxau-dev libxcb1-dev libxdmcp-dev 
  libxext-dev libxt-dev mesa-common-dev x11proto-core-dev 
  x11proto-input-dev x11proto-kb-dev x11proto-xext-dev 
  xorg-sgml-doctools xtrans-dev
0 upgraded, 22 newly installed, 0 to remove and 0 not upgraded.
Need to get 7,651 kB of archives.
After this operation, 24.9 MB of additional disk space 
 will be used.
Do you want to continue [Y/n]?

You also will need a C++ compiler to compile the code.

Finally, you may need to install the mesa-utils package in order to be able to use the glxinfo command:


# apt-get install mesa-utils

The glxinfo command displays useful information about your OpenGL installation, as you can see in the following output:


...
GLX version: 1.4
GLX extensions:
    GLX_ARB_get_proc_address, GLX_ARB_multisample, 
    GLX_EXT_import_context, GLX_EXT_texture_from_pixmap, 
    GLX_EXT_visual_info, GLX_EXT_visual_rating,
    GLX_MESA_copy_sub_buffer, GLX_MESA_multithread_makecurrent,
    GLX_OML_swap_method, GLX_SGIS_multisample, GLX_SGIX_fbconfig,
    GLX_SGIX_pbuffer, GLX_SGI_make_current_read
OpenGL vendor string: VMware, Inc.
OpenGL renderer string: Gallium 0.4 on llvmpipe 
  (LLVM 3.4, 128 bits)
OpenGL version string: 2.1 Mesa 10.1.3
OpenGL shading language version string: 1.30
OpenGL extensions:
...

Mesa is a 3-D graphics library with an API that is so very similar to OpenGL's, it is pretty much indistinguishable.

OpenGL Pipeline

Figure 2—taken from the OpenGL Shading Language book (aka "The Orange Book")—shows the programmable OpenGL pipeline with the vertex and fragment processors. As you can imagine, the OpenGL pipeline is complex, but you do not have to understand it fully in order to be able to use OpenGL. The Pipeline shows how OpenGL operates in the background. Newer versions of the OpenGL Pipeline are even more complex!

Figure 2. OpenGL Architecture

OpenGL is a big state machine. Most calls to OpenGL functions modify a global state that you cannot access directly.

The OpenGL Shading Language code that is intended for execution on one of the OpenGL programmable processors is called a Shader. The OpenGL Shading Language has its roots in C (presenting the OpenGL Shading Language is beyond the scope of this article).

OpenGL does not define a windowing layer, because it tries to be platform-neutral and leaves this functionality to the operating system. The OS must provide a rendering context that accepts commands as well as a framebuffer that keeps the results of the drawing commands.

Matrix Algebra is extensively used in 3-D graphics, so it is good for you to know how to add, multiply, subtract and divide matrices, although you will not need to code such operations yourself. It also is useful to become familiar with 3-D coordinates and be able to sketch on paper the 3-D scene you are trying to draw.

Drawing a Triangle

Now it's time for some real OpenGL code. The code in Listing 1, when executed, draws a triangle on-screen using OpenGL.

Listing 1. triangle.cc

// Programmer: Mihalis Tsoukalos
// Date: Wednesday 04 June 2014
//
// A simple OpenGL program that draws a triangle.

#include "GL/freeglut.h"
#include "GL/gl.h"

void drawTriangle()
{
    glClearColor(0.4, 0.4, 0.4, 0.4);
    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f(1.0, 1.0, 1.0);
    glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0);

        glBegin(GL_TRIANGLES);
                glVertex3f(-0.7, 0.7, 0);
                glVertex3f(0.7, 0.7, 0);
                glVertex3f(0, -1, 0);
        glEnd();

    glFlush();
}

int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE);
    glutInitWindowSize(500, 500);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("OpenGL - Creating a triangle");
    glutDisplayFunc(drawTriangle);
    glutMainLoop();
    return 0;
}

The code in Listing 1 for setting up OpenGL is large, but you will have to learn it only once.

On a Debian 7 system, the following command compiled the triangle.cc OpenGL program without any error messages:


$ g++ triangle.cc -lglut -o triangle

On an Ubuntu Linux system, the same command produced the following error messages:


/usr/bin/ld: /tmp/ccnBP5li.o: undefined reference to symbol 
 ↪'glOrtho'
//usr/lib/x86_64-linux-gnu/mesa/libGL.so.1: error adding 
//symbols: DSO missing from command line
collect2: error: ld returned 1 exit status

The solution is to compile the triangle.cc program by linking the executable to an additional library (-lGL):


mtsouk@mtsouk-VirtualBox:/media/sf_OpenGL.LJ/code$ g++ 
 ↪triangle.cc -lglut -lGL -o triangle

The libGL.so library accepts OpenGL commands and makes sure that they are put on the screen in some way. If your graphics card does not have 3-D acceleration, libGL contains a software renderer that gives a 2-D image as output to the X Window System. This is the case with Mesa. libGL also can pass the OpenGL information to the X Window System, if the GLX extension is present. Then, the X Window System can do software rendering with the help of Mesa or use hardware acceleration.

The output from the executable will produce the triangle shown in Figure 3. The correct compilation of triangle.cc is proof that your Linux system can be used for developing OpenGL applications.

Figure 3. Drawing a Triangle Using OpenGL

There is more than one way of drawing a triangle using OpenGL, mostly depending on the OpenGL version you are using. Although the presented method is an older way of programming OpenGL applications, I find it straightforward and easy to understand. Remember, whichever method you use, the coordinates of the triangle will be the same.

Note: keep in mind that the most important part of this article is the code!

Drawing a Cube Using OpenGL

Next, let's program an application that draws a cube using OpenGL. You need to construct the cube using triangles. A cube has six faces, and each face needs at least two triangles to be defined. The reason I say "at least" is that, generally speaking, you can use more triangles if you want in order to have a smoother and more accurate shape, but when drawing a cube, that's not necessary. As I'm sure you've realized, you are going to need 12 triangles in total.

A cube also has eight vertices. Each triangle requires three different vertices to be defined.

Listing 2 shows the full source code of the cube.cc file.

Listing 2. cube.cc

// Programmer: Mihalis Tsoukalos
// Date: Wednesday 04 June 2014
//
// A simple OpenGL program that draws a colorful cube
// that rotates as you move the arrow keys!
//
// g++ cube.cc -lm -lglut -lGL -lGLU -o cube

#define GL_GLEXT_PROTOTYPES
#ifdef __APPLE__
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
#include <math.h>

// Rotate X
double rX=0;
// Rotate Y
double rY=0;

// The coordinates for the vertices of the cube
double x = 0.6;
double y = 0.6;
double z = 0.6;

void drawCube()
{
        // Set Background Color
    glClearColor(0.4, 0.4, 0.4, 1.0);
        // Clear screen
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Reset transformations
    glLoadIdentity();

    // Rotate when user changes rX and rY
    glRotatef( rX, 1.0, 0.0, 0.0 );
    glRotatef( rY, 0.0, 1.0, 0.0 );

    // BACK
        glBegin(GL_TRIANGLES);
            glColor3f(0.4, 0.3, 0.5);
                glVertex3f(x, y, z);
                glVertex3f(x, -y, z);
                glVertex3f(-x, y, z);
        glEnd();

        glBegin(GL_TRIANGLES);
            glColor3f(0.5, 0.3, 0.2);
                glVertex3f(-x, -y, z);
                glVertex3f(x, -y, z);
                glVertex3f(-x, y, z);
        glEnd();

        // FRONT
        // Using 4 trianges!
        glBegin(GL_TRIANGLES);
            glColor3f(0.1, 0.5, 0.3);
                glVertex3f(-x, y, -z);
                glVertex3f(0, 0, -z);
                glVertex3f(-x, -y, -z);
        glEnd();

        glBegin(GL_TRIANGLES);
                glColor3f(0.0, 0.5, 0.0);
                glVertex3f(-x, -y, -z);
                glVertex3f(0, 0, -z);
                glVertex3f(x, -y, -z);
        glEnd();

        glBegin(GL_TRIANGLES);
            glColor3f(0.1, 0.3, 0.3);
                glVertex3f(-x, y, -z);
                glVertex3f(x, y, -z);
                glVertex3f(0, 0, -z);
        glEnd();

        glBegin(GL_TRIANGLES);
                glColor3f(0.2, 0.2, 0.2);
                glVertex3f(0, 0, -z);
                glVertex3f(x, y, -z);
                glVertex3f(x, -y, -z);
        glEnd();

        // LEFT
        glBegin(GL_TRIANGLES);
        glColor3f(0.3, 0.5, 0.6);
                glVertex3f(-x, -y, -z);
                glVertex3f(-x, -y, z);
                glVertex3f(-x, y, -z);
        glEnd();

        glBegin(GL_TRIANGLES);
                glColor3f(0.5, 0.5, 0.5);
                glVertex3f(-x, y, z);
                glVertex3f(-x, -y, z);
                glVertex3f(-x, y, -z);
        glEnd();

        // RIGHT
        glBegin(GL_TRIANGLES);
        glColor3f(0.2, 0.2, 0.2);
                glVertex3f(x, y, z);
                glVertex3f(x, y, -z);
                glVertex3f(x, -y, z);
        glEnd();

        glBegin(GL_TRIANGLES);
        glColor3f(0.0, 0.0, 0.0);
                glVertex3f(x, -y, -z);
                glVertex3f(x, y, -z);
                glVertex3f(x, -y, z);
        glEnd();

        // TOP
        glBegin(GL_TRIANGLES);
        glColor3f(0.6, 0.0, 0.0);
                glVertex3f(x, y, z);
                glVertex3f(x, y, -z);
                glVertex3f(-x, y, -z);
        glEnd();

        glBegin(GL_TRIANGLES);
        glColor3f(0.6, 0.1, 0.2);
                glVertex3f(-x, y, z);
                glVertex3f(x, y, z);
                glVertex3f(-x, y, -z);
        glEnd();

        // BOTTOM
        glBegin(GL_TRIANGLES);
        glColor3f(0.4, 0.0, 0.4);
                glVertex3f(-x, -y, -z);
                glVertex3f(-x, -y, z);
                glVertex3f(x, -y, z);
        glEnd();

        glBegin(GL_TRIANGLES);
                glColor3f(0.3, 0.0, 0.3);
                glVertex3f(x, -y, -z);
                glVertex3f(-x, -y, -z);
                glVertex3f(x, -y, z);
        glEnd();

    glFlush();
    glutSwapBuffers();
}

void keyboard(int key, int x, int y)
{
    if (key == GLUT_KEY_RIGHT)
        {
                rY += 15;
        }
    else if (key == GLUT_KEY_LEFT)
        {
                rY -= 15;
        }
    else if (key == GLUT_KEY_DOWN)
        {
                rX -= 15;
        }
    else if (key == GLUT_KEY_UP)
        {
                rX += 15;
        }

    // Request display update
    glutPostRedisplay();
}


int main(int argc, char **argv)
{
        // Initialize GLUT and process user parameters
        glutInit(&argc, argv);

        // Request double buffered true color window with Z-buffer
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);

        glutInitWindowSize(700,700);
        glutInitWindowPosition(100, 100);

        // Create window
        glutCreateWindow("Linux Journal OpenGL Cube");

        // Enable Z-buffer depth test
        glEnable(GL_DEPTH_TEST);

        // Callback functions
        glutDisplayFunc(drawCube);
        glutSpecialFunc(keyboard);

        // Pass control to GLUT for events
        glutMainLoop();

        return 0;
}

Figure 4 shows two different screenshots from the cube.cc application. The left screenshot of the program just shows a square, because you are looking straight at the front face of the cube and can't see any of the other five faces of the cube. Therefore, the shape you see on screen looks like it is in 2-D. When you start rotating the cube using the arrow keys, you can tell that it is a 3-D shape.

Figure 4. The Graphical Output of cube.cc

Explaining the Code

Each triangle is defined by three vertices. Each vertex is defined with the help of a single point (x,y,z). Each point comes with three numbers (coordinates), because OpenGL uses a 3-D space. A cube needs eight vertices to be defined.

As an exercise, the front face of the cube is made using four triangles. Figure 5 shows the coordinates of the front face of the cube when four triangles are used to define it. The point (0,0,-0.6) has been chosen arbitrarily. You just need a point that belongs to the front face of the cube.

Figure 5. The Coordinates of the Front Face of the Cube

Defining the Vertices:

Figure 6 shows the coordinates of the vertices of the cube for x=0.6, y=0.6 and z=0.6. Note that the vertices that define an edge have two out of their three coordinates exactly the same.

Figure 6. The Vertices of the Cube

As you can see in Figure 6, the coordinates of the four vertices that define the front face and the respective coordinates of the four vertices that define the back face differ only in the value of the z-axis coordinate.

Defining the Triangles:

The triangles are based on vertices. Each triangle needs three vertices to be defined. Two triangles are needed for defining each face of the cube, except for the front face where four triangles are used. The following commands create a color triangle based on the values of x, y and z:


glBegin(GL_TRIANGLES);
glColor3f(0.4, 0.0, 0.4);
        glVertex3f(-x, -y, -z);
        glVertex3f(-x, -y, z);
        glVertex3f(x, -y, z);
glEnd();

Changing Color:

You can change the color of a shape with the glColor3f(...) command. The glColor3f(...) command takes three parameters, which represent the RGB values of the desired color.

Changing Perspective:

You can change the perspective of the scene with the following commands:


glRotatef(rX, 1.0, 0.0, 0.0);
glRotatef(rY, 0.0, 1.0, 0.0);

As the user presses one of the four arrow keys, the perspective angle changes accordingly.

Drawing the Cube:

Drawing the cube is done face by face. Each face needs two triangles to be drawn except for the front face where four triangles are used.

Once you get the coordinates of the triangles correctly, drawing is pretty easy. The drawing of each triangle starts with the glBegin(GL_TRIANGLES) command and finishes with the glEnd() command. The GL_TRIANGLES is an OpenGL primitive. Other primitive types are GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP and GL_POLYGON. Eventually, every primitive shape in OpenGL is represented by one or more triangles.

If you are drawing using triangles (GL_TRIANGLES), the order in which you put the vertices is not important. If you are drawing using rectangles (GL_POLYGON), the four vertices of the rectangle must be drawn in the right order, although it does not matter if you draw CW or CCW. If the drawing order is wrong, the rectangle you are trying to draw will have a big hole in it.

Using the Arrow Keys:

Using the arrow keys is pretty simple. The following code checks for the arrow keys and acts accordingly:


void keyboard(int key, int x, int y)
{
    if (key == GLUT_KEY_RIGHT)
        {
                rY += 15;
        }
    else if (key == GLUT_KEY_LEFT)
        {
                rY -= 15;
        }
    else if (key == GLUT_KEY_DOWN)
        {
                rX -= 15;
        }
    else if (key == GLUT_KEY_UP)
        {
                rX += 15;
        }

    // Request display update
    glutPostRedisplay();
}

The keyboard(...) function is registered inside the main(...) function using the following line of code:


glutSpecialFunc(keyboard);
Rotating a Cube Automatically

As a bonus, let's look at how to make a cube rotate automatically (Figure 7).

This time, the cube is drawn using rectangles. As the cube has six faces, only six rectangles are needed. Drawing using rectangles is easier and requires less code, but complex OpenGL code runs faster when triangles are used.

Note: every object can be split into triangles, but a triangle cannot be split into anything other than triangles.

Figure 7. The Output of rotateCube.cc

Listing 3 shows the source code for rotateCube.cc.

Listing 3. rotateCube.cc

// Programmer: Mihalis Tsoukalos
// Date: Wednesday 04 June 2014
//
// A simple OpenGL program that draws a triangle
// and automatically rotates it.
//
// g++ rotateCube.cc -lm -lglut -lGL -lGLU -o rotateCube

#include <iostream>
#include <stdlib.h>

// the GLUT and OpenGL libraries have to be linked correctly
#ifdef __APPLE__
#include <OpenGL/OpenGL.h>
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

using namespace std;

// The coordinates for the vertices of the cube
double x = 0.6;
double y = 0.6;
double z = 0.6;

float angle = 0.0;

void drawCube()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Reset transformations
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glTranslatef(0.0, 0.0, -5.0);

        // Add an ambient light
        GLfloat ambientColor[] = {0.2, 0.2, 0.2, 1.0};
        glLightModelfv(GL_LIGHT_MODEL_AMBIENT, ambientColor);

        // Add a positioned light
        GLfloat lightColor0[] = {0.5, 0.5, 0.5, 1.0};
        GLfloat lightPos0[] = {4.0, 0.0, 8.0, 1.0};
        glLightfv(GL_LIGHT0, GL_DIFFUSE, lightColor0);
        glLightfv(GL_LIGHT0, GL_POSITION, lightPos0);

        glTranslatef(0.5, 1.0, 0.0);
        glRotatef(angle, 1.0, 1.0, 1.0);

    glRotatef( angle, 1.0, 0.0, 1.0 );
    glRotatef( angle, 0.0, 1.0, 1.0 );
        glTranslatef(-0.5, -1.0, 0.0);

        // Create the 3D cube

    // BACK
    glBegin(GL_POLYGON);
    glColor3f(0.5, 0.3, 0.2);
    glVertex3f(x, -y, z);
    glVertex3f(x, y, z);
    glVertex3f(-x, y, z);
    glVertex3f(-x, -y, z);
    glEnd();

        // FRONT
        glBegin(GL_POLYGON);
        glColor3f(0.0, 0.5, 0.0);
        glVertex3f(-x, y, -z);
        glVertex3f(-x, -y, -z);
        glVertex3f(x, -y, -z);
        glVertex3f(x, y, -z);
        glEnd();

        // LEFT
        glBegin(GL_POLYGON);
        glColor3f(0.5, 0.5, 0.5);
        glVertex3f(-x, -y, -z);
        glVertex3f(-x, -y, z);
        glVertex3f(-x, y, z);
        glVertex3f(-x, y, -z);
        glEnd();


        // RIGHT
        glBegin(GL_POLYGON);
        glColor3f(0.0, 0.0, 0.0);
        glVertex3f(x, -y, -z);
        glVertex3f(x, -y, z);
        glVertex3f(x, y, z);
        glVertex3f(x, y, -z);
        glEnd();

        // TOP
        glBegin(GL_POLYGON);
        glColor3f(0.6, 0.0, 0.0);
        glVertex3f(x, y, z);
        glVertex3f(-x, y, z);
        glVertex3f(-x, y, -z);
        glVertex3f(x, y, -z);
        glEnd();


        // BOTTOM
        glBegin(GL_POLYGON);
        glColor3f(0.3, 0.0, 0.3);
        glVertex3f(-x, -y, -z);
        glVertex3f(-x, -y, z);
        glVertex3f(x, -y, z);
        glVertex3f(x, -y, -z);
        glEnd();

        glFlush();
    glutSwapBuffers();
}

// Function for increasing the angle variable smoothly, 
// keeps it <=360
// It can also be implemented using the modulo operator.
void update(int value)
{
        angle += 1.0f;
        if (angle > 360)
                {
                        angle -= 360;
        }

        glutPostRedisplay();
        glutTimerFunc(25, update, 0);
}

// Initializes 3D rendering
void initRendering()
{
        glEnable(GL_DEPTH_TEST);
        glEnable(GL_COLOR_MATERIAL);

        // Set the color of the background
        glClearColor(0.7f, 0.8f, 1.0f, 1.0f);
        glEnable(GL_LIGHTING);
        glEnable(GL_LIGHT0);
        glEnable(GL_NORMALIZE);
}


// Called when the window is resized
void handleResize(int w, int h)
{
        glViewport(0, 0, w, h);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(45.0, (double)w / (double)h, 1.0, 200.0);
}


int main(int argc, char **argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(700, 700);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("OpenGL - Rotating a Cube");
        initRendering();

    glutDisplayFunc(drawCube);
        glutReshapeFunc(handleResize);

        // Add a timer for the update(...) function
    glutTimerFunc(25, update, 0);

    glutMainLoop();
    return 0;
}

Note that the main(...) functions of triangle.cc, cube.cc and rotateCube.cc are pretty similar, although the three programs perform different tasks.

The crucial thing here is the usage of the glutTimerFunc(...) function. It registers a timer callback for the update(...) function that is going to be triggered in a specified number of milliseconds. The update(...) function changes the angle of the scene every time it's called.

Conclusion

OpenGL programming is not easy, but this article should help you start writing OpenGL applications quickly. If you want to become proficient in OpenGL, keep practicing writing more OpenGL programs. OpenGL is easy to start with but difficult to master.

Acknowledgement

I would like to thank Dr Nikos Platis for sharing a small part of his OpenGL knowledge with me.

Resources

OpenGL: http://www.opengl.org

Learning Modern 3D Graphics Programming: http://www.arcsynthesis.org/gltut

OpenGL Superbible, 6th edition, Graham Sellers, Richard S. Wright and Nicholas Haemel, Addison Wesley, ISBN: 0321902947

GLEW: http://glew.sourceforge.net

The Mesa 3D Graphics Library: http://www.mesa3d.org

Mihalis Tsoukalos is a UNIX administrator and developer, a DBA and mathematician who enjoys technical writing. He is the author of Go Systems Programming and Mastering Go. You can reach him at http://www.mtsoukalos.eu and @mactsouk.

Load Disqus comments