Computing view matrix and then loading it on avery frame...

Started by paandar, April 15, 2013, 18:58:22

Previous topic - Next topic

paandar

A question about the view matrix,

I'm trying to implement a simple FPS camera with 6DOF rotation and translation. What I do is keep the camera position in a separate vector variable, and then on every frame I do something like this (pseudo code):

camera.transform.matrix.makeIdentity();
camera.univPosition(camera.transform.position.x, camera.transform.position.y, camera.transform.position.z);
camera.locRotation(pitch, yaw, 0);

glLoadMatrix(camera.transform.matrix);
glMultMatrix(model.transform.matrix);


This forces me to maintain the camera position in a separate vector because of the 'camera.transform.matrix.makeIdentity()' above.
I know this is an awful approach and it's driving me crazy but don't know a better way to do it. :(

What is the correct way to compute the view transformations on software and then loading then with glLoadMatrix() on every frame?

Thanks in advance.

quew8

My solution is to store matrices and vectors describing the transform (position and orientation). Then when it is moved, I update the vectors and set a flag saying a new matrix is needed. Then when I fetch the matrix, it checks if a new matrix is needed, constructs it if it is otherwise just returns the stored matrix. I don't see anything massively "awful" with this approach so I'm not really sure what your referring to.
The other solution I can think of is to store separate rotation and translation matrices (maybe even separate x, y and z rot matrices), transform them individually when the camera moves and combine them when required. However this seems to be replacing a few vectors with a few matrices. That's escalation for you.

I wasn't sure my first option was explained clearly so here is some code:
public class Camera {
    private Matrix projection;
    private boolean needsNewProjection = true;
    private Matrix viewing;
    private boolean needsNewViewing = true;
    private Matrix projviewing;
    private boolean needsNewProjViewing = true;
    
    private Vector position;
    private Vector orientation;
    private float aspectRatio;
    private float left, right, bottom, top, near, far;
    
    public Camera(Vector position, Vector orientation, float seenWidth, float seenHeight, float nearDepth, float farDepth) {
       this.position = position;
       this.orientation = orientation;
       this.aspectRatio = Grav3D.getWindow().getViewport().getAspectRatio();
       float f = seenWidth / 2 , g = (seenHeight / 2) * aspectRatio;
       this.left = -f;
       this.right = f;
       this.bottom = -g;
       this.top = g;
       this.near = nearDepth;
       this.far = farDepth;
    }
    
    public void translate(Vector dv) {
        this.position = position.add(dv);
        this.needsNewViewing = true;
        this.needsNewProjViewing = true;
    }
    
    public void rotate(Vector da) {
        this.orientation = orientation.add(da);
        this.needsNewViewing = true;
        this.needsNewProjViewing = true;
    }
    private void createProjectionMatrix() {
        if(needsNewProjection) {
            projection = Matrix.makeFrustum(left, right, bottom, top, near, far);
            needsNewProjection = false;
        }
    }
    public Matrix getProjectionMatrix() {
        createProjectionMatrix();
        return projection;
    }
    private void createViewingMatrix() {
        if(needsNewViewing) {
            viewing = Matrix.makeTranslation(position);
            viewing = viewing.times(Matrix.makeRotation(orientation.getZ(), Axis.Z));
            viewing = viewing.times(Matrix.makeRotation(orientation.getY(), Axis.Y));
            viewing = viewing.times(Matrix.makeRotation(orientation.getX(), Axis.X));
            needsNewViewing = false;
        }
    }
    public Matrix getViewingMatrix() {
        createViewingMatrix();
        return viewing;
    }
    private void createProjectionViewingMatrix() {
        if(needsNewProjViewing) {
            createProjectionMatrix();
            createViewingMatrix();
            projviewing = viewing.times(projection);
            needsNewProjViewing = false;
        }
    }
    public Matrix getProjectionViewingMatrix() {
        createProjectionViewingMatrix();
        return projviewing;
    }
}

This also includes a projection matrix and aspect ratio.
NB Vector and Matrix are my classes not the LWJGL ones so methods are probably different (weird that I have never even used LWJGL versions after all this time)

quew8

Just realized a far better but more complex method for my solution 2: one quaternion instead of 3 rotation matrices. Quaternions are good reading if you are prepared to be brain dead for a few weeks after reading about them for the first time.

paandar

Thank you very much for the answer, after all it was not as bad as I thought. I've seen that you first apply translation and then rotation when computing the view matrix, is this order required? Do I have to invert the operations when computing a model matrix to multiply by the view matrix? I've been trying to move the camera relative to its own axes without success, could you please give me some advice?

Thanks!!

quew8

In this, few things to remember: transforms are applied it opposite order to what you supplied. First transform is applied last and last is applied first. Second the view transforms generally want to be in the opposite order to model transforms (I think).
Don't tell anyone but I just keep changing the order until it does what I want it to. The posted code works for 6 dof camera. For translation relative to axis, I just extract the axes from the viewing matrix. In my Matrix class I have:
public Vector getFowardDirection() {
        return new Vector(data[2], data[6], data[10]);
    }
    public Vector getFowardDirectionXZ() {
        return new Vector(data[2], 0, data[10], Vector.NORMALIZE_BIT);
    }
    public Vector getBackDirection() {
        return new Vector(-data[2], -data[6], -data[10]);
    }
    public Vector getBackDirectionXZ() {
        return new Vector(-data[2], 0, -data[10], Vector.NORMALIZE_BIT);
    }
    public Vector getUpDirection() {
        return new Vector(data[1], data[5], data[9]);
    }
    public Vector getDownDirection() {
        return new Vector(-data[1], -data[5], -data[9]);
    }
    public Vector getRightDirection() {
        return new Vector(-data[0], -data[4], -data[8]);
    }
    public Vector getRightDirectionXZ() {
        return new Vector(-data[0], 0, -data[8], Vector.NORMALIZE_BIT);
    }
    public Vector getLeftDirection() {
        return new Vector(data[0], data[4], data[8]);
    }
    public Vector getLeftDirectionXZ() {
        return new Vector(data[0], 0, data[8], Vector.NORMALIZE_BIT);
    }


I can't remember whether this is column major or row major order, whichever one opengl uses by default (probably). I'm sorry for the ambiguity but I'ts late, I just got back from a wonderful play and my head is buzzing.

paandar

QuoteDon't tell anyone but I just keep changing the order until it does what I want it to.

Don't tell You either because I do the same! ;D

Well, I figured out that in order to achieve what I need, translation transformation must be negative for the view and positive for a model, and rotation transformation must be in inverse order for the view (I mean the order in which matrices are multiplied together).
I compute the camera's direction vector and that's all I need to move it in 3d space, which is used along with the pitch and yaw values (that I keep apart) to compute the view matrix for loading.

So everything is working now!
Thank You very much.