Saturday, March 30, 2013

Table of Contents

For those new to the site, here is an introduction.

This post is a table of all posts created so far and will be updated and reposted with every new post.

I also had some realizations about my past examples, which are covered in this note.

Here is the table:
Programming with C, 1989 Standard
    Preparation
    Basics
    Variables
    Operators
    Functions
    Logic and Conditions
    Loops
    Structures
    Pointers
    Strings
    Enumeration and Flags
Basics of SDL
    Setup
    Drawing
    Keyboard
    Mouse
    BMP Images
    Other Images
Using OpenGL 2.1
    Setup
    Drawing
    Textures 
    More Textures
    Transformations
    3D Transformations
    Depth Testing
    Lighting (in progress)

Also, here are the concepts still planned to be covered:
Adding GLSL to OpenGL with GLEW
Using OpenAL 1.1
Multimedia file formats

OpenGL Depth Testing

    Like SDL, OpenGL will draw shapes in the order you specify them.  As seen in the post http://multimediaprogram.blogspot.com/2012/08/drawing-in-sdl.html, shapes drawn later overwrite previously drawn shapes.  However, when dealing with 3D scenes, this can lead to weird visual effects.  Luckily, OpenGL provides an easy solution called depth testing.

Draw Order
    As said before, OpenGL will by default overwrite previously drawn shapes any time a new shape is drawn.  In 3D, we want shapes close to the camera to be the most visible, overwriting any shapes behind them.  As a demonstration, here is what happens if a distant shape is drawn last:
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*z positions*/
float tealZ = -3;
float colorZ = -5;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print depths of drawn shapes*/
    printf("depth of teal square = %g\n", tealZ);
    printf("depth of multi-colored square = %g\n", colorZ);

    /*set clear color to golden yellow*/
    glClearColor(0.8, 0.8, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    /*camera set at origin*/
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) { /*check exit button*/
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*draw quadrilaterals*/
       glBegin(GL_QUADS);

       /*light teal square*/
       glColor3ub(0, 255, 255);
       glVertex3f(-2, 1, tealZ);
       glVertex3f(-2, -1, tealZ);
       glVertex3f(0, -1, tealZ);
       glVertex3f(0, 1, tealZ);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 1, colorZ);
       glColor3f(1, 0, 0); glVertex3f(-1, -1, colorZ);
       glColor3f(0, 1, 0); glVertex3f(1, -1, colorZ);
       glColor3f(0, 0, 1); glVertex3f(1, 1, colorZ);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}

prompt/stdout:
depth of teal square = -3
depth of multi-colored square = -5

output inside window:
Distant Square Drawn Last


    Even though the multi-colored square is farther from the camera, it appears to be in front of the left square.  One solution would be to draw the light teal rectangle last so that it overwrites the multi-colored square.  However, if the camera were to move beyond the multi-colored square and look toward the teal square, we would have a similar problem.

    We could therefore switch the order shapes are drawn based on distance from the camera.  In fact, switching the order of shapes drawn used to be the best solution to 3D visibility.  This is known as the Painter's algorithm and can be read about at http://en.wikipedia.org/wiki/Painter%27s_algorithm.

Depth Testing (and Buffering)
    A more general solution to 3D visibility is Z-buffering, which can be read about at http://en.wikipedia.org/wiki/Z-buffering.  Basically, OpenGL can track the depth of each drawn pixel, save it to a buffer, and test against it when drawing new shapes.  Also, OpenGL makes it incredibly easy to use this feature:
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*z positions*/
float tealZ = -3;
float colorZ = -5;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print depths of drawn shapes*/
    printf("depth of teal square = %g\n", tealZ);
    printf("depth of multi-colored square = %g\n", colorZ);

    /*set clear color to golden yellow*/
    glClearColor(0.8, 0.8, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*setup depth buffering*/
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS); /*lesser depth*/
    glClearDepth(1); /*clear to maximum depth*/

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    /*camera set at origin*/
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    while (loop) {
       /*clear the color and depth of each pixel*/
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

       while (SDL_PollEvent(&input)) { /*check exit button*/
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*draw quadrilaterals*/
       glBegin(GL_QUADS);

       /*light teal square*/
       glColor3ub(0, 255, 255);
       glVertex3f(-2, 1, tealZ);
       glVertex3f(-2, -1, tealZ);
       glVertex3f(0, -1, tealZ);
       glVertex3f(0, 1, tealZ);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 1, colorZ);
       glColor3f(1, 0, 0); glVertex3f(-1, -1, colorZ);
       glColor3f(0, 1, 0); glVertex3f(1, -1, colorZ);
       glColor3f(0, 0, 1); glVertex3f(1, 1, colorZ);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}

