Swiftless Game Programming Site
Home Tutorials Contact
Home
News
OpenGL
GLSL
OpenCL
Maths
Misc
Work in Progress
Resume
 
 

A Basic OpenGL Camera Creation Tutorial

If you would like to see this site updated, please help out and

In this tutorial I will be teaching you how to make a 'camera' system using simple trigonometry. In our camera creation I will be using only keyboard input with the glut keyboard controls. Now camera rotation is where maths can first start coming into play when it comes to game programming. Math unfortunately for many, is crucial to graphics, physics and even sound, so math must be understood relatively well if you want to get anywhere.

now here we are setting positions for the camera with the variables:
xpos, ypos, zpos
we are then setting the angle at which the camera is rotated with:
xrot, yrot, xrot is the rotation on the x-axis(up and down), while
yrot is the rotation on the y-axis(left and right). And yes it is a little
confusing.

now because we are rotating and moving in circular directions, we need
to use PI, you might remember this as 3.14 but here we have to be alot more
accurate, so we use 3.141592654. We don't use the symbol for PI as each time the
computer calculates it, it will round off to a different number, and also takes
longer to calculate.

here is where you also have to use trigonometry to calculate your position
we are using:
yrotrad = (yrot / 180 * 3.141592654f);
xrotrad = (xrot / 180 * 3.141592654f);
xpos -= float(sin(yrotrad));
zpos += float(cos(yrotrad)) ;
ypos += float(sin(xrotrad));

now the yrotrad is angle at which you have rotated.
to calculate your position on the x,y and z axis you have to use 2 of the trigonomic
forumulas, sin(sine), cos(cosine). If you have done these in maths, you will
understand more of what is going on. If not, I suggest you take a crash course
in trigonometry. Rather simple concept when you know what it is.

now to set the angle of which you have rotated, we use the simple:
yrot -= 1;
if (yrot < -360) yrot += 360;

this says to take 1 of the angle of rotation on the y-axis
then says that if the angle is less then -360, to set it back to 360
so we can start subtracting the angle again. We have to use 360,
because there are 360 degrees in a circle.

now because I have described these lines up here, I will not comment them below.
you should get the jist of what they do if you have read what I just said.

I hope that you understood some of that, I am not sure I do :S
If you have any queries about this feel free to contact me at swiftless@gmail.com

1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
  #include <GL/gl.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <math.h>

//angle of rotation
float xpos = 0, ypos = 0, zpos = 0, xrot = 0, yrot = 0, angle=0.0;

//positions of the cubes
float positionz[10];
float positionx[10];

void cubepositions (void) { //set the positions of the cubes

    for (int i=0;i<10;i++)
    {
    positionz[i] = rand()%5 + 5;
    positionx[i] = rand()%5 + 5;
    }
}

//draw the cube
void cube (void) {
    for (int i=0;i<10;i++)
    {
    glPushMatrix();
    glTranslated(-positionx[i + 1] * 10, 0, -positionz[i + 1] *
10); //translate the cube
    glutSolidCube(2); //draw the cube
    glPopMatrix();
    }
}

void init (void) {
cubepositions();
}

void enable (void) {
    glEnable (GL_DEPTH_TEST); //enable the depth testing
    glEnable (GL_LIGHTING); //enable the lighting
    glEnable (GL_LIGHT0); //enable LIGHT0, our Diffuse Light
    glShadeModel (GL_SMOOTH); //set the shader to smooth shader

}

void camera (void) {
    glRotatef(xrot,1.0,0.0,0.0);  //rotate our camera on teh 
x-axis (left and right)

    glRotatef(yrot,0.0,1.0,0.0);  //rotate our camera on the 
y-axis (up and down)

    glTranslated(-xpos,-ypos,-zpos); //translate the screen
 to the position of our camera

}

void display (void) {
    glClearColor (0.0,0.0,0.0,1.0); //clear the screen to 
black

    glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /
/clear the color buffer and the depth buffer

    glLoadIdentity();  
    camera();
    enable();
    cube(); //call the cube drawing function
    glutSwapBuffers(); //swap the buffers
    angle++; //increase the angle
}

void reshape (int w, int h) {
    glViewport (0, 0, (GLsizei)w, (GLsizei)h); //set the viewport
 to the current window specifications

    glMatrixMode (GL_PROJECTION); //set the matrix to projection

    glLoadIdentity ();
    gluPerspective (60, (GLfloat)w / (GLfloat)h, 1.0, 1000.0
); //set the perspective (angle of sight, width, height, , 
depth)

    glMatrixMode (GL_MODELVIEW); //set the matrix back to model

}

