OpenGL Camera position woes (with a bonus FPS camera class)

When I’m not dabbling in web development (and to be fair, since I work full time as a web developer I don’t usually do that in my free time) I like to play around in various technologies to expand my repertoire and hopefully learn new concepts that I can use in other fields as well. My latest kick has been getting back into OpenGL. I had attempted to learn OpenGL when I was much younger with a pipe dream of creating the next big game by myself, but never really got past the rendering primitives part, as I had convinced myself that I would never be able to make the 3D models, sounds and music required for a full-fledged game. I had also done this with DirectX and XNA, and while I had gotten farther in terms of a full game with each technogoly, I always ended up stopping.

This time around, I’m using OpenGL through LWJGL so I can cut out a lot of the boring boilerplate that turned me off the first time. To further reduce some boilerplate, I used a generic FPS Camera from Lloyd Goodall. In addition, I’m sticking with a Voxel based project, so that I don’t have to worry about any model meshes or animation (for now, at least). Everything was going well until, when working on Chunk Management, which is essentially loading segments of the terrain around the player and removing ones that too far away so that you can show a “seamless” world while only processing what’s around the player. While I had no issue with the chunk management itself, there was a strange issue with the actual rendering. What was happening is that I would move the camera along the positive X-axis, but the chunks along the negative X-axis would start loading.

After pouring over the chunk manager for days and determining that nothing was wrong there, I started to look at the only part of the project I hadn’t written: the camera. Looking at the code, it uses the following to set up the camera position:

public void lookThrough()
{
    // Rotate the pitch around the X axis
    GL11.glRotatef(pitch, 1.0f, 0.0f, 0.0f);

    // Rotate the yaw around the Y axis
    GL11.glRotatef(yaw, 0.0f, 1.0f, 0.0f);

    // Translate to the position vector's location
    GL11.glTranslatef(position.x, position.y, position.z);
}

Recall back to your basic OpenGL graphics pipeline, which can be summarized in three big steps: model transform, view (camera) transform, and projection transform. The problem I was having laid in the second step, the view transform.

I don’t claim to be an expert in OpenGL by any means, but I will try to explain what was going on as far as I understand it. Recall that the view transform takes a set up “world” (where each entity that is on the screen was independently rendered from the origin of (0, 0, 0)) and adjusts it so that the “camera” is at the new origin point, and everything else in the world is shift view translation, rotation, scaling, etc to reflect that. The camera class I used made the assumption that the translation and rotation of the camera was for the camera itself, which would make sense if OpenGL supported actual cameras. However, recall that it does not, everything is rendered as if a “camera” was at the origin. As such, the view transform actually transforms the world itself, not the camera. Because of this, we have to translate the world by the negative camera position, which will correctly set the camera’s position in the translated world. I’ve drawn this crappy MS Paint diagram to hopefully show the different between what was expected, and what actually occurred:

Camera vs World translation

Camera vs World translation

In the end, your camera class will look more like below. I provided the whole class, as the movement code will change to adjust for the new rendering setup (thought the rotation calculations may not have been required to change). Also note that I do not claim to be an OpenGL expert, so this may not be perfect, but it appears to work correctly:

import org.lwjgl.opengl.GL11;
import org.lwjgl.util.glu.GLU;
import org.lwjgl.util.vector.Vector3f;

public class Camera {

    // Camera constants
    private final float CAMERA_SPEED = 20.0f;

    // Camera position
    private Vector3f position = null;

    // Camera view properties
    private float pitch = 0, yaw = 0, roll = 0;

    // Mouse sensitivity
    private float mouseSensitivity = 0.25f;

    // Constructor
    public Camera(float x, float y, float z) {
        this.position = new Vector3f(x, y, z);
    }

    // Used to change the yaw of the camera
    public void yaw(float amount) {
        this.yaw += (amount * this.mouseSensitivity);
    }

    // Used to change the pitch of the camera
    public void pitch(float amount) {
        this.pitch += (amount * this.mouseSensitivity);
    }

    // Used to change the roll of the camera
    public void roll(float amount) {
        this.roll += amount;
    }

    // Moves the camera forward relative to its current rotation
    public void walkForward(float distance) {
        float d = (CAMERA_SPEED * distance);
        position.x -= d * (float)Math.sin(Math.toRadians(yaw));
        position.z -= d * (float)Math.cos(Math.toRadians(yaw));
    }

    // Moves the camera backward relative to its current rotation
    public void walkBackwards(float distance) {
        float d = (CAMERA_SPEED * distance);
        position.x += d * (float)Math.sin(Math.toRadians(yaw));
        position.z += d * (float)Math.cos(Math.toRadians(yaw));
    }

    // Strafes the camera left relative to its current rotation
    public void strafeLeft(float distance) {
        float d = (CAMERA_SPEED * distance);
        position.x += d * (float)Math.sin(Math.toRadians(yaw-90));
        position.z += d * (float)Math.cos(Math.toRadians(yaw-90));
    }

    // Strafes the camera right relative to its current rotation
    public void strafeRight(float distance) {
        float d = (CAMERA_SPEED * distance);
        position.x += d * (float)Math.sin(Math.toRadians(yaw+90));
        position.z += d * (float)Math.cos(Math.toRadians(yaw+90));
    }

    public void goUp (float distance) {
        position.y += (CAMERA_SPEED * distance);
    }

    public void goDown (float distance) {
        position.y -= (CAMERA_SPEED * distance);
    }

    // Translates and rotates the matrix so that it looks through the camera
    public void lookThrough() {
        GL11.glRotatef(-pitch, 1.0f, 0.0f, 0.0f);
        GL11.glRotatef(-yaw, 0.0f, 1.0f, 0.0f);
        GL11.glTranslatef(-position.x, -position.y, -position.z);
    }
}

If you’re interested in writing a Voxel engine or game, the Let’s Make a Voxel Engine website is probably the best place to learn about it. Be forewarned though, there is a TON of data that is excluded in this tutorial, so you’ll have to piece together a lot of it from various sources. I’m planning on writing a similar tutorial tailored for LWJGL, but haven’t gotten to it yet.

Also read...

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>