Monday, December 24, 2012

OpenGL Colors and Shapes

    OpenGL specializes in drawing primitive shapes, especially triangles.  It can draw triangles of practically any shape and color and add a texture as needed.  OpenGL uses a process called rasterization, explained on the page http://www-users.mat.uni.torun.pl/~wrona/3d_tutor/tri_fillers.html.  Since OpenGL uses features of the graphics card, it does not require us to write code which draws triangles (which may not even run as fast as OpenGL).  This post will discuss some of these features.

Getting ready to draw
    With a working setup and OpenGL functions, we can begin to draw a triangle.  So far, the example from the previous post featured a program which runs and closes after 3 seconds.  In order to have programs which wait for the user to exit, we must make use of the variables SDL_Event input and int loop again.  Here is an example which still uses the new, OpenGL functions:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h> /*new header file*/
#include <stdio.h>

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

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 green*/
    glClearColor(0.0, 0.5, 0.0, 1.0);

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

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

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

    /*print that the program is exitting*/
    printf("User has quit the program\n");

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
User has quit the program

output inside window:
A dark green background


    SDL_OPENGL is still the last argument for the SDL_SetVideoMode function to build the OpenGL window.  This example uses a dark green background by inputting a red and blue of 0.0 to glClearColor while setting green to 0.5, or half intensity.  Alpha is still set to 1.0, but I don't believe that changing its value will change the output.  Feel free to change the background color by changing the red, green, and blue arguments; my later examples will still use this dark green.

    Inside the while loop, glClear is called with the input GL_COLOR_BUFFER_BIT.  OpenGL will interpret this as coloring all visible pixels the color specified in glClearColor.  This is similar to drawing a screen-size rectangle in SDL.  The inner-while loop checks if the user clicks the exit button and will stop the outer loop if that occurs.  Finally, the buffer which was drawn onto is made visible and the other is ready to be drawn on by calling SDL_GL_SwapBuffers, and a delay is added for the processor's sake.

    After the loop, the printf function will output a message.  This may be too quick to be observed in some cases, but can be in the stdout file.  This setup will allow us to observe a program for as long as needed and exit.  Another feature which can be added is exiting the program when the escape key is pressed, but this is not required.

Shapes
    To draw a triangle, we need to provide OpenGL the coordinates of the triangle's endpoints.  However, unlike SDL, OpenGL does not have a set coordinate system: the right of the screen could be +1 while the left is -1.  This would require float values to place a point anywhere within the screen boundaries.  OpenGL does provide functions to help us define the coordinate space we want so that once we give it the endpoints of the triangle, it will know where to place them.

    The function gluOrtho2D will define the coordinate space and the function glVertex2i will place endpoints of a triangle.  The following example should draw a white triangle:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h> /*new header file*/
#include <stdio.h>

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

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 green*/
    glClearColor(0.0, 0.5, 0.0, 1.0);

    /*tell OpenGL how to interpret coordinates*/
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(0, 640, 480, 0);

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

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

       /*tell OpenGL to draw triangles and give it three points*/
       glBegin(GL_TRIANGLES);
       glVertex2i(320, 120);
       glVertex2i(480, 360);
       glVertex2i(160, 360);
       glEnd();

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

    /*print that the program is exitting*/
    printf("User has quit the program\n");

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
User has quit the program

output inside window:
A Triangle


    After setting the clear color, glMatrixMode is called and given the enumeration GL_PROJECTION as input.  OpenGL uses a set of matrices to determine where on-screen a point will be placed.  For this two-dimensional demo, setting the projection matrix should work fine.  The next command is gluOrtho2D which allows us to define what the left, right, bottom, and top of the screen are (in that order).  I chose a coordinate system similar to SDL's in this case.

    Inside the while loop are commands used to draw a triangle.  The function glBegin is given the argument GL_TRIANGLES, which means OpenGL is now ready to draw triangles.  This is followed by glVertex2i which allows us to give OpenGL a point of a triangle.  The 2i suffix means two integers will define this point.  The final command, glEnd, complements the glBegin call earlier and OpenGL stops accepting points to draw triangles.

    The triangle is drawn every frame after the screen is cleared and before the buffers are swapped.  The top vertex is drawn first, followed by the lower right, and finally the lower left.  Only three points are specified for a single triangle, but we may later need to draw several triangles and other shapes.  Here is a demonstration with more shapes:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h> /*new header file*/
#include <stdio.h>

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

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 green*/
    glClearColor(0.0, 0.5, 0.0, 1.0);

    /*use a -1 to +1 coordinate system (lower-left origin)*/
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(-1, 1, -1, 1);

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

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

       /*draw triangles*/
       glBegin(GL_TRIANGLES);

       /*upper-left triangle*/
       glVertex2f(-0.5, 0.75);
       glVertex2f(-0.25, 0.25);
       glVertex2f(-0.75, 0.25);

       /*upper-right triangle*/
       glVertex2f(0.25, 0.75);
       glVertex2f(0.75, 0.5);
       glVertex2f(0.25, 0.25);
       glEnd();

       /*draw quadrilaterals*/
       glBegin(GL_QUADS);

       /*lower-left rectangle*/
       glVertex2f(-0.75, -0.25);
       glVertex2f(-0.75, -0.75);
       glVertex2f(-0.25, -0.75);
       glVertex2f(-0.25, -0.25);
       glEnd();

       /*draw a single polygon*/
       glBegin(GL_POLYGON);

       /*lower-right pentagon*/
       glVertex2f(0.5, -0.3);
       glVertex2f(0.3, -0.5);
       glVertex2f(0.4, -0.7);
       glVertex2f(0.6, -0.7);
       glVertex2f(0.7, -0.5);
       glEnd();

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

    /*print that the program is exitting*/
    printf("User has quit the program\n");

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
User has quit the program