void keyboard (unsigned char key, int x, int y) {
    if (key=='q')
    {
    xrot += 1;
    if (xrot >360) xrot -= 360;
    }

    if (key=='z')
    {
    xrot -= 1;
    if (xrot < -360) xrot += 360;
    }

    if (key=='w')
    {
    float xrotrad, yrotrad;
    yrotrad = (yrot / 180 * 3.141592654f);
    xrotrad = (xrot / 180 * 3.141592654f);
    xpos += float(sin(yrotrad)) ;
    zpos -= float(cos(yrotrad)) ;
    ypos -= float(sin(xrotrad)) ;
    }

    if (key=='s')
    {
    float xrotrad, yrotrad;
    yrotrad = (yrot / 180 * 3.141592654f);
    xrotrad = (xrot / 180 * 3.141592654f);
    xpos -= float(sin(yrotrad));
    zpos += float(cos(yrotrad)) ;
    ypos += float(sin(xrotrad));
    }

    if (key=='d')
    {
    yrot += 1;
    if (yrot >360) yrot -= 360;
    }

    if (key=='a')
    {
    yrot -= 1;
    if (yrot < -360)yrot += 360;
    }
    if (key==27)
    {
    exit(0);
    }
}

int main (int argc, char **argv) {
    glutInit (&argc, argv);
    glutInitDisplayMode (GLUT_DOUBLE | GLUT_DEPTH); //set 
the display to Double buffer, with depth

    glutInitWindowSize (500, 500); //set the window size
    glutInitWindowPosition (100, 100); //set the position of 
the window

    glutCreateWindow ("A basic OpenGL Window"); //the caption
 of the window

    init (); //call the init function
    glutDisplayFunc (display); //use the display function to 
draw everything

    glutIdleFunc (display); //update any variables in display,
 display can be changed to anyhing, as long as you move the 
variables to be updated, in this case, angle++;

    glutReshapeFunc (reshape); //reshape the window accordingly

    glutKeyboardFunc (keyboard); //check the keyboard
    glutMainLoop (); //call the main loop
    return 0;
}

Download C++ Source Code for this Tutorial

Download Visual Basic Source Code for this Tutorial


Comments:

Name: aj
Date: 2010-01-16 17:06:23
Comment:
Just an FYI... There is one cube that moves weirdly compared to the other cubes. After launching the OpenGL window, press 'a' about 15-20 times. Then, press zoom-in and zoom-out by pressing 'w' and 's'.

I was able to get around it by initializing xpos, ypos, zpos, xrot, yrot, and angle in the main function.

Name: Ryan Badour
Date: 2010-01-19 15:09:04
Comment:
//positions of the cubes
float positionx[10];

void cubepositions (void) //set the positions of the cubes
{
for (int i = 0; i != 10; ++i)
{
positionx[i] = i * 5;
}
}


I'd recommend trying it this way. By default your looking at 10 cubes, and there is no magically cube separate from the others. Also the glutIdleFunc() shouldn't really be called on the display function that just rendering the scene again. If you want to redraw the screen with keyboard presses just use the glutPostRedisplay() function it tells the program to redraw the scene.

Anyways on the bright side, thanks for the tutorial I learned a few things fixing your code. :P

Name: Donald Urquhart
Date: 2010-01-19 19:31:48
Comment:
Hi AJ, I am not sure why you are getting one cube that moves differently to another. All of their positions are set the same way. Also, 'w' and 's' are not zoom in and zoom out, they are move forward and move back.

Hey Ryan, to set the cube positions, that is just a matter of preference. I just did it with the rand() function to position the cubes randomly.

Also, I am calling both the glutDisplayFunc and glutIdleFunc on the display() method, as this ensures maximum framerates and a continues update of the display. The glutIdleFunc() is called when there are no window events waiting. Whether or not you use the glutIdleFunc() to handle your non drawing code, and the glutDisplayFunc() for only your drawing, is a matter of preference. As I said, I want constant continuous updates, and this ensures maximum frame rates.

Finally, I'm not sure why you would want to tell OpenGL to redraw the scene with key presses, as the glutDisplayFunc() is a constant main loop that will draw automatically.

Cheers,
Donald

Name: Ryan Badour
Date: 2010-01-25 07:40:04
Comment:
There is a ghost cube being drawn separate of the other cubes. It moves differently. For example if you turn the camera to position the ghost cube infront of the other cubes then start moving forward and back you will notice that the ghost cube will eventually move behind the other cubes. That means the ghost cube is not being moved properly.

