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!

  • March 25, 2010
  • 14