prompt/stdout:
depth of teal square = -3
depth of multi-colored square = -5

output inside window:
Squares with Depth Buffering


    The first necessary command is "glEnable(GL_DEPTH_TEST);".  Like textures, the depth testing must first be enabled to be used.  The following 2 commands could be called in any order, but glClearDepth works similar to glClearColor.  For glClearDepth, the default depth is set for each pixel when glClear is called.  1 refers to the maximum depth, so 0 would refer to the closest distance.

    The next command, glDepthFunc, will setup how depth testing is performed.  In this case, GL_LESS means pixels with a lesser depth are drawn.  For this example, the left square is drawn first and each of its pixels are set to a small depth value.  When the multi-colored square is drawn, half of the pixels do not have a lesser depth value than what has been recorded, so the left half is not drawn.

    In order to account for a moving camera, it is best to clear the depth buffer like we have done with the color buffer so far.  That is why the command "glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);" is used in this example.  Note that each pixel's color becomes the most recent input to glClearColor while each pixel's depth becomes the most recent input to glClearDepth.

Depth Function
    Enabling depth testing simply means the depth of individual pixels will be saved in a depth buffer.  Setting the depth function will allow us to specify which pixels are drawn.  For 3D scenes, GL_LESS or GL_LEQUAL (less than or equal) will ensure the closest shape is drawn.  However, there are other inputs to glDepthFunc which perform different tests.

    The documentation for glDepthFunc, http://www.opengl.org/sdk/docs/man/xhtml/glDepthFunc.xml, shows that other tests include GL_GREATER, GL_GEQUAL (greater/equal), GL_NOTEQUAL, and GL_ALWAYS or GL_NEVER.  For GL_GREATER, this means farther pixels will pass the depth test and get drawn.  GL_ALWAYS means everything passes the test and is similar to the regular draw order of OpenGL, except that pixel depths are recorded.  Here is an example of the GL_GREATER test in action:
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*z positions*/
float redZ = -3;
float greenZ = -5;
float blueZ = -4;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print depths of drawn shapes*/
    printf("depth of red square = %g\n", redZ);
    printf("depth of green square = %g\n", greenZ);
    printf("depth of blue square = %g\n", blueZ);

    /*set clear color to golden yellow*/
    glClearColor(0.8, 0.8, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*setup depth buffering*/
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_GREATER); /*greater depth*/
    glClearDepth(0); /*clear to minimum depth*/

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    /*camera set at origin*/
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    while (loop) {
       /*clear the color and depth of each pixel*/
       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

       while (SDL_PollEvent(&input)) { /*check exit button*/
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*draw quadrilaterals*/
       glBegin(GL_QUADS);

       /*red square*/
       glColor3ub(255, 0, 0);
       glVertex3f(-2, 1, redZ);
       glVertex3f(-2, -1, redZ);
       glVertex3f(0, -1, redZ);
       glVertex3f(0, 1, redZ);

       /*green square*/
       glColor3ub(0, 255, 0);
       glVertex3f(-1, 1, greenZ);
       glVertex3f(-1, -1, greenZ);
       glVertex3f(1, -1, greenZ);
       glVertex3f(1, 1, greenZ);

       /*blue square*/
       glColor3ub(0, 0, 255);
       glVertex3f(-1, 0, blueZ);
       glVertex3f(-1, -2, blueZ);
       glVertex3f(1, -2, blueZ);
       glVertex3f(1, 0, blueZ);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}

prompt/stdout:
depth of red square = -3
depth of green square = -5
depth of blue square = -4

output inside window:
Same-Sized Squares with GL_GREATER Depth Test


    Here, three squares are drawn with the GL_GREATER depth test.  The red square is drawn first, followed by the green, and finally the blue.  Since the green square has the greatest depth, all of its pixels are drawn.  Even though the blue is drawn last, some of its pixels fail the depth test and are not drawn.

    The clear depth (glClearDepth) was set to 0 for this example.  Had it remained 1, nothing would pass the depth test since 1 is the maximum pixel depth OpenGL will record.  Even though the depth of these squares is -3, -4, and -5, the depth will be saved in the depth buffer as a number between 0 and 1.  The way it is translated to this fraction is determined by the near and far values used in gluPerspective.

    That is it for this post.  The next post should discuss OpenGL lighting.  Thank you for reading!

P.S.  tohtml.com is currently fixing bugs so the code will not be highlighted as a result.  This should be remedied soon though.

Thursday, February 28, 2013

3D transformations in OpenGL

    Let's take a look at 3D!  A big selling point of OpenGL (in my opinion) is its ability to draw 3D graphics with just a few commands.  Adding a dimension has some interesting side effects: translating and scaling can now be done on an additional axis, and rotation can now use any 3D axis rather than just the z-axis.  This post should cover how to setup a 3D scene and use some transformations.

Perspective
    For 2D scenes, the function gluOrtho2D helped setup the coordinate system.  Now, we need a function which can setup a 3D coordinate system; we need the function gluPerspective.  Documentation for it can be found at http://www.opengl.org/sdk/docs/man2/xhtml/gluPerspective.xml, but basically it takes 4 arguments: field of view, aspect ratio, near distance, and far distance.

    The field of view is the amount of the world that the camera can see at any moment.  It is an angle measured in degrees.  The aspect ratio is the window width divided by its height.  This is also accounted for in previous 2D examples, but here we only need to provide one value.  The near distance is the closest distance at which shapes are drawn, and the far distance is the maximum distance for drawing shapes.  These are both adjustable, but its best to keep them above zero and not ridiculously far apart.

    This function will create a 3D camera at the position (0, 0, 0), so in order to see shapes drawn, we must move them in front of the camera.  When using gluPerspective, negative values of Z are in front of the camera (into the monitor) while positive Z is behind the camera (out of the monitor).  The following example will draw a plane and place it in front of the camera:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*translation amounts (negative Z in front of camera)*/
float shiftX = 0, shiftY = -1, shiftZ = -3;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*set clear color to dark teal*/
    glClearColor(0, 0.5, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    /*print proposed movement of shape*/
    printf("will move shape to new position");
    printf("(%g, %g, %g)\n", shiftX, shiftY, shiftZ);

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*move shape in front of (and slightly below) camera*/
       glLoadIdentity();
       glTranslatef(shiftX, shiftY, shiftZ);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 0, -1);
       glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
       glColor3f(0, 1, 0); glVertex3f(1, 0, 1);
       glColor3f(0, 0, 1); glVertex3f(1, 0, -1);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
will move shape to new position(0, -1, -3)

output inside window:
A Quadrilateral Drawn in 3D


    First, three float variables are declared which are used later for glTranslatef.  The shiftZ is negative in order to move the shape in front of the camera.  These variables will form the position that the shape is drawn around, so the point (0, -1, -3) is at the center of this square.  Also, the point (-1, -1, -4) is at the far, left corner since the square's point (-1, 0, -1) will be shifted by -1 in the Y direction and -3 in the Z.

    The function gluPerspective is given the field of view 70.  This is a personal preference, so feel free to change this value and observe the result.  The given aspect ratio (1.333) is accurate for a 640 by 480 window, so if you use different window dimensions, just divide the width by the height.

    The near and far values are also personal preferences, but if the near value is changed to 3, then the closer half of the plane is no longer visible.  This is because nothing closer than the near distance is drawn, and this is called clipping.  Alternatively, if the far value is set to 3, then the farther half of the plane is removed.  This is because it is farther than the given far distance.

Rotations
    Honestly, the previous example is just a roundabout way of drawing a trapezoid.  So, one way to showcase the 3D aspect of it would be to include some interactive rotation.  The following example will rotate the plane based on SDL mouse position:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*SDL mouse coordinates*/
int mouseX, mouseY;

/*rotation angles*/
float angleX = 0.0, angleY = 0.0;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*set clear color to dark teal*/
    glClearColor(0, 0.5, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       /*glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
          if (input.type == SDL_KEYDOWN) { /*exit with escape key*/
              if (input.key.keysym.sym == SDLK_ESCAPE) loop = 0;
          }
       }

       SDL_GetMouseState(&mouseX, &mouseY);

       /*move shape directly in front of camera*/
       glLoadIdentity();
       glTranslatef(0, 0, -3);

       /*rotate about Y axis based on mouseX*/
       angleY = 360.0 * (float)mouseX / 640.0;
       glRotatef(angleY, 0, 1, 0);

       /*rotate about X axis based on mouseY*/
       angleX = 180.0 * (float)mouseY / 480.0;
       glRotatef(angleX, 1, 0, 0);

       /*printf rotations*/
       printf("X-axis: %g degrees, Y-axis: %g degrees\n", angleX, angleY);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 0, -1);
       glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
       glColor3f(0, 1, 0); glVertex3f(1, 0, 1);
       glColor3f(0, 0, 1); glVertex3f(1, 0, -1);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
