23. OpenGL Camera Part 2 (Version 2.0)
Introduction
Building on the basics of camera movement in OpenGL, this tutorial will demonstrate how to incorporate mouse input and strafe movement for a more immersive and intuitive control system. These features are common in first-person shooter games and can make your OpenGL applications feel more dynamic.
We’ll cover:
- Implementing strafe movement using keyboard controls.
- Using the mouse to control the camera’s pitch and yaw.
- Registering and processing mouse input with GLUT.
By the end of this tutorial, you’ll have a functional first-person camera system.
Adding Strafe Movement
Strafe movement allows the camera to move sideways (left or right) while maintaining its current orientation. This is achieved by using the cosine and sine functions to calculate the direction of movement based on the camera’s yaw angle (yrot
).
Here’s the implementation for strafing:
// Move right (D key) if (key == 'd') { float yrotrad = (yrot / 180 * 3.141592654f); // Convert yrot to radians xpos += float(cos(yrotrad)) * 0.2; // Move along X-axis zpos += float(sin(yrotrad)) * 0.2; // Move along Z-axis } // Move left (A key) if (key == 'a') { float yrotrad = (yrot / 180 * 3.141592654f); // Convert yrot to radians xpos -= float(cos(yrotrad)) * 0.2; // Move along X-axis zpos -= float(sin(yrotrad)) * 0.2; // Move along Z-axis }
Notice how the same calculations are used, but with opposite signs for xpos
and zpos
. This ensures the camera moves in the correct direction based on the input.
Mouse Input for Camera Rotation
Mouse input is a natural way to control the camera’s pitch (up/down) and yaw (left/right). In this tutorial, we’ll use GLUT’s glutPassiveMotionFunc
to register mouse movements when no buttons are pressed.
Here’s the function for handling mouse movement:
void mouseMovement(int x, int y) { int diffx = x - lastx; // Calculate horizontal movement int diffy = y - lasty; // Calculate vertical movement lastx = x; // Update the last X position lasty = y; // Update the last Y position xrot += (float)diffy; // Update pitch yrot += (float)diffx; // Update yaw }
To enable this functionality, add the following line in your main
function:
glutPassiveMotionFunc(mouseMovement); // Register the mouse movement function
Combining Everything
The following code integrates strafe movement and mouse input into the existing camera system. Use the WASD keys for movement and the mouse for looking around.
Tutorial Code
#include <GL/gl.h> #include <GL/glut.h> #include <cstdlib> #include <cmath> // Camera variables float xpos = 0, ypos = 0, zpos = 0, xrot = 0, yrot = 0; float lastx, lasty; // 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 lighting and depth testing void enable(void) { glEnable(GL_DEPTH_TEST); glEnable(GL_LIGHTING); glEnable(GL_LIGHT0); glShadeModel(GL_SMOOTH); } // Camera setup void camera(void) { glRotatef(xrot, 1.0, 0.0, 0.0); glRotatef(yrot, 0.0, 1.0, 0.0); glTranslated(-xpos, -ypos, -zpos); } // 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 yrotrad; if (key == 'w') { // Move forward yrotrad = (yrot / 180 * 3.141592654f); xpos += float(sin(yrotrad)); zpos -= float(cos(yrotrad)); } if (key == 's') { // Move backward yrotrad = (yrot / 180 * 3.141592654f); xpos -= float(sin(yrotrad)); zpos += float(cos(yrotrad)); } if (key == 'd') { // Strafe right yrotrad = (yrot / 180 * 3.141592654f); xpos += float(cos(yrotrad)) * 0.2; zpos += float(sin(yrotrad)) * 0.2; } if (key == 'a') { // Strafe left yrotrad = (yrot / 180 * 3.141592654f); xpos -= float(cos(yrotrad)) * 0.2; zpos -= float(sin(yrotrad)) * 0.2; } if (key == 27) { // Exit program exit(0); } } // Mouse movement void mouseMovement(int x, int y) { int diffx = x - lastx; int diffy = y - lasty; lastx = x; lasty = y; xrot += (float)diffy; yrot += (float)diffx; } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(500, 500); glutCreateWindow("Advanced OpenGL Camera"); init(); glutDisplayFunc(display); glutIdleFunc(display); glutReshapeFunc(reshape); glutPassiveMotionFunc(mouseMovement); 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!
Please run you damn code before you post it online, it is full of glitches.
Hey KanTheMan2134,
You sound frustrated, how is everything? The code has been run and virtually all of my code is run before it goes online. This is an older tutorial and some of the formatting may not be up to scratch but I can assure you that it will work. You may just have to type it out instead of copying and pasting.
Thanks,
Swiftless
thanks for the code, I was looking for a fps example
In Code::Blocks, you need to replace the single quotation marks. But good overall code!!!
Hi drew,
That’s a problem with how WordPress converts the characters.
Cheers,
Swiftless
This implementation has gimbal lock
Hi Joshua,
It doesn’t technically have gimbal lock when you first start, it can however suffer from gimbal lock. This is only a starter, as we don’t want to scare people away with quaternions straight away.
Cheers,
Swiftless
Hi, two suggestions:
– if you include math.h you can use the M_PI define for the pi angle.
– you are moving the modelview around, it’s incorrect, you want to move the camera, so in the display you should traslate and rotate the GL_PROJECTION, and then before the return point, set the GL_MODELVIEW again. Obviously, if you do this you have to change all the functions, but it’s the correct way.
Anyway very good tutorial keep up the good work!
Hi Enrico,
That’s definitely true about math.h and M_PI!
As for saying what I’m doing is incorrect, I hate to disappoint, but there is absolutely nothing wrong with it. In fact, it is a lot riskier to mess around with your GL_PROJECTION matrix and debugging your camera code can be a massive pain. The GL_PROJECTION matrix defines the projection from 3D to 2D on our screen, not where we are in our 3D scene.
As a camera moves around a 3D scene, it makes a lot more sense to move the objects around the point of projection, to me that makes more sense anyway.
I recommend you take a look at the following article on GL_PROJECTION abuse:
http://www.sjbaker.org/steve/omniv/projection_abuse.html
Cheers,
Swiftless
Really good. It helped me a lot. I’m working OpenGL with SDL instead GLUT, but that strafe movement was what I wanted to learn how to implement.
I was using gluLookAt() to try to do that, but I understood the way you did. Thanks!
Hello,
I am working on a project that involves being able to walk around a scene using the asdw keys and looking around using the arrow keys. I just recently added lighting effects to the mix. I also have a variable ‘mode’ that determines whether the scene is viewed in orthogonal mode or perspective mode. In orthogonal mode, I not able to change the position of the camera or eye (however you wanna think of it). You can only walk around in the perspective mode. When in orthogonal mode, the lighting effects work. But as soon as I switch to perspective mode, it is as if I never applied the lighting effects. The global ambiance is back and light intensities do not change. Would you have any insight as to why this would be happening. I was wondering if it had anything to do with the specific method you used to create the first person perspective as opposed to using something like gluLookAt(). Thanks
Bonjour,
D’abord je vous remercie sur votre code qui’est vraiment intéressant, et qui m’a aidé vraiment.
Pourriez vous faire un autre tutorial sur linteraction multitouch avce glut et opengl.
et merci