| |
 |
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
|
 |