OpenGL Tips

Camera

Some informational pages about implementing a camera:

Stack Exchange – I’m rotating an object on two axes, so why does it keep twisting around the third axis?

LearnOpenGL – Camera

3D Game Engine Programming – Understanding the View Matrix

Placing a Camera: the LookAt Function

These generally suggest accumulating yaw and pitch, clamping pitch, and calculating right, up, and forward vectors to create a LookAt view matrix.

Here we suggest a related approach, wherein per-frame yaw and pitch values are taken as direction vectors in camera space, to calculate a forward vector for LookAt in world space, while maintaining a consistent up vector.

The result is a camera that reacts consistently to input, regardless of the orientation of the camera. In other words, when the camera is looking at an object, rotating the camera left will always make the object on the screen appear to move to the right, regardless if the camera is above, or behind, or in any other relation to the object.

The gist of it, as the code below shows, is to transform the vector (yaw, pitch, 1, 0) by the inverse of the view matrix. (The view matrix transforms world coordinates to camera space; its inverse transforms camera space to world coordinates.) This produces the desired forward vector in world space.

The fourth column of that inverted matrix is the position of the camera (assuming a column-major matrix), and the second column is the camera up vector, both in world space. Taken together, the camera position, and forward and up vectors are used to construct a new LookAt view matrix for rendering.


public class Camera
{

//
// this float[16] array can be passed
// as a shader uniform, e.g.:
//
// uniform mat4 ViewMatrix;
// gl_Position = ProjectionMatrix
//             * ViewMatrix
//             * vec4(aPos, 1.);
//
// use your standard method to overwrite this with
// a constructed look-at matrix, whenever the camera
// has to be set explicitly, e.g. during scene set-up.
//

public readonly float[] ViewMatrix = Matrix.Identity();

//
// per-frame Update method.
//
// yRotation - negative values rotate left,
//             positive values rotate right
//
// xRotation - negative values rotate down,
//             positive values rotate up
//
// zMovement - negative values move backward,
//             positive values move forward
//
// (the above assumes a left-hand coordinate system
// where positive X goes right, positive Y goes up,
// and positive Z goes forward into the screen.)
//
// consider pre-multiplying these values by
// deltaTime, before using them in this method
//

public void Update (
    float yRotation,    // yaw left/right
    float xRotation,    // pitch up/down
    float zMovement)    // forward/back
{
    // the inverse of the view matrix (i.e. world
    // to camera) is a matrix that transforms from
    // camera to world space
    var inv = Matrix.Inverse(ViewMatrix);

    // using rotation input in a vector:
    //      (yaw, pitch, 1, 0)
    // and rotating it into camera space.
    // note that w == 0, so translation is
    // not applied
    var vector =
        new float[] { yRotation, xRotation, 1f, 0f };
    Matrix.TransformVector(vector, inv);
    var (x, y, z) = (vector[0], vector[1], vector[2]);

    // the rotated vector is the new forward direction,
    // relative to the current camera position/origin.
    // the fourth column of the (inverted view) matrix
    // is the translation vector from world to camera,
    // i.e., the current camera position/origin
    var factor = zMovement
               / MathF.Sqrt(x * x + y * y + z * z);
    // array indices are for a column-major matrix:
    var positionX = inv[12] + x * factor;
    var positionY = inv[13] + y * factor;
    var positionZ = inv[14] + z * factor;

    // finally, use the new position and forward
    // direction, and the old up vector, to
    // calculate a new view matrix
    Matrix.SetLookAtMatrix(ViewMatrix,
                   positionX, positionY, positionZ,
                   // camera target (forward vector)
                   positionX - x,
                   positionY - y,
                   positionZ - z,
                   // up vector is the second column
                   // of the inverted view matrix
                   inv[4], inv[5], inv[6]);
}

//
// Methods used:
//
// Matrix.Identity returns a float[16] identity matrix.
//
// Matrix.Inverse returns a float[16] inverse matrix for
// some input float[16] matrix.
//
// Matrix.TransformVector multiplies float[4] vector V
// by float[16] matrix M, in-place, i.e.:  V = M * V
//
// Matrix.SetLookAtMatrix(M,Position, Target, UpVector)
// overwrites float[16] matrix M with a look-at matrix
// constructed from position, target, and up vector.
//
// Matrix are column-major as expected by OpenGL.
//
// Coordinate system is left-handed with positive X
// is right, positive Y is up, positive Z is forward.
//
}