About this Blog

This is my first blog. Ever.

It is simply going to be about my hobby; playing with computer programming. I do not know much about blogging, but I will use this one to learn a bit more about it.

Programming has always been a bit of a passion for me, as from those early days when I first tapped in a sample BASIC program on my old Sinclair Spectrum back in 1986. I have been through many platforms, languages and OS's since, but always carried the hobby with me. I am not particularly good at it; perfection requires a large time investment and continuous practice. I do not have the luxury of the amount of time required to keep the fire burning constantly, so the hobby has inevitably gone through periods of extreme withering. I have, however, finally settled for C++, as the title of this blog implies, and play around with it for some entertainment when ever I can.

This here will serve me as a written record of what I am up to, and hopefully be a reinforcement to my memory every now and then. That is all there is to it.

So, if you read this blog, please don't expect anything snazzy, but be you welcome just the same!

Wednesday 14 December 2011

OpenGL / Allegro 5.1 Animation and Back Face Culling

So, in this post I might say that I am now ready to tackle a slightly more complex integration of OpenGL into Allegro 5.1; Animation.


Much the same stuff that has been in my previous posts, up to a point; include the Allegro and GLU headers, set up a camera, draw a flat board with a texture on it, etcetera...

/* Comment --- reference library link line 
`pkg-config --libs allegro-5.1 allegro_image-5.1`

-lGL

-lGLU
*/


#include "allegro5/allegro.h"
#include "allegro5/allegro_image.h"
#include "allegro5/allegro_opengl.h"
#include "GL/glu.h"


const int SCREEN_X = 800;
const int SCREEN_Y = 600;
const float FPS = 60.0;

// Function to keep angle values between 0 and 359
void angle_max(GLfloat &_angle)
{
if(_angle >= 360.0)
_angle -= 360.0;
else if(_angle < 0.0)
_angle += 360.0;
}

// Function to set up camera
void setup_3D_camera()
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(35.0, GLdouble(SCREEN_X) / GLdouble(SCREEN_Y), 1.0, 100.0);
}
Except that now instead of drawing the camera straight into int main() function, I have the camera setting up in its own custom function. I also added a function called angle_max(), that receives a reference pointer of an angle. Here is a brief break down.

//DEMO EXAMPLE
const int SCREEN_X = 800;
const int SCREEN_Y = 600;
These are just the display size, in integers, to create an 800 x 600 pixel window. It may seem pretty obvoius, but I mention it here for one reason alone, one which I have not mentioned before. Allegro creates displays using integers values with al_create_display(). OpenGL uses the values of the display width and height to get the aspect ratio of the screen for the gluPerspective() function, but requires the aspect ratio to be in a double type. These integers need to be type cast to double in the gluPerspective() function, or you get some funny perspective effects when the program runs. It caught me out. That's all!

// DEMO EXAMPLE
const float FPS = 60.0;


60 frames per second. Allegro stuff. This will be the speed of the timer to draw my animation. It is just declared here.

// DEMO EXAMPLE
void angle_max(GLfloat &_angle)
{
if(_angle >= 360.0f)
_angle -= 360.0f;
else if(_angle < 0.0f)
_angle += 360.0f;
}
Very simple, and needed only to keep the angle inside the 0º to 360º constraints. OpenGL uses angles in degrees. This function accepts an angle and makes sure that it never exceeds 359.99º or falls below 0º. It is a generic angle "corrector", and any angle of float type in degrees can be passed to it for the check, simplifying the code.

The void setup_3D_camera() function is simply what has been seen in other posts on this blog, only here it is in a function. Big deal. In the end it only gets called once, anyway, to set up the glMatrixMode(GL_PROJECTION).

Now the function to draw the board, also in its own custom function this time around...