X-axis: 16.125 degrees, Y-axis: 73.6875 degrees
X-axis: 25.5 degrees, Y-axis: 102.375 degrees
X-axis: 27 degrees, Y-axis: 106.875 degrees
X-axis: 28.125 degrees, Y-axis: 110.813 degrees
X-axis: 28.875 degrees, Y-axis: 113.625 degrees
X-axis: 30 degrees, Y-axis: 117 degrees
X-axis: 30.75 degrees, Y-axis: 120.375 degrees
X-axis: 31.5 degrees, Y-axis: 122.625 degrees
X-axis: 32.625 degrees, Y-axis: 126.563 degrees
X-axis: 33 degrees, Y-axis: 128.25 degrees
X-axis: 34.125 degrees, Y-axis: 131.625 degrees
X-axis: 34.875 degrees, Y-axis: 135 degrees
X-axis: 36.375 degrees, Y-axis: 139.5 degrees
X-axis: 37.875 degrees, Y-axis: 144 degrees
X-axis: 38.625 degrees, Y-axis: 146.813 degrees
X-axis: 40.125 degrees, Y-axis: 150.75 degrees
X-axis: 40.875 degrees, Y-axis: 152.438 degrees
X-axis: 42.375 degrees, Y-axis: 156.938 degrees
X-axis: 43.5 degrees, Y-axis: 160.875 degrees
X-axis: 45 degrees, Y-axis: 165.375 degrees
X-axis: 45.375 degrees, Y-axis: 167.625 degrees
X-axis: 46.5 degrees, Y-axis: 171 degrees
X-axis: 46.875 degrees, Y-axis: 173.813 degrees
X-axis: 47.625 degrees, Y-axis: 177.188 degrees
X-axis: 48 degrees, Y-axis: 180 degrees
X-axis: 48.75 degrees, Y-axis: 184.5 degrees
X-axis: 49.125 degrees, Y-axis: 188.438 degrees
X-axis: 49.125 degrees, Y-axis: 190.125 degrees
X-axis: 51 degrees, Y-axis: 197.438 degrees
X-axis: 51.375 degrees, Y-axis: 201.375 degrees
X-axis: 52.125 degrees, Y-axis: 203.063 degrees
X-axis: 52.5 degrees, Y-axis: 208.688 degrees
X-axis: 52.875 degrees, Y-axis: 212.063 degrees
X-axis: 53.25 degrees, Y-axis: 215.438 degrees
X-axis: 53.625 degrees, Y-axis: 216.563 degrees
X-axis: 53.625 degrees, Y-axis: 218.25 degrees
X-axis: 54 degrees, Y-axis: 219.375 degrees
X-axis: 54 degrees, Y-axis: 222.188 degrees
X-axis: 54.375 degrees, Y-axis: 224.438 degrees
X-axis: 54.75 degrees, Y-axis: 226.688 degrees
X-axis: 54.75 degrees, Y-axis: 226.688 degrees
X-axis: 54.75 degrees, Y-axis: 227.25 degrees
X-axis: 54.75 degrees, Y-axis: 228.938 degrees
X-axis: 54.75 degrees, Y-axis: 230.625 degrees
X-axis: 54.75 degrees, Y-axis: 231.75 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.313 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 232.875 degrees
X-axis: 54.75 degrees, Y-axis: 233.438 degrees
X-axis: 54.75 degrees, Y-axis: 233.438 degrees
X-axis: 54.75 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.125 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 55.875 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees
X-axis: 56.25 degrees, Y-axis: 233.438 degrees

