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!

  • March 25, 2010
  • 26