// Function to draw the 3D bill board
void draw_board(GLuint _tex, GLfloat _ang_y)
{
glMatrixMode(GL_MODELVIEW),
glLoadIdentity();

glTranslatef(0.0f, 0.0f, -20.0f);

glRotatef(0.0f, 1.0f, 0.0f, 0.0f);
glRotatef(_ang_y, 0.0f, 1.0f, 0.0f); // Y axis, _ang_y variable used here.
glRotatef(0.0f, 0.0f, 0.0f, 1.0f);

glEnable(GL_TEXTURE_2D);
//glEnable(GL_CULL_FACE);
glBindTexture(GL_TEXTURE_2D, _tex);

glBegin(GL_QUADS);

glTexCoord2f(0.0f, 0.0f);
glVertex3f(-2.5f, -2.5f, 0.0f); //lower lh corner
glTexCoord2f(1.0f, 0.0f);
glVertex3f(2.5f, -2.5f, 0.0f); //lower rh corner
glTexCoord2f(1.0f, 1.0f);
glVertex3f(2.5f, 2.5f, 0.0f); //upper rh corner
glTexCoord2f(0.0f, 1.0f);
glVertex3f(-2.5f, 2.5f, 0.0f); //upper lh corner

glEnd();

//glDisable(GL_CULL_FACE);
glDisable(GL_TEXTURE_2D);
}


Apart from the drawing of the bill board here being in a function, there are only two new and notable features. First, note that a variable for an angle is passed to this funtion (GLfloat _ang_y), and is used in the glRotatef() call for the Y axis. The implicatin is that every time the function is called, a new value of the Y axis angle may be passed to the function. That would give me an "animation" of a sorts, right?

The other feature of note is the inclusion of glEnable(GL_CULL_FACE), though commented out for now. There will be more on this face culling business at the end of the post. Just for now, related to this topic, note that the bill board has been drawn in a counter-clockwise sense; that is, I started at the bottom left vertex, went to the bottom right, up to the top right, then to the top left. The order in which the vertices are drawn is important, when it comes to face culling.

