Wednesday, October 25, 2017

OpenGL Tutorial - Part 3 of 3

Welcome back to my OpenGL tutorial, this time we're on the third and final part where we will get to experience some hands-on coding with some of the more basic features of OpenGL to implement something a little more cooler than a simple stationery white square.

I'm going to dump some code that draws a rotating 3D-cube on the screen in various colors.  The first thing I'd like to do after that is explain what the code does to get the cube to display on the screen.  After that, we can better guide the reader through the process of implementing some extra bits.

At first, this code will draw a rotating 3D-cube, stationery on a black background.  And with some extra effort, we'll be animating the cube so that it bounces around inside a larger defined boundary cube.  This is like the old screen saver with a bouncing circle along the edges of your monitor.  Except this will be a cube.

Starting Point

Copy-and-paste the following code.  This is some code that I wrote.  Run it to make sure that it compiles, builds and executes properly.  It should be a rotating cube.

#include <GL\glew.h>
#include <GL\freeglut.h>
 
//disables console window so only our application window displays
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" )
 
//defining variables here
float rot = 0.01;
 
//draws six faces of a cube with varying colors
void drawCube(void)
{
    glBegin(GL_QUADS);
    {
        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
 
        glColor3f(1.0f, 0.5f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
 
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
 
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glEnd();
    }
}
 
//draw our cube at its location and rotation angle
void render(void)
{
    //clear, reset, recenter, update rotation
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
 
    //snap our cube to its position and rotate it
    glTranslatef(-1.5f, 0.0f, -15.0f);
    glRotatef(rot, 1, 1, 0);
 
    //draw our cube
    drawCube();
 
    //standard call to dump our drawing into the next frame
    glutSwapBuffers();
}
 
//animation for rotating the cube
void animate()
{
 
    //update rotation angle
    rot += 1;
    rot = rot >= 360 ? 0 : rot;
 
    //refresh screen
    glutPostRedisplay();
}
 
//reshapes the drawing when window is resized
void reshape(int wint h)
{
    glViewport(0, 0, wh);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (float)w / (float)h, 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
 
//main entry point
int main(int argcchar** argv)
{
    //standard initialization routine
    glutInit(&argcargv);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(320, 230);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutCreateWindow("Simple Shape Drawing");
 
    //color to draw background
    glClearColor(0.0, 0.0, 0.0, 0.0);
 
    //enable depth viewing
    glEnable(GL_DEPTH_TEST);
 
    //call our display function which actually does some drawing
    glutDisplayFunc(render);
    glutIdleFunc(animate);
    glutReshapeFunc(reshape);
 
    //enters our display functions into a repeating loop
    glutMainLoop();
 
    return 0;
}

Explaining Code

Let's dig deeper and see what our code is really doing.

Creating the Window

Let's have a look at the main entry point: the method named main.  This serves as a basic naming function for C++ to know where to begin.  Inside that method is a pretty standard OpenGL initialization routine.  The first glutInit call initializes the OpenGL engine with the given command line arguments to main.  Then, the window position and size is initialized just after.  You can modify the values in these if you like.

The glutInitDisplayMode defines the display mode for the created window.  You can play around with these settings if you want.  The use of the "|" (bitwise or) operator allows settings to be chained together so that you can specify multiple different settings in the same line.  If you remove the GLUT_DOUBLE, which stands for double buffering, you may notice the cube rotates very hyper, i.e. very less smoothly.

Finally, glutCreateWindow will form the window with the given title.  You can name this however you want.  Shortly after, glEnable(GL_DEPTH_TEST) allows us to view our renderings in 3D by performing a depth test so that only the frontward faces are visible.  If you remove this line, you'll notice that you can see all six faces of the cube at once as they're blended together in weird ways.

Calling our Drawers

Next, we reference our method where the actual cube drawing takes place with a trio of methods.  The glutDisplayFunc is what gets called when the window first displays or displays after being gone.  The glutIdleFunc gets called constantly.  Lastly, the glutReshapeFunc is what will be called when changing the size of the window when it is running.  Here, we use the display func to store our actual drawing of the cube, and the idle func to animate our cube in its rotational manner.

Lastly, the reshape function is generally used to maintain any aspect ratio of the drawn shapes.  In this demo, we use it to also grow or shrink the cube as the window changes size.  You can see how this is done in the reshape method, which utilizes a bit of a standard routine.  The important call is in gluPerspective, which sets the field of view to a 45 degree cone, and the aspect ratio to whatever the ratio of the window is.  The final two parameters to that method define the clipping points in the depth.  Here, anything outside of Z=1 and Z=1000 will be clipped and not drawn.

Drawing the Cube

As stated just a bit ago, our cube will get drawn in the display func, which we've established is the function named render.  Going there, we notice the first thing done is to clear the screen and load the identity via glLoadIdentity.  This refers to the identity matrix, in which OpenGL uses a matrix to store the contents of drawn shapes.  This must be reset at the beginning of every display func so that it can successfully redraw the intended drawing properly from scratch.

The next thing done is to translate our "position" to the location where we want to draw the cube.  Again, this has to do with the matrix OpenGL uses.  We will first move to the location, and then we will perform the rotation at that point.  When rotation and translation are done in opposite order, the cube will rotate around the point instead of in place at the point.  This may be counter-intuitive, but to me it seems backwards.  So it may be best to think of these operations as working in reverse.

Then, the cube is drawn in our custom method named drawCube.  This is done by telling OpenGL that we're about to begin drawing some "gl_quads" primitives with glBegin(GL_QUADS).  These are four-sized rectangles specified by four vertices.  To draw a cube with rectangles, we basically stitch them together on common vertices to define the six faces.  This part may seem a bit too "numbery", so you may want to suffice it enough to say, that this sequence of vertex assignments is indeed a cube.  However, note at the beginning of each face, we've defined a new color so that our faces are more distinguishable when drawn.  Lastly, a call to glEnd indicates that we're done drawing these primitives.  I like to use curly brackets around these sections, but note that they're more for visual code style and are not necessary.

Finally, the end of our render function calls glutSwapBuffers as part of its double buffering routine to increase the smoothness of the animation.

Animating the Cube

Our last code bit basically is the animate method which is called by our constantly repeating idle func.  This part is simple: all we do is increment the rotation angle and keep it bounded cyclically between 0 and 360.  The important bit is when glutPostRedisplay is called to send a signal so that our display function is called to draw our cube.

That should be it!  Now that we've reviewed pretty much all the basics of our demo program, we can begin to add a feature to it.  Let's try to get this rotating cube to now move and then get it to bounce off walls.  Finally, we'll draw the wall we're bouncing the cube off and have ourselves a bit of a nice screensaver type of program.

Adding Features

Now for the fun part.  Basically, I'd like to get the cube to bounce off the walls like a screensaver, except in 3D.

More Member Variables

I've already provided the variable for our rotation angle, but let's add several more that we'll need in adding our feature.  Place these variables below just below the rotation angle variable, rot, so that these are inside our class file but outside of any methods.

Location, Size, Velocity

First, let's add the location.  This will be a 3D-coordinate system location defined as an array of 3 floating point values.  The first two represent the position left/right (X) and top/down (Y) in the view.  The last value represents the depth (Z).  Since we more or less want the view to be some distance away from us, we specify an initial deep depth of -15.

float location[3] = { -1.5f, 0.0f, -15.0f }; //coordinate vector

To control the size of our cube, let's define a scale variable that shrinks or inflates our cube based on its setting.  A value of 0.5 halves the size, and a value of 2 will double it.  We will see its use later and how it can be used to scale our drawn shapes.

float scale = 0.5;

To animate our cube, we define a 3D-velocity that represents how much our cube moves in every direction.  As far as animation goes, this is the amount the cube will move across every second.

float velocity[3] = { 1.0f, 1.0f, 1.0f }; //velocity vector

Boundary Walls

To allow our cube to bounce properly, we'll need to define where the walls are.  This is a bounding box with six faces, and so hence we'll have six values to define the location of those planar faces.

float velocity[3] = { 1.0f, 1.0f, 1.0f }; //velocity vector
float lbounds[3] = {-5.0f, -5.0f, -50.0f}; //boundary box lower limits

Animation Timing

Since the animation routine will be called as indeterminate intervals, we'll have to handle animation by checking against the amount of time between calls and scaling our velocity with those elapsed time periods.  These below variables will allow us to collect the time since epoch and use the elapsed milliseconds to more smoothly draw our animated cube.

First, add an import with the include tag at the top of the file:

#include <chrono>

Then, add these variables with our others:

__int64 elapsedTimeInMS = 0; //time since last draw
__int64 lastTimeEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::system_clock::now().time_since_epoch()).count(); //current time

Updates to Drawing

Since we have added some information to describe the position of our cube, let's update our display function so that the cube is always drawn at its given location.  To do this, simply edit our line where the glTranslatef function is located and pass in our shape information:

glTranslatef(location[0], location[1], location[2]);

As for another update, since we have a variable to store the size or scale of our shape, let's also add that below the call to glRotatef:

glScalef(scale, scale, scale);

The scale call basically scales what is drawn by a factor along each of the three dimensions.  If edited, we could scale unevenly across our three dimensions.  For example, a scale of 0.5 will halve the size, and a scale of 2.0 would double it.

Updating Animation

Now that we have some standard location animation variables, we can get to making our cube move.  Inside our idle function, that is, over in our animate function, we can make a few adjustments.  The first thing we'll want to do is find out how much time has elapsed since our last call to animate.  This allows us to smoothly move the cube at its given velocity instead of moving in large hops.  Add this code to the top of our animate function:

    //get elapsed milliseconds
    __int64 timeNow = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    elapsedTimeInMS = timeNow - lastTimeEpoch;
    lastTimeEpoch = timeNow;

Next, we can make the position updates using a loop across our three dimensions:

    //update position and detect wall bouncing
    for (int i = 0; i < 3; i++)
    {
        location[i] = location[i] + velocity[i] * (1.0/ elapsedTimeInMS);
    }  

Finally, we can then implement velocity changes when a "bounce" off the boundary wall is detected.  To do this, whenever a bounce is detected in that dimension, we simply reverse the velocity along that direction.  Add this code after the position updates, but inside our loop:

        if (location[i] > ubounds[i] || location[i] < lbounds[i])
        {
            velocity[i] *= -1; //when wall bounce detected, just simply go in opposite direction
        }

The last part of the animation should remain the same.  When put all together, it should look like this:

//routine to animate our cube with its velocity and elapsed time since last call
void animate(void)
{   
    //get elapsed milliseconds
    __int64 timeNow = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    elapsedTimeInMS = timeNow - lastTimeEpoch;
    lastTimeEpoch = timeNow;
 
    //update position and detect wall bouncing
    for (int i = 0; i < 3; i++)
    {
        location[i] = location[i] + velocity[i] * (1.0/ elapsedTimeInMS);
        if (location[i] > ubounds[i] || location[i] < lbounds[i])
        {
            velocity[i] *= -1; //when wall bounce detected, just simply go in opposite direction
        }
    }   
 
    //update rotation angle
    rot += 1;
    rot = rot >= 360 ? 0 : rot;
 
    //refresh screen
    glutPostRedisplay();
}

Drawing Our Boundary Walls

If you run the code thus far, you should see our cube bounce about its boundary box.  The trouble is, we can't see where our boundary walls are and that would be a nice feature.  So let's work on that.

What we want to do is draw a wire-frame cube.  To do the drawing, we'll do something similar to what was done for drawing our solid cube.  Let's add a method for that first:

//draw the boundary box where cube bounces as a wireframe
void drawBounds(void)
{
    glColor3f(1.0f, 1.0f, 1.0f);
    glPolygonMode(GL_FRONT_AND_BACKGL_LINE);
    glBegin(GL_QUADS);
    {      
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
 
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glEnd();
    }
    glPolygonMode(GL_FRONT_AND_BACKGL_FILL);
}

The first thing to note is that we define a polygon mode in glPolygonMode(GL_FRONT_AND_BACKGL_LINEto be that of a wireframe, using constants that let us know only draw the lines and not fill to a solid color.  The first constant (GL_FRONT_AND_BACK) tells OpenGL to go ahead and draw all faces, even the ones we can't see.  We unset this at the end so that our cube drawing doesn't draw in the same way.

Finally, we can hook up to our new drawBounds method from our display function.  This part is simple, but the complexity behind it is a little more sophisticated.  After having drawn our little cube, we'll want to undo what we've done to the matrix in our translate, rotate and scale operations.  In this way, we can draw the wireframe boundary box in its actual place, devoid of any rotation, scale or translation.

The trick to undoing those operations is in the math.  To undo a scale, we'd need an inverse.  To undo our translation and rotation, we just need a negative.  Additionally, the order here reflects that in the way they were first performed.  Since our scale was last to perform, it is first to be undone, and so on.

    //undo our snap, rotate and scale
    glScalef(1 / scale, 1 / scale, 1 / scale);
    glRotatef(-rot, 1, 1, 0);
    glTranslatef(-location[0], -location[1], -location[2]);
    

Afterwards, we can now draw our boundary box and complete the method.

    //draw the boundary wall
    drawBounds();

Wrapping Up

That should do it.  We've finished doing what we set out to do.  For reference, here is the completed new code.

#include <GL\glew.h>
#include <GL\freeglut.h>
#include <chrono>
 
//disables console window so only our application window displays
#pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) 
 
//defining variables here
float rot = 0.01;
float scale = 0.5;
float location[3] = { -1.5f, 0.0f, -15.0f }; //coordinate vector
float velocity[3] = { 1.0f, 1.0f, 1.0f }; //velocity vector
float lbounds[3] = {-5.0f, -5.0f, -50.0f}; //boundary box lower limits
float ubounds[3] = { 5.0f, 5.0f, -10.0f }; //boundary box upper limits
__int64 elapsedTimeInMS = 0; //time since last draw
__int64 lastTimeEpoch = std::chrono::duration_cast<std::chrono::milliseconds>(
    std::chrono::system_clock::now().time_since_epoch()).count(); //current time
 
//routine to animate our cube with its velocity and elapsed time since last call
void animate(void)
{   
    //get elapsed milliseconds
    __int64 timeNow = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    elapsedTimeInMS = timeNow - lastTimeEpoch;
    lastTimeEpoch = timeNow;
 
    //update position and detect wall bouncing
    for (int i = 0; i < 3; i++)
    {
        location[i] = location[i] + velocity[i] * (1.0/ elapsedTimeInMS);
        if (location[i] > ubounds[i] || location[i] < lbounds[i])
        {
            velocity[i] *= -1; //when wall bounce detected, just simply go in opposite direction
        }
    }   
 
    //update rotation angle
    rot += 1;
    rot = rot >= 360 ? 0 : rot;
 
    //refresh screen
    glutPostRedisplay();
}
 
//draw the boundary box where cube bounces as a wireframe
void drawBounds(void)
{
    glColor3f(1.0f, 1.0f, 1.0f);
    glPolygonMode(GL_FRONT_AND_BACKGL_LINE);
    glBegin(GL_QUADS);
    {      
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
 
        glVertex3f(lbounds[0], ubounds[1], ubounds[2]);
        glVertex3f(lbounds[0], ubounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], lbounds[2]);
        glVertex3f(lbounds[0], lbounds[1], ubounds[2]);
 
        glVertex3f(ubounds[0], ubounds[1], lbounds[2]);
        glVertex3f(ubounds[0], ubounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], ubounds[2]);
        glVertex3f(ubounds[0], lbounds[1], lbounds[2]);
        glEnd();
    }
    glPolygonMode(GL_FRONT_AND_BACKGL_FILL);
}
 