Secondly, the suggestions I made are to help put the cubes by default directly infront of the camera. If you fix the ghost cube than people who use this tutorial will not notice the cubes right away, they have to turn to the left a bit.

Thirdly, glutIdleFunc() is not meant for calling the draw function: http://www.opengl.org/resources/libraries/glut/spec3/node63.html
However, if you are correct about the maximum frame rates then kudos. But somehow I doubt calling that function (not meant for rendering) is going to give you a performance boost.

Finally, glutDisplayFunc() is only called when glutPostRedisplay() is called or when GLUT determines to call it: http://www.opengl.org/resources/libraries/glut/spec3/node46.html
"GLUT determines when the display callback should be triggered based on the window's redisplay state. The redisplay state for a window can be either set explicitly by calling glutPostRedisplay or implicitly as the result of window damage reported by the window system. Multiple posted redisplays for a window are coalesced by GLUT to minimize the number of display callbacks called."

Name: Ryan Badour
Date: 2010-01-25 07:42:22
Comment:
PS: Btw, you don't use "angle" anywhere, you simply add to it each iteration of display.

Name: Donald Urquhart
Date: 2010-01-25 09:21:33
Comment:
Hi Ryan,

The only thing I can think of that is causing the 'ghost' cube, is:
glTranslated(-positionx[i + 1] * 10, 0, -positionz[i + 1] *
10); //translate the cube

Where I appear to be going out of range of the array. Other than that, there is no code that would generate a 'ghost' cube.

I just read over the details for the glutIdleFunc() which you linked to, and they do in fact state that it can be used for rendering. In the very first sentence: "or continuous animation when window system events are not being received". There are quite a alot of OpenGL programmers who use this function for rendering also. But the spec does state: "In general, not more than a single frame of rendering should be done in an idle callback.", but I am not sure exactly why they say that.

Sorry, when I was talking about the glutDisplayFunc(), I was referring to this tutorial. Typically you do not require glutPostRedisplay() if you are using glutSwapBuffers(). I have not come across a case where I have had to use glutPostRedisplay() since using double buffering.

Oh, and yes, I don't use 'angle', that must have been there from a previous tutorial. Thanks.

Name: Victor
Date: 2010-01-27 00:12:27
Comment:
Hi Donald, I face the same phenomenon as Ryan. There is a cube that behaves differetnly from the rest from moving the camera towards and away from the cubes (the 'w' and 's' keys). It seem as if you had gone out of array range in the following statement:

glTranslated(-positionx[i + 1] * 10, 0, -positionz[i + 1] *
10); //translate the cube

However when I edited it to:

glTranslated(-positionx[i] * 10, 0, -positionz[i] *
10); //translate the cube

Now there are only 9 cubes rendered (all behaved normally tho).

Name: Baby Stuey
Date: 2010-03-05 20:23:40
Comment:
After reading the comments about there being this "ghost" cube, I had a look at the offending line:

glTranslated(-positionx[i + 1] * 10, 0, -positionz[i + 1] * 10); //translate the cube


Since (1 % 10 = 1) and (10 % 10 = 0), this tweak will reference indicies 1..9,0 properly rather than looking for the non-existent index 10:

glTranslated(-positionx[(i + 1) % N_CUBES] * 10, 0.0, -positionz[(i + 1) % N_CUBES] * 10); //translate the cube

// (Note: #define N_CUBES 10 has been added as well)


Actually, Victor's correction works fine as well (I'm not actually sure the reason behind indexing i+1...Donald?), but it's not the reason only 9 cubes display...

Name: Baby Stuey
Date: 2010-03-05 20:30:15
Comment:
I added a printf() within the for loop in cubepositions() to have a look at the cube positions:
//set the positions of the cubes
void cubepositions(void)
{ int i;
for(i = 0; i < N_CUBES; i++)
{ positionz[i] = rand() % 5 + 5;
positionx[i] = rand() % 5 - 2;
printf("Cube %d (x,y,z): %0.3f, 0.000, %0.3f\n", i, positionx[i], positionz[i] );
}
}

Now, since rand() returns a pseudo-random number between 0 and RAND_MAX, the operation
rand() % 5 clamps the position values from 0 to 4 and
rand() % 5 + 5 clamps the position values from 5 to 9