output inside window:
Various Shapes


    The first change made was the coordinate system: now left is -1 and right is +1.  Bottom is -1 and top is +1 also, so compared to SDL's coordinate system, this is flipped vertically.  In order to place points within these boundaries, glVertex2f is used since the 2f suffix allows two float values as input.

    To draw two triangles, we can simply provide six points between glBegin and glEnd.  We could also have two glBegin/glEnd blocks with three points each, but I believe this would be slower.  Aside from triangles, a "quad" or quadrilateral can be drawn by calling glBegin with GL_QUADS as input and providing four points.  Like the triangles, another could be drawn by providing four more points.

    However, glBegin with input GL_POLYGON does not work this way: only one polygon will be drawn since it can have an arbitrary number of points (hexagon, octagon, etc.)  Also, notice that the points must trace around the perimeter of the shape clockwise or counter-clockwise.  Even the rectangle required this, so make sure to give OpenGL points which connect to the next or previous point by edge.

Drawing with Color
    So far, the output has been white-colored shapes (although they may appear another color since OpenGL's default color varies).  To specify which color we want a shape, we must change OpenGL's current color before drawing the shape.  OpenGL saves its current working color and applies it to any shape drawn, but this color can be changed with either the function glColor3f or glColor3ub.

    Note from the previous example that the shapes are stretched horizontally: the triangles should be of equal dimensions and the rectangle is actually a square.  However, the coordinate system I use assigns equal distances for the horizontal and vertical axes despite the pixel width and height not being equal.

    SDL does not encounter this problem since the coordinate system uses the pixel dimensions.  When using OpenGL, we must ensure that our coordinate system has the same width to height ratio as the pixel dimensions of the window.  This is called the aspect ratio and for a 640 by 480 window, the aspect ratio is 1.33333 or 4:3.

    The following example will cover how to calculate the aspect ratio and color shapes:
/*thanks to tohtml.com for syntax highlighting*/

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h> /*new header file*/
#include <stdio.h>

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

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

    /*calculate and print aspect ratio of window*/
    aspect = (double)screen->w / (double)screen->h;
    printf("aspect ratio = %lg\n", aspect);

    /*set clear color to green*/
    glClearColor(0.0, 0.5, 0.0, 1.0);

    /*set origin to lower left, use 4:3 ratio*/
    glMatrixMode(GL_PROJECTION);
    gluOrtho2D(0, 4, 0, 3);

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

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

       /*draw quadrilaterals*/
       glBegin(GL_QUADS);

       /*set color to blue*/
       glColor3ub(0, 0, 255);

       /*blue square on left*/
       glVertex2f(1.5, 2);
       glVertex2f(1.5, 1);
       glVertex2f(0.5, 1);
       glVertex2f(0.5, 2);

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

       /*trapezoid on right*/
       glVertex2f(2, 2.5);
       glVertex2f(3, 2.5);

       glColor3f(1, 0, 0); /*set color to red*/
       glVertex2f(4, 0.5);

       glColor3f(0, 1, 0); /*set color to green*/
       glVertex2f(1, 0.5);

       glEnd();

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

    /*print that the program is exitting*/
    printf("User has quit the program\n");

    /*perform final commands then exit*/
    SDL_Quit(); /*close SDL*/
    fflush(stdout); /*update stdout*/
    return 0;
}
prompt/stdout:
aspect ratio = 1.33333
User has quit the program

output inside window:
Colored Quadrilaterals


    To calculate the aspect ratio, simply divide the width by the height.  However, make sure that two doubles or floats are divided since an integer division would result in 1 due to rounding.  The aspect ratio is preserved in the coordinate system by setting the right to 4 and the top to 3 (for a 4:3 ratio).  Now the square appears as a square rather than stretched to a rectangle.

    To set the color to blue, glColor3ub requires three unsigned bytes (or unsigned char values).  This works similar to SDL, where 0 is the lowest and 255 is the highest.  Blue would be 0 red, 0 green, and 255 blue as shown above.  Next, all points of the square are given and a blue square is drawn.

    Color can be changed before or while the shape is drawn.  The trapezoid has two yellow vertices, one red vertex, and a green vertex.  To have points of different colors, use glColor3f (or glColor3ub) to set the color before individual points are given.  Finally, notice that like SDL, when a shape overlaps another the last one drawn is the most visible.  In this case, the trapezoid is drawn over some pixels of the square.

    For more information on OpenGL shapes and colors, see the pages http://www.swiftless.com/tutorials/opengl/square.html and http://www.swiftless.com/tutorials/opengl/color.html.  Also check out http://www.videotutorialsrock.com/opengl_tutorial/color/text.php.  For documentation of glBegin and gluOrtho2D, see http://www.opengl.org/sdk/docs/man2/xhtml/glBegin.xml and http://www.opengl.org/sdk/docs/man2/xhtml/gluOrtho2D.xml.

    Thank you for reading and happy holidays.  I hope to have a post about OpenGL textures up before New Years.

No comments:

Post a Comment