int main(int argc, char *argv[])
{
ALLEGRO_DISPLAY *display = NULL;
ALLEGRO_BITMAP *tex_bmp = NULL;
ALLEGRO_EVENT_QUEUE *event_queue = NULL;
ALLEGRO_TIMER *timer = NULL;

bool loop = true;
bool draw = false;

GLfloat angle_y = 0.0f;
GLfloat rate_y = 1.0f;
GLuint ogl_tex = 0;

al_init(); // Again, avoiding error trapping for clarity...
al_init_image_addon();

al_set_new_display_flags(ALLEGRO_OPENGL);
display = al_create_display(SCREEN_X, SCREEN_Y);
tex_bmp = al_load_bitmap("texture1.jpg");
ogl_tex = al_get_opengl_texture(tex_bmp);

event_queue = al_create_event_queue();
timer = al_create_timer(1.0f/FPS);

al_register_event_source(event_queue, al_get_display_event_source(display));
al_register_event_source(event_queue, al_get_timer_event_source(timer));

al_start_timer(timer);
setup_3D_camera();

//glCullFace(GL_BACK);
//glCullFace(GL_FRONT);

So, what's new as from the previous posts? For starters I have declared an ALLEGRO_EVENT_QUEUE and an ALLEGRO_TIMER. There are also two booleans, loop and draw. Finally, there are two new GLfloats (angle_y and rate_y). Taken from the top...

The ALLEGRO_EVENT_QUEUE is created with al_create_event_queue(), and it will be used to store events from different sources of input into the program (such as the display, or the timer, keyboard, mouse, and so on). However, in order for the event_queue to receive input from a ny of these sources, an "avenue" of communication must be opened from the source to the event_queue by using al_register_event_source(), or the program simply will not know that an input was intended at all. I register inputs for the event_queue from the display using al_get_display_event_source() and from the timer using al_get_timer_event_source(). It makes the program keep an "eye open" for events from these two sources.

The ALLEGRO_TIMER is created with al_create_timer(1.0f/FPS). Basically (remembering that FPS = 60.0), I am dividing 1 second into 60 parts. This will create a timer that will throw an ALLEGRO_TIMER_EVENT into the event_queue every 1/60 of a second. In the code (later) I can retrieve and use that timer event to control the flow of the "animation".

All of what is covered in the last two paragraphs is not specifically for OpenGL integration; it is plain old Allegro, and can be used in any Allegro 5 program. More on it at these two links.

Allegro 5 events tutorial

Allegro 5 timer tutorial

The GLfloat angle_y variable is the angle that will be passed to the custom draw_board() function, in order to set the angle of the board around its Y axis. The GLfloat rate_y is the amount by which the angle will change each frame loop of the animation.

I then start the timer with al_start_timer(timer). With this, the timer is now running and throwing ALLEGRO_EVENT_TIMER every 1/60 of a second. And finally, I call the setup_3D_camera() function.

I will ignore the two two commented out glCullFace() lines for now.

By the way, here is the bitmap I used for this sample program (an Anime "Oh! My Goddess" wallpaper cropped and "gimped")...


Now the loop that will detect the events and handle them...

 // Start the loop
while(loop)
{
ALLEGRO_EVENT event;
al_wait_for_event(event_queue, &event);
// Detect the events
switch(event.type)
{
case ALLEGRO_EVENT_DISPLAY_CLOSE:
loop = false;
break;

case ALLEGRO_EVENT_TIMER:
draw = true;
break;
}

// Draw the screen if draw = true
if(draw == true && al_event_queue_is_empty(event_queue))
{
angle_y += rate_y;
angle_max(angle_y);
al_clear_to_color(al_map_rgb_f(0.0f, 0.0f, 0.5f));
draw_board(ogl_tex, angle_y);
al_flip_display();
draw = false;
}
}


Yeah, while the boolean loop variable is true, the loop will keep cycling. First, I will make an empty ALLEGRO_EVENT structure variable, and then wait for an event to be dropped into the event_queue with al_wait_for_event(). When an event occurs, I can examine it in the event structure using a switch control loop.

If I get an ALLEGRO_EVENT_DISPLAY_CLOSE event (ie; I click on the window "X" icon to close it), I set the loop variable to false, and the program exits the loop on the next while() cycle.

If I get an ALLEGRO_EVENT_TIMER, and I will 1/60 of a second just so long as the timer is running, I set the draw variable to true. This will cause the drawing to take place by executing the if(draw == true...) statement.

When the loop variable is set to false, the while(loop) exits and the following block is performed. The application ends.

 // Clean up and kill the application
al_destroy_bitmap(tex_bmp);
al_stop_timer(timer);
al_destroy_timer(timer);
al_flush_event_queue(event_queue);
al_destroy_event_queue(event_queue);
al_destroy_display(display);
return 0;
}


Now, what about that back face culling business? If the blocks of code above (barring, of course, the //DEMO EXAMPLE blocks) are strung together and compiled, I get an animation of the goddesses spinning around on a bill board. Both sides of the bill board will be seen, the second one being a reverse image of the first. The face that shows the black haired goddess with the red shawl (Skuld, by name, incidentally) on the left is the front face, with the vertices arranged counter clockwise. If that board it flipped around around (as the animation does) the vertices will no longer be in a counter clockwise orientation. This is the back face, with Skuld on the right. All very well, but maybe I only want the front face to be visible. In this case, I un-comment the lines in the draw_board() function;

...
//glEnable(GL_CULL_FACE);
...
//glDisable(GL_CULL_FACE);
...


By default, OpenGL culls (or suppresses the drawing of) the backface, that one with clockwise vertices. If I run the program now, we will only see the face of the board that has Skuld on the left. However, we can manipulate OpenGL very easily so that it culls only the front face and leaves the back face alone. If I find the following line, just after al_start_timer()...

//glCullFace(GL_FRONT);

...and un-comment it, then recompile and run, I will only see the face that has Skuld on the right (ie; the back face).

Back face culling is useful to speed up a program that has many objects in it, which the back faces need not be drawn (for example, they are the inside of a space ship that the user will never see anyway. Some more neat tricks with back face culling here...

Video Tutorials Rock Back face Culling

That's all for this post.

No comments:

Post a Comment