Since pseudo-random number sequences are repeatable, this produces:
Cube 0 (x,y,z): -1.000, 0.000, 8.000
Cube 1 (x,y,z): -2.000, 0.000, 7.000
Cube 2 (x,y,z): -2.000, 0.000, 8.000
Cube 3 (x,y,z): +0.000, 0.000, 6.000
Cube 4 (x,y,z): -1.000, 0.000, 9.000
Cube 5 (x,y,z): +0.000, 0.000, 7.000
Cube 6 (x,y,z): +2.000, 0.000, 5.000
Cube 7 (x,y,z): -1.000, 0.000, 8.000
Cube 8 (x,y,z): -1.000, 0.000, 5.000
Cube 9 (x,y,z): -1.000, 0.000, 7.000


You may notice that values for cubes 0 and 7 are the same, which means they will overlap, resulting in only 9 cubes.

My solution was to add a second value to the positions, but there are others (as Ryan and Victor have suggested previously). Adding a non-zero y-component would also be a fix.
void cubepositions(void) 
{ int i;
for(i = 0; i < N_CUBES; i++)
{ // clamp to range [0..4] + 5 = [5..9]
positionz[i] = rand() % 5 + 5;
// clamp to range [0.000..0.999] and add to z-position
positionz[i] += rand() % 1000 / 1000.0;
// clamp to range [-2..2], keeping cubes within the initial field of view
positionx[i] = rand() % 5 - 2;
// clamp to range [0.000..0.999] and add to x-position
positionx[i] += rand() % 1000 / 1000.0;
printf("Cube %d (x,y,z): %0.3f, 0.000, %0.3f\n", i, positionx[i], positionz[i] );
}
}

Now, the cubes are more spread out and no two get the same values:
Cube 0 (x,y,z): +0.915, 0.000, 8.886
Cube 1 (x,y,z): -0.508, 0.000, 8.335
Cube 2 (x,y,z): +0.027, 0.000, 9.421
Cube 3 (x,y,z): +1.926, 0.000, 5.059
Cube 4 (x,y,z): +0.736, 0.000, 5.426
Cube 5 (x,y,z): +0.429, 0.000, 6.368
Cube 6 (x,y,z): +0.123, 0.000, 7.530
Cube 7 (x,y,z): +2.802, 0.000, 7.135
Cube 8 (x,y,z): +2.167, 0.000, 7.058
Cube 9 (x,y,z): -0.958, 0.000, 8.456

Remember: Pseudo-random number generators (PRNGs) must be treated with a heavy emphasis on the "pseudo", but can get you what you want if they're used creatively!

Name: Baby Stuey
Date: 2010-03-05 20:41:39
Comment:
Here's my fix to all of the above issues and some logic issues in the keyboard function, as well as the addition of more keyboard controls (I type Dvorak, so QWERTY-specific keymaps are less-than-intuitive for me) and elimination of all of the "magic numbers" and some C-unfriendly statements...

#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include <GL/gl.h>
#include <GL/glut.h>

#define PI 3.141592654f
#define N_CUBES 10 // display 10 cubes
#define MAX_ROTATION 360 // there are 360 degrees in a full rotation
#define DEG_PER_RAD 180 // 180 degrees in 1 PI Radian
#define ROTATION_STEP 1 // amount to shift with each key press
#define SPACING_FACTOR 10 // how spread out we want our cubes

//angle of rotation
float xpos = 0., ypos = 0., zpos = 0., xrot = 0., yrot = 0., angle = 0.0;

// x & z positions of the cubes (y = 0.0)
float positionz[N_CUBES];
float positionx[N_CUBES];

//set the positions of the cubes
void cubepositions(void)
{ int i;
for(i = 0; i < N_CUBES; i++)
{ positionz[i] = rand() % 5 + 5; // clamp to range [0..4] + 5 = [5..9]
positionz[i] += rand() % 1000 / 1000.0; // clamp to range [0.000..0.999]
positionx[i] = rand() % 5 - 2; // clamp to range [-2..2], keeping cubes within the initial field of view
positionx[i] += rand() % 1000 / 1000.0; // clamp to range [0.000..0.999]
printf("Cube %d (x,y,z): %+0.3f, 0.000, %0.3f\n", i, positionx[i], positionz[i] );
}
}

//draw the cube
void cube (void)
{ int i;
for(i = 0; i < N_CUBES; i++)
{ glPushMatrix();
glTranslated(-positionx[(i + 1) % N_CUBES] * SPACING_FACTOR, 0.0, 0.0); // translate the cube
glTranslated(0.0, 0.0, -positionz[(i + 1) % N_CUBES] * SPACING_FACTOR);
glutSolidCube(2); //draw the cube
glPopMatrix();
}
}

void init(void)
{ cubepositions();
}

void enable(void)
{ glEnable(GL_DEPTH_TEST);//enable the depth testing
glEnable(GL_LIGHTING); //enable the lighting
glEnable(GL_LIGHT0); //enable LIGHT0, our Diffuse Light
glShadeModel(GL_SMOOTH);//set the shader to smooth shader
}

