22. OpenGL Camera (Version 2.0)
Introduction
When creating a 3D application in OpenGL, the camera is your window into the virtual world. It defines what the viewer sees and how they navigate the scene. A well-implemented camera system can make or break the user experience, whether you’re building a game, simulation, or visualization tool.
In this tutorial, we’ll build a simple camera system that allows you to move freely in a 3D space. By understanding how the camera operates, you’ll gain insights into how OpenGL transforms and renders your objects.
The Role of the Camera in OpenGL
OpenGL itself doesn’t have a built-in camera. Instead, you simulate a camera by applying transformations to your entire scene. These transformations include:
- Translation: Moving the scene along the X, Y, and Z axes.
- Rotation: Changing the direction the scene is viewed from.
These transformations make it appear as though the camera is moving, but it’s really the objects in the scene that are being repositioned and rotated.
Key Variables for the Camera
Our camera’s movement and orientation will be controlled by these variables:
- xpos, ypos, zpos: The camera’s position in 3D space.
- xrot, yrot: The camera’s rotation along the X-axis (pitch) and Y-axis (yaw).
Here’s an example of how we’ll use these variables:
glRotatef(xrot, 1.0, 0.0, 0.0); // Rotate up and down glRotatef(yrot, 0.0, 1.0, 0.0); // Rotate left and right glTranslated(-xpos, -ypos, -zpos); // Move the scene
These transformations are applied in reverse order due to OpenGL’s matrix stack.
Understanding Movement with Trigonometry
To move the camera forward and backward based on its orientation, we need to calculate the movement direction using trigonometry:
- sin: Used to calculate movement along the X-axis (horizontal).
- cos: Used to calculate movement along the Z-axis (depth).
For example:
yrotrad = (yrot / 180 * 3.141592654f); // Convert yrot to radians xpos += float(sin(yrotrad)); // Move forward along X-axis zpos -= float(cos(yrotrad)); // Move forward along Z-axis
By combining these functions, you can move the camera in the direction it’s facing, regardless of its current rotation.
Rotations: Pitch and Yaw
Rotation is controlled by the variables xrot
(pitch) and yrot
(yaw):
- Pitch: Up and down movement (rotation around the X-axis).
- Yaw: Left and right movement (rotation around the Y-axis).
When implementing rotations, it’s important to wrap angles to stay within the range of 0–360 degrees:
if (xrot > 360) xrot -= 360; if (xrot < -360) xrot += 360; if (yrot > 360) yrot -= 360; if (yrot < -360) yrot += 360;
This ensures smooth rotation without unexpected behavior.
Enhancing the Scene
To make the scene more interactive and realistic:
- Add cubes at random positions to create a navigable environment.
- Enable lighting to highlight the objects.
- Use depth testing to ensure objects closer to the camera obscure those behind them.
Tutorial Code
Here’s the complete example, combining everything we’ve discussed:
#include <GL/gl.h> #include <GL/glut.h> #include <cstdlib> #include <cmath> // Camera position and rotation float xpos = 0, ypos = 0, zpos = 0, xrot = 0, yrot = 0; // Set cube positions float positionz[10]; float positionx[10]; void cubepositions(void) { for (int i = 0; i < 10; i++) { positionz[i] = rand() % 5 + 5; positionx[i] = rand() % 5 + 5; } } // Draw cubes void cube(void) { for (int i = 0; i < 10; i++) { glPushMatrix(); glTranslated(-positionx[i + 1] * 10, 0, -positionz[i + 1] * 10); glutSolidCube(2); glPopMatrix(); } } // Initialize settings void init(void) { cubepositions(); } // Enable features void enable(void) { glEnable(GL_DEPTH_TEST); // Enable depth testing glEnable(GL_LIGHTING); // Enable lighting glEnable(GL_LIGHT0); // Enable diffuse light glShadeModel(GL_SMOOTH); // Smooth shading } // Camera setup void camera(void) { glRotatef(xrot, 1.0, 0.0, 0.0); // Rotate along X-axis glRotatef(yrot, 0.0, 1.0, 0.0); // Rotate along Y-axis glTranslated(-xpos, -ypos, -zpos); // Translate the camera } // Display function void display(void) { glClearColor(0.0, 0.0, 0.0, 1.0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); camera(); enable(); cube(); glutSwapBuffers(); } // Reshape function void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60, (GLfloat)w / (GLfloat)h, 1.0, 1000.0); glMatrixMode(GL_MODELVIEW); } // Keyboard controls void keyboard(unsigned char key, int x, int y) { float xrotrad, yrotrad; if (key == 'w') { // Move forward 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') { // Move backward yrotrad = (yrot / 180 * 3.141592654f); xrotrad = (xrot / 180 * 3.141592654f); xpos -= float(sin(yrotrad)); zpos += float(cos(yrotrad)); ypos += float(sin(xrotrad)); } if (key == 'a') { yrot -= 1; if (yrot < -360) yrot += 360; } // Rotate left if (key == 'd') { yrot += 1; if (yrot > 360) yrot -= 360; } // Rotate right if (key == 'q') { xrot += 1; if (xrot > 360) xrot -= 360; } // Look up if (key == 'z') { xrot -= 1; if (xrot < -360) xrot += 360; } // Look down if (key == 27) { exit(0); } // Exit program } // Main function int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL Camera System"); init(); glutDisplayFunc(display); glutIdleFunc(display); glutReshapeFunc(reshape); glutKeyboardFunc(keyboard); glutMainLoop(); return 0; }
If you have any questions or run into issues, feel free to email me at swiftless@gmail.com. Happy coding!
The code all seems to be centered instead of left-aligned
Hi.
Thank You for this example, it’s great. I just have one little question. Let’s say I wanted to add the ability to roll the camera around the z axis. How can I do that?
Thank You
Hmm this camera is moving the world itself right?
Hi how do change the sensetivity?
Shouldn’t “-positionx[i+1]” be an error? It would be out of range in the last round.
also when i changed this line zpos -= float(cos(yrotrad)) ; by this one zpos -= 0.9 ; only difference is speed
i don’t know why we need to put trigonometry?
Zilot, his calculation works because it creates a new position vector that is pointing the same direction as the old. If you were to simply add 0.9 then you wouldn’t be traveling in the same direction that the camera is facing. To see what I mean, try adding a larger number like 10 instead… the camera rotates instead of moving forward as intended.
A problem I see is that the speed in which the camera moves varies depending on the values of yrot and xrot to a small extent, it isn’t significant for this program but if the camera’s speed is ever to be scaled up then it may become more noticeable.
An alternative would be to add one unit vector in the direction the camera is facing, ie divide the position vector by it’s magnitude then add that vector to the original position vector. That way it’s easier to program the exact speed you want and avoids using sine and cosine. The drawback is more CPU calculations are required, however.
Thanks for the tutorial, Swiftless, it made much more sense then everything else I’ve found.
hi swifless first thx for this tutorial
i m a bit confused when press w and s button and when i removed this lines from the code
xpos += float(sin(yrotrad)) ;
ypos -= float(sin(xrotrad)) ;
it really gave the same result as when they exist i haven’t understand their role?
i hope you can post some screenshot for each tutorial so that we can understand more clearly what it does…:)
Just wanted to say thanks for this tutorial. I completely overlooked using radians, and everything was acting weird. Thanks!
Thanks so much! Got this running in no time. Thanks for sharing… it’s so refreshing..
wats the difference between and header files!!!???
i like this tutorial. it gives me alot of ideas. thanks
Hi,
Thanks for the great tutorial. However, I don’t really get the math behind scene of camera function and when user press ‘w’.
Thank you
Hi dorekofu_87,
The maths behind the key presses is all about trigonometry and circles. A bit of research on this and you should be able to figure it out.
Cheers,
Swiftless
Hi,
I would still highly suggest storing your value of Pi as a constant that you then refer to in the code. C++ compilers are very good at both handling this constant correctly (not rounding it to different numbers), and more importantly generating fast code.
It’s a simple technique that generally appears even in compilers made by undergraduate students for their university classes and is called constant propagation. Basically what it means is that if there is any value that depends only on constant values will be computed once at compile time. So for example, in the code above when you have lines such as yrotrad = (yrot / 180 * 3.141592654f); the compiler will do the math for 1/180 * 3.141592654f exactly one, and the result will be stored as constant data that the program uses at runtime.
Even if it weren’t the case that the compiler will automatically generate fast code here, the readability and maintainability benefits of using a constant for pi mean that you really should! Just wait till you hardcode the value for pi in your program 100 different places, but do it differently in each place!
Hi Spectre256,
Nice suggestion! There are many different optimization routes you can take, and I suggest people explore them. This tutorial is originally about 5 years old now, and I plan on rewriting it, probably with this suggestion included 🙂
Cheers,
Swiftless
did you ever rewrite this code?
did you ever re-write this code?
float*
What is the int angel there for?, i cant see that u are using it in anything, just increasing it
Hey Per,
That looks like just a left-over variable from the code I built the project on.
Cheers,
Swiftless
Nice and easy, just what I needed after making my own crappy camera movement! 🙂