output inside window:
Rotated Quadrilateral


    Here glClear has been turned off again, so if you don't want the after images seen in the picture above, be sure to uncomment the glClear line.  The picture above is based on me moving the mouse toward the yellow corner until they overlap.  Notice that the example exits if the escape key is pressed; this was to stop the printf statements without having to click the upper-right close button (which still works).

    SDL_GetMouseState is used to get the mouse coordinates and save them to the mouseX and mouseY variables.  For every frame, these coordinates are used to calculate the angleX and angleY values.  Notice that mouseX and mouseY are divided by the window width and height respectively.  These will give us values between 0 and 1 which will give us angles between 0 and 360 degrees or 0 and 180 degrees when multiplied.

    For this example, the shape is first rotated around the X axis, then the Y axis, and finally translated into position.  Remember that OpenGL performs transformations opposite the order we write them.  If you were to switch the order of the X and Y rotations, the result would be changed.  It is hard to explain, but the "feel" of the rotation is different, so I highly recommend running this example and swapping the order of rotations to try it out.

    When working with rotations, be cautious of gimbal lock.  This example rotates about the X and Y axes, and the order of the rotations affects the result.  I believe the axis of the second rotation operation is rotated by the first, so if I rotate 90 degrees on the Y axis, any following X axis rotation would actually be a Z axis rotation.  For more information about gimbal lock, see http://en.wikipedia.org/wiki/Gimbal_lock.