void camera(void)
{ glRotatef(xrot,1.0,0.0,0.0); //rotate our camera on the x-axis (left and right)
glRotatef(yrot,0.0,1.0,0.0); //rotate our camera on the y-axis (up and down)
glTranslated(-xpos,-ypos,-zpos); //translate the screen to the position of our camera
}

void display(void)
{ glClearColor(0.0,0.0,0.0,1.0); //clear the screen to black
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //clear the color buffer and the depth buffer
glLoadIdentity();
camera();
enable();
cube(); //call the cube drawing function
glutSwapBuffers(); //swap the buffers
angle++; //increase the angle
}

void reshape(int w, int h)
{ glViewport(0, 0, (GLsizei)w, (GLsizei)h); //set the viewport to the current window specifications
glMatrixMode(GL_PROJECTION); //set the matrix to projection

glLoadIdentity ();
gluPerspective (60, (GLfloat)w / (GLfloat)h, 1.0, 1000.0); //set the perspective (angle of sight, width, height, depth)
glMatrixMode (GL_MODELVIEW); //set the matrix back to model
}

void keyboard(unsigned char key, int x, int y)
{ if(key == 'q') // rotate down
{ xrot += ROTATION_STEP;
if(xrot >= MAX_ROTATION) // keep rotation within [0..359 degrees]
xrot -= MAX_ROTATION;
} else if(key == 'z') // rotate up
{ xrot -= ROTATION_STEP;
if (xrot < 0)
xrot += MAX_ROTATION;
} else if(key == 'w'|| key == '+') // move forward
{ float xrotrad, yrotrad;
// rotation values expressed in radians (n degrees = n x PI/180 radians)
yrotrad = (yrot / DEG_PER_RAD * PI);
xrotrad = (xrot / DEG_PER_RAD * PI);
xpos += sin(yrotrad);
zpos -= cos(yrotrad);
ypos -= sin(xrotrad);
} else if(key == 's' || key == '-') // move backward
{ float xrotrad, yrotrad;
// rotation values expressed in radians (n degrees = n x PI/180 radians)
yrotrad = (yrot / DEG_PER_RAD * PI);
xrotrad = (xrot / DEG_PER_RAD * PI);
xpos -= sin(yrotrad);
zpos += cos(yrotrad);
ypos += sin(xrotrad);
} else if(key == 'd') // rotate right
{ yrot += ROTATION_STEP; // keep rotation within [0..359 degrees]
if(yrot >= MAX_ROTATION)
yrot -= MAX_ROTATION;
} else if(key == 'a')
{ yrot -= ROTATION_STEP; // rotate left
if (yrot < 0)
yrot += MAX_ROTATION;
} else if(key == 27) // Esc
{ exit(0);
}
}

void keyboard2(int key, int x, int y)
{ if(key == GLUT_KEY_UP) // look up
{ xrot += ROTATION_STEP;
if(xrot >= MAX_ROTATION) // keep rotation within [0..359 degrees]
xrot -= MAX_ROTATION;
} else if(key == GLUT_KEY_DOWN) // look down
{ xrot -= ROTATION_STEP;
if (xrot < 0)
xrot += MAX_ROTATION;
}else if(key == GLUT_KEY_RIGHT) // keep rotation within [0..359 degrees]
{ yrot += ROTATION_STEP;
if(yrot >= MAX_ROTATION)
yrot -= MAX_ROTATION;
} else if(key == GLUT_KEY_LEFT)
{ yrot -= ROTATION_STEP;
if (yrot < 0)
yrot += MAX_ROTATION;
}
}

int main (int argc, char **argv)
{ glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH); //set the display to Double buffer, with depth
glutInitWindowSize(500, 500); //set the window size
glutInitWindowPosition(100, 100); //set the position of the window
glutCreateWindow("A basic OpenGL Window"); //the caption of the window
init(); //call the init function
glutDisplayFunc(display); //use the display function to draw everything
glutIdleFunc(display); //update any variables in display, display can be changed to anyhing, as long as you move the variables to be updated, in this case, angle++;
glutReshapeFunc(reshape); //reshape the window accordingly
glutKeyboardFunc(keyboard); //check the keyboard
glutSpecialFunc(keyboard2); //check the keyboard for 'special' keys

glutMainLoop(); //call the main loop
return 0;
}
Name   Email


Reload Image

 
     

 

Copyright 2009, Donald Urquhart AKA Swiftless
Check out: http://www.cdadc.com