//draws six faces of a cube with varying colors
void drawCube(void)
{
    glBegin(GL_QUADS);
    {
        glColor3f(0.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
 
        glColor3f(1.0f, 0.5f, 0.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
 
        glColor3f(1.0f, 0.0f, 0.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 1.0f, 0.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
 
        glColor3f(0.0f, 0.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, 1.0f);
        glVertex3f(-1.0f, 1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, -1.0f);
        glVertex3f(-1.0f, -1.0f, 1.0f);
 
        glColor3f(1.0f, 0.0f, 1.0f);
        glVertex3f(1.0f, 1.0f, -1.0f);
        glVertex3f(1.0f, 1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, 1.0f);
        glVertex3f(1.0f, -1.0f, -1.0f);
        glEnd();
    }
}
 
//draw our cube at its location and rotation angle
void render(void)
{
    //clear, reset, recenter, update rotation
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
 
    //snap our cube to its position, rotate it and scale it
    glTranslatef(location[0], location[1], location[2]);
    glRotatef(rot, 1, 1, 0);
    glScalef(scale, scale, scale);
 
    //draw our cube
    drawCube();
    
    //undo our snap, rotate and scale
    glScalef(1 / scale, 1 / scale, 1 / scale);
    glRotatef(-rot, 1, 1, 0);
    glTranslatef(-location[0], -location[1], -location[2]);
    
    //draw the boundary wall
    drawBounds();
 
    //standard call to dump our drawing into the next frame
    glutSwapBuffers();
}
 
//reshapes the drawing when window is resized
void reshape(int wint h)
{
    glViewport(0, 0, wh);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, (float)w / (float)h, 1, 1000);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
}
 
//main entry point
int main(int argcchar** argv) {
    //standard initialization routine
    glutInit(&argcargv);
    glutInitWindowPosition(100, 100);
    glutInitWindowSize(320, 230);
    glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
    glutCreateWindow("Simple Shape Drawing");
 
    //color to draw background
    glClearColor(0.0, 0.0, 0.0, 0.0);
 
    //more standard things
    glEnable(GL_DEPTH_TEST);
    glHint(GL_PERSPECTIVE_CORRECTION_HINTGL_NICEST);
 
    //call our display function which actually does some drawing
    glutDisplayFunc(render);
    glutIdleFunc(animate);
    glutReshapeFunc(reshape);
 
    //enters our display functions into a repeating loop
    glutMainLoop();
 
    return 0;
}

Road Map:

Part 1 - Introduction
Part 2 - Setting up OpenGL in Visual Studio
Part 3 - Implementing The Boundary Bouncing Cube

OpenGL Tutorial - Part 2 of 3

Welcome back to my three part OpenGL tutorial.  In the last part, we briefly introduced the idea of OpenGL, but in this part of the tutorial, we will guide the reader through the process of setting up a development environment so that they can write code and run programs in OpenGL.

Without any further ado, here are the steps.

1. Download Glut

The core OpenGL headers and libraries come from glut/freeglut.  Use the link below to download freeglut from the MSVC section (this was listed in a link with text "Download freeglut 3.0.0 for MSVC" as of this writing.)  Go ahead and keep the zip around for later reference.

2. Download Glew

Short for OpenGL Extension Wrangler Library.  This contains a lot of extended features that you may not need until you become more advanced with working in OpenGL.  You can obtain this from the link here:


3. Setup directories and copy files

Set up a directory so that you can copy/move the downloaded files from steps 1 and 2.  This can be mostly anywhere, but you may want to keep it simple.  For example, lets use:
  • Central root location for everything: E:\opengl
  • Place for code and projects to go: E:\opengl\projects
  • Put your library files (i.e. freeglut; other dependencies) in here: E:\opengl\lib
After creating the file structure above, unzip the downloads from steps 1 and 2 and paste their folders into E:\opengl\lib.  So your file tree should look something like:

E:\OPENGL
├───lib
│   ├───freeglut-3.0.0
│   └───glew-2.1.0
└───projects

Note that you can output cool text trees like this using the tree command in a terminal window on both Windows and Unix systems.

4. Create C++ Project

In this tutorial, we're going to create a simple OpenGL project to demonstrate some of its 3D capabilities.  This demo will just be a cube that bounces around inside of a 3D box, utilizing some simple math of calculating angles of the bounce off each wall, as well as the simple physics of constant animating velocity and rotation.

Fire up Visual Studio (in this guide, version 2015 is used) and create a new project through the dialog in File > New > Project.  In here, search for Visual C++ in the templates section on the left, and then select Empty Project from the middle.  Give the project a name, like DemoOpenGL, and also click on Browse to place the project in your projects directory from the previous step.

Now your file structure should appear similar to this:

E:\OPENGL
├───lib
│   ├───freeglut-3.0.0
│   └───glew-2.1.0
└───projects
    └───DemoOpenGL

4.1. Add First Source

First, we'll need to add at least one source file.  Right click on the DemoOpenGL project node in the Solution Explorer and navigate to Add > New Item.  Find Visual++ from the left, and select C++ file in the middle.  Name the file main.cpp and click the Add button.

Doing this enables some project settings for the next step so that we can configure properly.


5. Configuring your project

This step is an important one for anyone developing in OpenGL.  It is probably the most complicated one, and so its important to understand what's going on and become comfortable creating new projects with ease.

There's three things we basically need:

1. To see the OpenGL "include" header files.
2. To resolve external references via linked libraries
3. Runtime dlls

While you're doing this, be aware that there are two configurations: 32-bit and a 64-bit.  While headers are platform-independent, the dll's and libs are not, and hence, we will provide forked instructions for both platforms.

To make sure your build configurations have both a 32-bit and 64-bit setting, check out the box just to the right of where it says "Debug" on Visual Studio (up at the top, left of the middle).  Currently, it should by default say x86.  If you already have a setting in there for x64, then you should be fine.  The x86 is a 32-bit version while the x64 is the 64-bit version.  If not, then you can go inside to create a new build configuration (this should be simple).

5.1. Including headers

Inside Visual Studio, get to the project properties.  To do this, right click on your DemoOpenGL project node in the Solution Explorer and navigate to Properties.  Make sure the Configuration drop-down at the top-left is selected to "All Configurations".

Headers are platform independent, so be sure to set these settings under "All Platforms" in the drop down at the top-middle.

Navigate to C/C++ > General.  On the right, look for Additional Include Directories.  Click in the box beside it, and there should be a little down-arrow at the far right.  Click on that and then click on <Edit...>.  This brings up a dialog window where you can specify the location of directories to "include" so that C++ knows where to look in its attempts to resolve external references.

In that new little window that popped up, click on the New Line icon (looks like a folder).  This inserts a new line in the text area and you can click on the "..." button to browse to a location to include.  We want to do this twice and point to the include folders of each of our libraries:

 - e:\opengl\lib\freeglut-3.0.0\include
 - e:\opengl\lib\glew-2.1.0\include

Make sure you did this for "Additional Include Directories" and not "Additional #using Directories".

Click OK.


5.2. Including libraries

Next, we'll be doing something similar around libs instead of headers.  Inside the project properties dialog, browse to Configuration Properties > Linker > General and look for Additional Library Directories on the right.  Before you click, check that the Platform drop-down box is selected on the correct platform (Win32 is 32-bit and x64 is 64-bit).

With the correct platform, click the text box and then on the little arrow and then on <Edit...>.  Another similar window from the last step will launch.  Click the New Line icon and on the "..." button to browse to add the following folders:

[64 bit Version]
 - e:\opengl\lib\freeglut-3.0.0\lib\x64
 - e:\opengl\lib\glew-2.1.0\lib\Release\x64

[32 bit Version]
 - e:\opengl\lib\freeglut-3.0.0\lib
 - e:\opengl\lib\glew-2.1.0\lib\Release\Win32

Click Apply.

One more thing needs to be done in this section.  The above specified where libs could be found, but now we need to specify what libs to grab.  This part is platform independent, so change the Platform selector to All Platforms.

Go to Configuration Properties > Linker > Input and click in Additional Dependencies.  Bring up the dialog box with the <Edit...> link and type into the top empty text box the following libs:

freeglut.lib  (and press enter to get a new line)
glew32.lib

Click OK, and then OK again from the Property Pages.

5.3. Copy dll files

Last but not least, lets move our dll files over into our bin output folder.  This is also platform-dependent, so be sure to copy the correct files.

[64 bit]
Copy:
 - e:\opengl\lib\freeglut-3.0.0\bin\x64\freeglut.dll
 - e:\opengl\lib\glew-2.1.0\bin\Release\x64\glew32.dll (not a typo)

Paste into:
 - e:\opengl\projects\DemoOpenGL\x64\Debug

[32 bit]
Copy:
 - e:\opengl\lib\freeglut-3.0.0\bin\freeglut.dll
 - e:\opengl\lib\glew-2.1.0\bin\Release\Win32\glew32.dll

Paste into:
 - e:\opengl\projects\DemoOpenGL\Debug

6. Write code

Go ahead and paste this code into main.cpp, which I got from https://www.badprog.com/c-opengl-hello-world.  It should display a simple white square when run in the next step.

#include <GL/glut.h>
 
void displayMe(void)
{
    glClear(GL_COLOR_BUFFER_BIT);
    glBegin(GL_POLYGON);
    glVertex3f(0.0, 0.0, 0.0);
    glVertex3f(0.5, 0.0, 0.0);
    glVertex3f(0.5, 0.5, 0.0);
    glVertex3f(0.0, 0.5, 0.0);
    glEnd();
    glFlush();
}
 
int main(int argc, char** argv)
{
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_SINGLE);
    glutInitWindowSize(300, 300);
    glutInitWindowPosition(100, 100);
    glutCreateWindow("Hello world :D");
    glutDisplayFunc(displayMe);
    glutMainLoop();
    return 0;
}


7. Compile/build/run

There shouldn't be any red-lined errors.  If there are red-underlined errors indicating that Visual Studio is unable to find a header file, then you may want to revisit step 5.1 to ensure the directories were set properly.

If there are linker errors, you may want to revisit step 5.2, which indicates that the libraries can't be found to resolve those references.

Lastly, if there is a problem running the application but not in building, you may get an error specifying that a required dll was not found.  For this, revisit step 5.3

When the above code is run, it should display a stationary white square on a black background.  Pretty simplistic "hello world" program of sorts.  In the next step, we will work around something a little less basic to give the reader a little more hands-on experience.

Road Map:

Part 1 - Introduction
Part 2 - Setting up OpenGL in Visual Studio
Part 3 - Implementing The Boundary Bouncing Cube

OpenGL Tutorial - Part 1 of 3




Overview


Welcome to my tutorial on OpenGL, a graphics package for C++ that supports rendering of views in 3D and also in 2D.  It's a nice library for developing games as well as simulations that require visual representation.  Check out its website for a lot more information: https://www.opengl.org/about/.

This tutorial focuses on getting a developer up and started with using OpenGL using a Visual Studio environment.  This writing assumes a Windows platform of either 32-bit or 64-bit, but OpenGL is supported on many others, including Linux and Mac.

In the first several steps, we'll introduce the developer to setting up OpenGL for the first time, and then we will write a short program to demonstrate some of its 3D capabilities.

I learned about OpenGL while taking a trio of graduate level computer science courses at West Virginia University, the last of which used OpenGL around the topics of medical image analysis.  Some of what I thought the coolest features were focus on surface details such as textures, reflections and shaders, in which pixels and fragments can be dynamically colored based on specific lighting and material conditions.

For this blog, this will be a three-part post.  This first post just serves as an introduction, but the second one will help a user setup a Visual Studio environment where they can begin to work with and write code in OpenGL.  In the third part, I'll introduce some code around a simple little demo so that the user can get their hands on actually changing some given code and implementing the desired behavior.

Road Map:

Part 1 - Introduction
Part 2 - Setting up OpenGL in Visual Studio
Part 3 - Implementing The Boundary Bouncing Cube