Moving the Camera
    So far, shapes have been placed in front of the point (0, 0, 0) so that they can be visible.  However, we can also have a "moving" camera by applying the same set of transformations to every shape.  For a large, 3D scene, every shape must be rotated and translated in terms of the camera's position and angles.  While we could use glRotatef and glTranslatef, there are situations where the function gluLookAt can help us more.

    This function takes 9 arguments, which are grouped into sets of three.  The first three are the x, y, and z of the camera's position.  The second three are the x, y, and z of the point which the camera is focused on.  The last three make up a three-dimensional direction for the camera, the up vector.  This is usually (0, 1, 0), but can be any other direction if you wanted to tilt the camera sideways or upside down.

    We still need a way to save and restore the current matrix.  If we setup a matrix which puts everything around the camera, we still need to be able to scale, rotate and translate individual shapes without them affecting each other.  The matrix created from gluLookAt can be saved with "glPushMatrix();" and can become the current modelview matrix again by using "glPopMatrix();".

    The following example will use gluLookAt to move the camera and draw rectangles at (0, 0, 0) and (-5, 0, 0):
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*camera position*/
float camX = 2, camY = 2, camZ = 2;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print camera position*/
    printf("camera position = (%g, %g, %g)\n", camX, camY, camZ);

    /*set clear color to dark teal*/
    glClearColor(0, 0.5, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*create 3D camera*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70, 1.333, 1, 100);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*move camera away from shape, rotate to see shape*/
       glLoadIdentity();
       gluLookAt(camX, camY, camZ,  0, 0, 0,  0, 1, 0);

       /*transformations which work in place of gluLookAt*/
       /*glRotatef(35.26, 1, 0, 0); /*x-axis rotation*/
       /*glRotatef(-45, 0, 1, 0); /*y-axis rotation*/
       /*glTranslatef(-2, -2, -2); /*move to position*/

       /*scale and translate the shape*/
       glPushMatrix(); /*save current modelview*/
       glTranslatef(-5, 0, 0);
       glScalef(2, 1, 2);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*light teal square*/
       glColor3ub(0, 255, 255);
       glVertex3f(-1, 0, -1);
       glVertex3f(-1, 0, 1);
       glVertex3f(1, 0, 1);
       glVertex3f(1, 0, -1);

       glEnd();
       glPopMatrix(); /*restore previous modelview*/

       /*scale the shape*/
       glPushMatrix(); /*save current modelview*/
       glScalef(2, 1, 1);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex3f(-1, 0, -1);
       glColor3f(1, 0, 0); glVertex3f(-1, 0, 1);
       glColor3f(0, 1, 0); glVertex3f(1, 0, 1);
       glColor3f(0, 0, 1); glVertex3f(1, 0, -1);

       glEnd();
       glPopMatrix(); /*restore previous modelview*/

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
camera position = (2, 2, 2)

output inside window:
Two Planes seen from a 3D Camera


    Every frame, the modelview matrix is set to the identity matrix and changed using the gluLookAt function.  Then, each shape is transformed using the result of gluLookAt and some unique transformations (scaling and translating in this case).  The unique transformations are applied after glPushMatrix and are removed by calling glPopMatrix.

    In this example, gluLookAt is told that the camera is at position (2, 2, 2) and looking at point (0, 0, 0).  This same transformation can be accomplished with two glRotatef calls and a glTranslatef as seen by the commented lines.  To test this, you can comment the gluLookAt line and un-comment the three OpenGL functions below it.  Note that these three lines translate then rotate (and don't scale), reversing the usual order of transformations.

    After the camera transformations are in the modelview matrix, we can still apply transformations to individual shapes.  The teal rectangle is scaled then translated in order to have a 4x4 plane centered at (-5, 0, 0).  The multi-colored rectangle is scaled to be 4x2 and remains centered at (0, 0, 0).  These individual transformations can be applied after the camera transformations.

    However, we don't want to move the multi-colored rectangle to (-5, 0, 0) or scale it beyond 4x2.  That means we need a way to remove the "glTranslatef(-5, 0, 0);" and "glScalef(2, 1, 2);" calls used for the teal rectangle.  We could apply "glScalef(0.5, 1, 0.5);" and glTranslatef(+5, 0, 0);", the opposite transformations in the opposite order.

    Since OpenGL provides the functions glPushMatrix and glPopMatrix, we can avoid calculating the reverse transformation needed to restore the result of gluLookAt.  glPushMatrix can store the current modelview matrix and glPopMatrix will remove the current modelview matrix and replace it with what was saved earlier.  Without the first call to glPopMatrix, the multi-colored rectangle would be transformed using gluLookAt, glTranslatef, and every call to glScalef.

    To wrap up this post, here are better explanations of the way matrices help transform shapes: http://robertokoci.com/world-view-projection-matrix-unveiled/ and http://3dgep.com/?p=1700.   For OpenGL, there are separate projection and modelview matrices, but the modelview can also be split into a world matrix (individual transformations) and a view matrix (camera transformations).  Also, here is the documentation for gluLookAt: http://www.opengl.org/sdk/docs/man2/xhtml/gluLookAt.xml.  Finally, another tutorial page can be found at http://www.swiftless.com/tutorials/opengl/rotation.html, and it is written in C++ using GLUT rather than C using SDL.

    Thank you for reading, and future posts should discuss lighting and depth testing.

Monday, February 18, 2013

OpenGL Transformations

    Another feature of OpenGL is that it can make changes to shapes without us adjusting any vertex positions.  The three basic changes that can be applied are scaling, rotating, and translating.  OpenGL uses a 4x4 matrix on shapes to modify each of its vertices, or points.  This post should cover each basic transformation in two dimensions.

Scaling
    If we wanted to re-size a shape, we could change the vertex positions to make a bigger shape.  However, OpenGL offers the function glScalef which will use a matrix to re-size input shapes along the x, y, and z axes.  The following example will only use the x and y axes for a two-dimensional rectangle:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*scale factors*/
float scaleX = 3;
float scaleY = 2;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print the scale factors*/
    printf("will scale shape %g times on the x-axis\n", scaleX);
    printf("and %g times on the y-axis\n", scaleY);

    /*set clear color to dark red*/
    glClearColor(0.5, 0, 0, 1.0);

    /*set origin to center, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); /*start with identity matrix*/
    gluOrtho2D(-4, 4, -3, 3);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*setup modelview matrix to scale shape*/
       glLoadIdentity();
       glScalef(scaleX, scaleY, 1);

       /*set color to yellow*/
       glColor3f(1, 1, 0);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*square*/
       glVertex2f(-1, -1);
       glVertex2f(-1, 1);
       glVertex2f(1, 1);
       glVertex2f(1, -1);

       glEnd();

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
will scale shape 3 times on the x-axis
and 2 times on the y-axis

output inside window:
A Square scaled to a Rectangle


    The function glMatrixMode will accept the enumerations GL_PROJECTION, GL_MODELVIEW, and two others.  OpenGL uses two matrices to modify shapes: a ModelView matrix and a Projection matrix.  The projection matrix is mostly used to help display the shape, especially three dimensional shapes.  For this example, it maps the shape's points to their pixel positions (similar to the coordinates used in SDL).

    The function glLoadIdentity requires no arguments, but will set the specified matrix (modelview or projection) to the identity matrix.  Any additional functions like gluOrtho2D or glScalef will multiply this identity matrix by an additional matrix.  The result will be the new matrix since any matrix multiplied by the identity matrix is the same matrix (hence the name identity matrix).

    For this example, the screen is mapped from -4 on the left to 4 on the right as well as -3 on the bottom to 3 on the top.  The specified shape is a 2x2 square in the center, yet the final picture shows a 6x4 rectangle.  Before the projection matrix maps the shape points to their correct pixel positions, the modelview matrix is applied, which in this case scales horizontally by 3 and by 2 along the y-axis.

    As a demonstration of the math, here is a diagram which multiples a shape point by the modelview matrix:


    This modelview matrix would be the result of the glScalef operation.  Note that the example above does scale along the z-axis by 1.  If the modelview matrix were not "scaled", the 3 and 2 would instead be 1's and the modelview matrix would be the identity matrix.  Also, notice that the point consists of an x and y (which are given by glVertex2f), a 0 for z (since its two dimensional), and an additional 1 at the end.  This extra 1 will prove useful later.

    Previous examples used the identity matrix as the modelview matrix, which meant the input points were mapped according to the projection matrix.  This example will scale each point to produce a different shape, then apply the project matrix to the new shape.  This intermediate step allows us to make easy changes to a shape with any number of points since OpenGL will automatically apply the transformation to every vertex.

Rotating
    OpenGL also allows us to rotate shapes before they are displayed on screen.  This transformation is the most complex mathematically, especially in three dimensions.  It requires that we specify an angle (in degrees) and an axis to rotate around.  For two-dimensional rotation, we can use the z-axis.

    The z-axis in OpenGL is perpendicular to the screen.  If you imagine a pole running straight through your monitor, that would represent the z-axis.  you could turn the monitor clockwise or counter-clockwise around this pole by a certain number of degrees.  For this example, the drawn square has a similar pole, which is specified by the z-axis (or vector <0,0,1>):
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*degrees of rotation*/
float angle = 0;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*set clear color to dark red*/
    glClearColor(0.5, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*set origin to center, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); /*start with identity matrix*/
    gluOrtho2D(-4, 4, -3, 3);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       /*glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*setup modelview matrix to rotate shape*/
       glLoadIdentity();
       glRotatef(angle, 0, 0, 1); /*along z-axis*/

       /*print rotation for current frame*/
       printf("drawing shape at %g degrees\n", angle);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex2f(-1, -1);
       glColor3f(1, 0, 0); glVertex2f(-1, 1);
       glColor3f(0, 1, 0); glVertex2f(1, 1);
       glColor3f(0, 0, 1); glVertex2f(1, -1);

       glEnd();
       angle += 1; /*adjust angle for next frame*/

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
drawing shape at 0 degrees
drawing shape at 1 degrees
drawing shape at 2 degrees
drawing shape at 3 degrees
drawing shape at 4 degrees
drawing shape at 5 degrees
drawing shape at 6 degrees
drawing shape at 7 degrees
drawing shape at 8 degrees
drawing shape at 9 degrees
drawing shape at 10 degrees
drawing shape at 11 degrees
drawing shape at 12 degrees
drawing shape at 13 degrees
drawing shape at 14 degrees
drawing shape at 15 degrees
drawing shape at 16 degrees
drawing shape at 17 degrees
drawing shape at 18 degrees
drawing shape at 19 degrees
drawing shape at 20 degrees
drawing shape at 21 degrees
drawing shape at 22 degrees
drawing shape at 23 degrees
drawing shape at 24 degrees
drawing shape at 25 degrees

output inside window:
A Square Rotated over Time


    The function glRotatef uses the inputs angle, x, y, and z (in that order).  The angle is in degrees and can be negative to rotate the opposite way.  The x, y, and z make up the axis or vector we wish to rotate about.  The length of the input vector does not affect the rotation, so we could use <0,0,5>.  However, if we change the vector to <0,0,-1>, we have also changed the direction, which would reverse the way the square is rotated.

    Notice that the glClear call is commented out.  This is to show the rotation of the square over time.  Feel free to un-comment this line when running the example.  For this example, the square starts out aligned to the x and y axes, then rotates counter-clockwise until the program is ended.  The picture above should show a rotation from 0 to 25 degrees, with each new square drawn on top of the previous.

    While OpenGL does allow is to rotate around any axis we input, the following diagram only shows rotations around the z-axis (or two-dimensional rotations):

    The alpha symbol represents the angle we wish to rotate by.  If we wanted to rotate 45 degrees, to make the square look like a diamond, the new point would have an x of 0 and a y of root 2.  Basically, the upper-right corner would be moved to the center and moved up slightly.  Since a similar process is done for every corner, the end result is a rotation of 45 degrees.

Translating
    The final transformation is referred to as translation, which means moving or shifting the shape.  This requires the function glTranslatef, which requires an x, y, and z input.  These float variables will specify how much to move the shape in each direction.  They can be negative to indicate a movement in the opposite direction.

    For two-dimensional shapes, we only need to use x and y inputs and pass 0 to z.  The following example will use translation with scaling and rotating:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <stdio.h>

SDL_Surface* screen;
SDL_Event input;
int loop = 1;

/*scale variables*/
float scaleX = 2, scaleY = 1.5;
/*rotation variable*/
float angle = 0;
/*translation variables*/
float shiftX = 1, shiftY = 1;

int main(int argc, char** argv) {
    SDL_Init(SDL_INIT_EVERYTHING); /*initialize SDL*/
    screen = SDL_SetVideoMode(640, 480, 32, SDL_OPENGL);

    /*print scaling variables*/
    printf("will scale shape %g times on the x-axis\n", scaleX);
    printf("and %g times on the y-axis\n", scaleY);

    /*print translating variables*/
    printf("will move shape to (%g,%g)\n", shiftX, shiftY);

    /*set clear color to dark red*/
    glClearColor(0.5, 0, 0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);

    /*set origin to center, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); /*start with identity matrix*/
    gluOrtho2D(-4, 4, -3, 3);

    glMatrixMode(GL_MODELVIEW); /*for transforming shapes*/

    while (loop) {
       /*glClear(GL_COLOR_BUFFER_BIT); /*clear screen*/

       while (SDL_PollEvent(&input)) {
          if (input.type == SDL_QUIT) loop = 0;
       }

       /*setup modelview matrix*/
       glLoadIdentity();
       glTranslatef(shiftX, shiftY, 0); /*move shape*/
       glRotatef(angle, 0, 0, 5); /*along z-axis*/
       glScalef(scaleX, scaleY, 1); /*re-size shape*/

       /*print rotation for current frame*/
       printf("drawing shape at %g degrees\n", angle);

       /*draw quadrilateral*/
       glBegin(GL_QUADS);

       /*multi-colored square*/
       glColor3f(1, 1, 0); glVertex2f(-1, -1);
       glColor3f(1, 0, 0); glVertex2f(-1, 1);
       glColor3f(0, 1, 0); glVertex2f(1, 1);
       glColor3f(0, 0, 1); glVertex2f(1, -1);

       glEnd();
       angle += 1; /*adjust angle for next frame*/

       SDL_GL_SwapBuffers();
       SDL_Delay(20); /*wait 20ms*/
    }

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
will scale shape 2 times on the x-axis
and 1.5 times on the y-axis
will move shape to (1,1)
drawing shape at 0 degrees
drawing shape at 1 degrees
drawing shape at 2 degrees
drawing shape at 3 degrees
drawing shape at 4 degrees
drawing shape at 5 degrees
drawing shape at 6 degrees
drawing shape at 7 degrees
drawing shape at 8 degrees
drawing shape at 9 degrees
drawing shape at 10 degrees
drawing shape at 11 degrees
drawing shape at 12 degrees
drawing shape at 13 degrees
drawing shape at 14 degrees
drawing shape at 15 degrees
drawing shape at 16 degrees
drawing shape at 17 degrees
drawing shape at 18 degrees
drawing shape at 19 degrees
drawing shape at 20 degrees
drawing shape at 21 degrees
drawing shape at 22 degrees
drawing shape at 23 degrees
drawing shape at 24 degrees

output inside window:
A Transformed Rectangle


    While we can apply all three transformations, we must be careful to do so in the right order.  The ideal order is scale, rotate, translate (as introduced in this post).  Were we to rotate after translating, the shape would rotate around the origin rather than its own origin; and if we rotate then scale in this example, the shape would be stretched horizontally at all times and appear to have a bad aspect ratio.

    Despite my opinion that scale, rotate, then translate is the ideal order, the example shows the commands in the order translate, rotate, then scale.  This is because OpenGL reverses the order of the transformations when creating matrices (except glLoadIdentity).  It can be tricky, so be careful to remember this quirk of OpenGL.

    So, onto translation.  This is where the extra 1 at the end of our points comes in handy: were it not for this 1, moving the shape with matrices would be very difficult.  Here is a diagram which translates one of the square's points:

    The column on the right of the modelview matrix consists of the x, y, and z we wish to move the shape by.  Each of these are multiplied by the 1 at the end of the point and added to the point's x, y, or z.  For this case, the 2d point (1, -1) is moved to (2, 0) since an x shift of 1 and a y shift of 1 was used.

    With that, I will wrap up this post.  For documentation on OpenGL transformations, see http://www.opengl.org/sdk/docs/man2/xhtml/glScale.xml, http://www.opengl.org/sdk/docs/man2/xhtml/glRotate.xml, and http://www.opengl.org/sdk/docs/man2/xhtml/glTranslate.xml.  For more information about rotation matrices, see the page http://en.wikipedia.org/wiki/Rotation_matrix.  For a more in-depth tutorial, check out http://www.videotutorialsrock.com/opengl_tutorial/transform/text.php.

    Thanks again for reading; the next post will discuss how to setup a 3D camera and use 3D transformations!

P.S. I had to change the matrix diagrams from transparent GIF's to PNG's with a white background for better readability.  Also, I fixed some awkward spacing .