Jump to content

  • Log In with Google      Sign In   
  • Create Account


First person camera and matrix transforms


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
20 replies to this topic

#1 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 29 January 2013 - 12:18 PM

Well that's pretty much it. I've been trying to find a resource on how to implement a 1rst person camera in OpenGL (particulary 3.3, using LWJGL bindings for Java) and I always find things that either:
 
a. Use deprecated functions or things that depend on deprecated functions (gluLookAt) or
b. Rely on some third party library that isn't available on Java (ie, GLM)
 
Currently I just have "working" the perspective projection, translations and rotations along a single arbitrary vector (taken from http://en.wikipedia.org/wiki/Rotation_matrix, "Rotation matrix from axis and angle"). The rotation I coded is plain archaic, press a to look to the left, d to the right, z up, x down, etc. And I can't figure out how to make them "stack" properly so its either look to the sides or up and down.
 
Adding to that, I'm kinda confused on how many space transformations do I need for some things. For example, say that I load a model. I have to put it in world space first, what would involve? I imagine translation, scaling and rotation. That would be my "model to world" transform i guess.
 
Then I have to transform that to view space. And that's where the first person camera kicks in and when I get lost.
 
I've seen examples using matrix stacks (either the deprecated ones or the implementation of GLM) and I can't figure out how those work nor if there is a better way than using them (I guess they got deprecated for a reason?), so I didn't understand those either.
 
So what do I need to get a camera working? And which transforms differentiate one space from another?
 
If you wan't me to post code or upload the jar to see the issues I'm having just ask.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


Sponsor:

#2 BCullis   Crossbones+   -  Reputation: 1813

Like
1Likes
Like

Posted 29 January 2013 - 01:20 PM

You've got world right: world transform scales, rotates, and places (translates) your object out of its object-space (model-space) coordinates into your world coordinates.
View happens in world-space, it simply reorients everything around the eye point and look-at direction,

Perspective projection is the matrix that converts things to screen space coordinates (with parallax, scaling according to distance from camera, etc).

 

The correct multiplication order should first apply your world transform to your model, then the view transform, then the perspective transform. 

 

...unless there's something weird about OpenGL, as I'm basing this on my experience with DirectX.  I do think the underlying math principles are the same though.


Hazard Pay :: FPS/RTS in SharpDX
DeviantArt :: Because right-brain needs love too

#3 Trienco   Crossbones+   -  Reputation: 2089

Like
1Likes
Like

Posted 29 January 2013 - 11:38 PM

Most of the "easy, convenient and beginner friendly" stuff was removed to clean up the API, because it was kind of silly to have three or more ways of doing the same thing. They are still around through the compatibility extension, though.

 

The easiest way to handle any camera to me is to treat it like any other object. Give it a transformation matrix, understand that the columns of that matrix are right/up/forward/position vectors (depending on your setup) and that to turn it into a "view matrix" you just have to invert it. Every frame you add the new transformations depending on your input and set the inverted matrix as view matrix.

 

It also means that "attaching" a camera to an object or moving it for cut scenes is literally straight "forward", compared to trying to keep the inverted matrix and apply everything "in reverse".


f@dzhttp://festini.device-zero.de

#4 Khatharr   Crossbones+   -  Reputation: 2937

Like
1Likes
Like

Posted 30 January 2013 - 02:34 AM

BCullis, on 29 Jan 2013 - 11:28, said:
BCullis, on 29 Jan 2013 - 11:28, said:
...unless there's something weird about OpenGL, as I'm basing this on my experience with DirectX. I do think the underlying math principles are the same though.

I haven't messed with OGL for a while, but unless they've made some pretty drastic changes it's the same.

MSDN has a decent article: http://msdn.microsoft.com/en-us/library/windows/desktop/bb206269(v=vs.85).aspx

Here's a really annoying video where a 5-year-old teaches the world matrix with the assistant of his middle-aged manservant. I tried finding a better one, but entering terms like 'world', 'view', 'projection' and 'matrix' into youtube means that you get flooded with thousands of new-age and conspiracy videos.



In terms of rotations, you can rotate each axis individually and then multiply the resulting rotations to get your final rotation, but beware of gimbal lock and remember that the order in which you multiply effects the final rotation. Alternatively you can use quaternion rotation.

The view matrix, as Trienco mentioned, is just the inverse of the matrix to transform the camera from the origin to its position and orientation.

The perspective matrix... (Did they really remove all of the helper functions? That seems asinine since they're used constantly and more or less everyone will just have to rewrite them....) The MSDN article goes into it if you follow the perspective link.

Edited by Khatharr, 30 January 2013 - 03:20 AM.

void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

#5 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 30 January 2013 - 09:11 AM

You've got world right: world transform scales, rotates, and places (translates) your object out of its object-space (model-space) coordinates into your world coordinates.
View happens in world-space, it simply reorients everything around the eye point and look-at direction,

Perspective projection is the matrix that converts things to screen space coordinates (with parallax, scaling according to distance from camera, etc).

 

The correct multiplication order should first apply your world transform to your model, then the view transform, then the perspective transform. 

 

...unless there's something weird about OpenGL, as I'm basing this on my experience with DirectX.  I do think the underlying math principles are the same though.

 

All right. But then, to go from world to view wouldn't require another translation (camera position), rotation(where the camera is looking at) and scaling (zoom effects)? Or I'm missing something?
 
Math is the same, I mentioned LWJGL and OGL 3.3 so no one would say "just use gluLookAt!" or "use this GLM/freeGlut handy function!".

 

Most of the "easy, convenient and beginner friendly" stuff was removed to clean up the API, because it was kind of silly to have three or more ways of doing the same thing. They are still around through the compatibility extension, though.

 

The easiest way to handle any camera to me is to treat it like any other object. Give it a transformation matrix, understand that the columns of that matrix are right/up/forward/position vectors (depending on your setup) and that to turn it into a "view matrix" you just have to invert it. Every frame you add the new transformations depending on your input and set the inverted matrix as view matrix.

 

It also means that "attaching" a camera to an object or moving it for cut scenes is literally straight "forward", compared to trying to keep the inverted matrix and apply everything "in reverse".

Ohh, I read that somewhere in Arcsynthesis book. So, a view matrix is the inverse of the various transformation matrices that place a camera in the world? So say that I'm at (30,30,0) coords in the world, looking 45º down (rotation of 45º along a (1,0,0) unit vector), I multiply those matrices, invert the result, and I have my view matrix?

 

BCullis, on 29 Jan 2013 - 11:28, said:
BCullis, on 29 Jan 2013 - 11:28, said:
...unless there's something weird about OpenGL, as I'm basing this on my experience with DirectX. I do think the underlying math principles are the same though.

I haven't messed with OGL for a while, but unless they've made some pretty drastic changes it's the same.

MSDN has a decent article: http://msdn.microsoft.com/en-us/library/windows/desktop/bb206269(v=vs.85).aspx

In terms of rotations, you can rotate each axis individually and then multiply the resulting rotations to get your final rotation, but beware of gimbal lock and remember that the order in which you multiply effects the final rotation. Alternatively you can use quaternion rotation.

The view matrix, as Trienco mentioned, is just the inverse of the matrix to transform the camera from the origin to its position and orientation.

The perspective matrix... (Did they really remove all of the helper functions? That seems asinine since they're used constantly and more or less everyone will just have to rewrite them....) The MSDN article goes into it if you follow the perspective link.

I'm reading that MSDN article right now. Too bad that the View Transform section uses functions that I don't have. There wasn't any helper function in OGL, but functions to deal with the fixed-function pipeline constructs which had matrix stacks, projection matrices, transforms, etc. Since those constructs are removed in modern OGL, I have to implement them myself.

 

There is GLM which is a C++ library that do has some helper functions and data types that mirror GLSL types for example. I'm using Java though. 

 

Anyway, I should point out that I had separate rotations for each axis working before, and those did stack. It was an awful amount of multiplying though (3 separate matrices for rotations, one for translation and the perspective). I was looking for a more straightforward way.

.

I thought that the general rotation matrix with 1 vector and 1 angle would work but it didn't quite worked as I imagined.

 

In the code I set up three unit vectors, (1,0,0), (0,1,0), (0,0,1). A 3 floats for each angle and an offset (say, +5 degrees for each update). Camera looking down Z axis by default. When you press a key that rotates, say, along Z , I send the angle (currentAngle = currentAngle + offset) and the Z unit vector.

 

That works, it rotates the world along the Z axis by currentAngle (in radians). But if I rotate again, along Y for example, the rotation will work as if the camera was looking down the Z axis again (which it isnt, because I rotated it before). So each rotation works separately.

 

Then I thought, well yeah because the unit vectors, which represent the axis of rotations, never changed. So I set it up so after a rotation, it rotates each vector by the same matrix and then normalizes the result. That didn't worked either, it still rotates as if the camera is always looking down the Z axis, but in awkward ways.

 

 

Even if it didn't worked, I couldn't recognize where I was doing my world transform and view transform. Now I think that I'm not doing any world transform since I did not need it for what I was doing. It renders the vertices as they are (it is supposed to be the terrain so (0,0) position of the terrain is (0,0) of the world), and then trying to transform them to camera space.


Edited by TheChubu, 30 January 2013 - 09:12 AM.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#6 BCullis   Crossbones+   -  Reputation: 1813

Like
1Likes
Like

Posted 30 January 2013 - 09:31 AM

All right. But then, to go from world to view wouldn't require another translation (camera position), rotation(where the camera is looking at) and scaling (zoom effects)? Or I'm missing something?

 

Yes, it does.  I'm just saying it doesn't change "spaces" when you apply the view matrix.  Like others have said, it essentially applies an inverse of the camera's position, rotation, and scale.  I use helper functions like "createLookAt", but my camera stores per-axis rotations and a world-position vector that end up being used for the calculations (world position gives the origin for the look-at vector, and the camera rotations are fed into a "CreateRotationYawPitchRoll" matrix that modifies the camera's orientation vectors (left, up, and forward)). 

 

Gah, any more parentheses there and I'd be writing Lisp o_o


Hazard Pay :: FPS/RTS in SharpDX
DeviantArt :: Because right-brain needs love too

#7 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 30 January 2013 - 10:28 AM

I see. Now it makes more sense. So, where the look-at vector comes from? It is computed from mouse input?


"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#8 Trienco   Crossbones+   -  Reputation: 2089

Like
1Likes
Like

Posted 30 January 2013 - 11:56 AM

Personally I would absolutely avoid storing angles, though it works fine for a simple FPS style camera (just make sure you always rotate around "up" first and "right" second). The simple fact is that the order in which your rotations are applied is important and completely lost if you just accumulate angles.

 

Also, pleaaaase don't abuse scaling to zoom. Zooming is a perspective effect and the result of focusing your lens to show a smaller area, ie. what you get by reducing your field of view. Scaling will scale your objects, can make them grow beyond your near plane or even behind your camera and suddenly zooming turns into a wall hack.

 

My usual camera class is using my own vector/matrix stuff, so it would be mostly useless to you, but the basic setup is that it stores a transformation matrix (4x4). All rotations and translations are directly applied and accumulated in that matrix when processing the users input. Since matrix math is neat, applying a rotation around (1,0,0) will always look up/down around your current "right", just like a translation along (1,0,0) will always strafe left/right. For the rare situations where you want to move/rotate using the world axes, you just multiply the new transformation from the other side (to visualize it, one way the rotation happens after the previous transformations, the other way it happens before... while all local axes are still aligned with the world axes).

 

Btw., your typical lookAt function will just build a transformation matrix from the passed vectors and then invert it, so it becomes extremely superfluous if you store the transformation matrix in the first place. Inverting it is simple, as long as you stay away from scaling.

 

 
viewMatrix = Matrix44(
        Transform[0], Transform[4], Transform[8], 0,
        Transform[1], Transform[5], Transform[9], 0,
        Transform[2], Transform[6], Transform[10], 0,
        -(Transform[0]*Transform[12] + Transform[1]*Transform[13] + Transform[2]*Transform[14]),
        -(Transform[4]*Transform[12] + Transform[5]*Transform[13] +    Transform[6]*Transform[14]),                        
        -(Transform[8]*Transform[12] + Transform[9]*Transform[13] + Transform[10]*Transform[14]), 1);

f@dzhttp://festini.device-zero.de

#9 BCullis   Crossbones+   -  Reputation: 1813

Like
1Likes
Like

Posted 30 January 2013 - 12:15 PM

I see. Now it makes more sense. So, where the look-at vector comes from? It is computed from mouse input?

It depends on your implementation.  I could technically say "yes" when talking about my own code, because mouse movement alters the camera's rotation, and that rotation modifies the camera's "Forward" vector, which is used together with the camera's world position to make my look-at matrix.  But the DirectX LookAt method takes an origin and a destination, and calculates the matrix that would represent a view "looking at" the destination from the origin.

 

The following is C# code, but probably explains better than I just tried to:

(_rotation is a vector representing x, y, and z-axis rotation of the camera)

public virtual void Update(float timeDelta)
        {
            Matrix rotation = Matrix.RotationYawPitchRoll(_rotation.Y, _rotation.X, 0);
            _forward = Vector3.TransformCoordinate(Vector3.UnitZ, rotation);
            _up = Vector3.TransformCoordinate(Vector3.UnitY, rotation);
            _left = Vector3.Normalize(Vector3.Cross(_forward, _up));
            _viewMatrix = Matrix.LookAtLH(_worldPosition, _worldPosition + _forward, _up);
        }

Edited by BCullis, 30 January 2013 - 12:17 PM.

Hazard Pay :: FPS/RTS in SharpDX
DeviantArt :: Because right-brain needs love too

#10 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 31 January 2013 - 06:23 AM

All right. Thank you all for your answers! I'm trying to implement it right now. If I come across another rock I'll be back :D


"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#11 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 01 February 2013 - 12:37 PM

Gah, I'm still having issues. Translation works ok but rotations work awkwardly. Like the camera isn't rotating on itself but around some point elsewhere. And it seems I can only rotate around a limited arc, like 60ºC left and 60ºC right.

 

This is my lookAt function. I use DefaultMatrixFloat for both matrices and vectors. eyePos, target and such are treated as column vectors with 3 elements.

 

 

    public static DefaultMatrixFloat lookAt ( DefaultMatrixFloat eyePos, DefaultMatrixFloat target,  DefaultMatrixFloat up)
    {
 
        DefaultMatrixFloat zAxis = UtilsMath.substractMatrix(target,eyePos);
        UtilsMath.normalize3f(zAxis);
        
        DefaultMatrixFloat xAxis = UtilsMath.crossProduct3f(up, zAxis);
        UtilsMath.normalize3f(xAxis);
        
        DefaultMatrixFloat yAxis = UtilsMath.crossProduct3f(zAxis, xAxis);
        UtilsMath.normalize3f(yAxis);
//       This is emulating GLM's lookAt function. Reverses cross products. Has the same issues.
//        DefaultMatrixFloat zAxis = UtilsMath.substractMatrix(target,eyePos);
//        UtilsMath.normalize3f(zAxis);
//        
//        DefaultMatrixFloat xAxis = UtilsMath.crossProduct3f(zAxis,up);
//        UtilsMath.normalize3f(xAxis);
//        
//        DefaultMatrixFloat yAxis = UtilsMath.crossProduct3f(xAxis,zAxis);
//        UtilsMath.normalize3f(yAxis);       
        //4x4 matrix.
        DefaultMatrixFloat     transform = new DefaultMatrixFloat(4,4);
        transform.setIdentity();
 
        //setValueAt takes (row,column,value) as parameters.
        //getValueAt takes (row,column) and returns the float in that position.
 
        transform.setValueAt(0, 0, xAxis.getValueAt(0, 0)); 
        transform.setValueAt(0, 1, yAxis.getValueAt(0, 0)); 
        transform.setValueAt(0, 2, zAxis.getValueAt(0, 0)); 
        transform.setValueAt(1, 0, xAxis.getValueAt(1, 0)); 
        transform.setValueAt(1, 1, yAxis.getValueAt(1, 0)); 
        transform.setValueAt(1, 2, zAxis.getValueAt(1, 0)); 
        transform.setValueAt(2, 0, xAxis.getValueAt(2, 0));
        transform.setValueAt(2, 1, yAxis.getValueAt(2, 0));
        transform.setValueAt(2, 2, zAxis.getValueAt(2, 0));
        transform.setValueAt(0, 3, - dotProduct3f(xAxis, eyePos));
        transform.setValueAt(1, 3, - dotProduct3f(yAxis, eyePos));
        transform.setValueAt(2, 3, - dotProduct3f(zAxis, eyePos));
//      Also tried doing the dotProduct in the last row like GLM's implementation but it warps everything.
//      transform.setValueAt(3, 0, - dotProduct3f(xAxis, eyePos));
//        transform.setValueAt(3, 1, - dotProduct3f(yAxis, eyePos));
//        transform.setValueAt(3,2, - dotProduct3f(zAxis, eyePos));
        
        return transform;
        
    }

And this is how I get input from the user:

    public void pollInput ( ZGLClass glObject )
    {
        float posOff = 5.0f;
        float viewOff = 0.2f;
        
        //eyePos,target,up are 3 element column vectors.
        //up vector is defined as (0,1,0).
        
        //Translations. Affect both eyePos and target vectors by the same offset.
        if (Keyboard.isKeyDown(Keyboard.KEY_S))
        {
            zPos += posOff;
            zView += posOff;
            this.eyePos.setValueAt(2, 0, zPos);
            this.target.setValueAt(2, 0, zView);
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_W))
        {
            zPos -= posOff;
            zView -= posOff;
            this.eyePos.setValueAt(2, 0, zPos);
            this.target.setValueAt(2, 0, zView);
        }
        //Looking right/left only modify the target vector, not the position of the eye.
        if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT))
        {
            xView += viewOff;
            this.target.setValueAt(0, 0, xView);
        }
        if (Keyboard.isKeyDown(Keyboard.KEY_LEFT))
        {
            xView -= viewOff;
            this.target.setValueAt(0, 0, xView);
        }
        //"rotationMatrix" is actually the view matrix. I just hijacked the variable for now.
        //updateViewMatrix updates the related "rottionMatrix" uniform where the viewMatrix is saved.
        glObject.rotationMatrix = UtilsMath.lookAt(eyePos, target, up);
        glObject.updateViewMatrix();

        }

There are more ifs for translations along X (xPos) and Y (yPos), and up/down rotation, they work the same. Adding an offset and setting it up on the eyePos/target vector.

 

I have no idea where I'm going wrong.


Edited by TheChubu, 01 February 2013 - 12:51 PM.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#12 Trienco   Crossbones+   -  Reputation: 2089

Like
1Likes
Like

Posted 01 February 2013 - 11:25 PM

Judging from your code the very simple reason why your rotation isn't working is because you aren't rotating at any point. Shifting the point to look at left and right has nothing to do with rotation and a very simple question should make it obvious: "how do you expect to look behind your position?"

 

Rotation involves trigonometry, either used directly (I wouldn't) or hidden away (constructing a rotation matrix). Look at the graphs for sin and cos, remember that an angle of 0° is straight to the right and you should notice how x = cos and y = sin for any given angle.


f@dzhttp://festini.device-zero.de

#13 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 02 February 2013 - 01:17 PM

Let's see. I can view up/down in a 180º arc (directly up, directly down). And I can view right/left in a 180º arc too (directly left, directly right).

 

Then what exactly handles the view matrix? Translations work fine, just displace the eyePos vector by an offset. I can look up, press W, and I will advance in that direction (sort of). While looking left/right doesn't quite works (due lack of rotation as you said), looking up/down should. And it isn't working. It works at the start, when the camera is at (0,0) world position, if I move, then looking up/down breaks.

 

 

Say that I rotate 10º generate a rotation matrix along Y with that (yaw). Then what I should rotate with that matrix? The model? The vectors?

 

Anyway I've been thinking, I don't know exactly what the view matrix should do. What should I expect if it works properly? Viewing up/down and viewing right/left by 360º ? And what happens if I turn to the left and press W (forward), I should expect the view matrix translates accordingly? (towards -y i think).


"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#14 Khatharr   Crossbones+   -  Reputation: 2937

Like
1Likes
Like

Posted 02 February 2013 - 02:38 PM

This article has an in depth look along with a sample LookAt() implementation.

 

http://3dgep.com/?p=1700


void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

#15 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 02 February 2013 - 06:44 PM

That article is where my first lookAt method came from (that's why the view direction is called "target" like the article).

 

Since that article made no mention about further rotating vertices with another rotation thought that you could change look directions by just changing the target vector, but Trineco says I also need another rotation?

 

Anyway that article doesn't mentions if I should expect fully working 1rst person camera out of the view matrix (ie, looking up/down, and 360º left/right).


"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#16 Trienco   Crossbones+   -  Reputation: 2089

Like
2Likes
Like

Posted 03 February 2013 - 01:40 AM

The problem is that by storing a target position, you are making something that is conceptually really simple a lot more messy. For one, you can't simply rotate a target position, because it would rotate around the origin instead of the camera position. You can't just linearly move it around either, as you might have noticed from things not working. So stop dealing with "which point am I looking at" and think about "which direction am I looking in".

For an fps camera, you probably want to limit your up/down rotation to +/-89°, so in your case, the otherwise ugly Euler Angles are the easiest approach. Why not 90° you might ask? Because of something many people don't quite understand about the lookAt helper function. "Up" must be roughly your local up (ie. imagine sticking a pencil in the top of your head, that's the "up" lookAt needs. Now, simply using (0,1,0) works great, until you rotate exactly +/-90° up/down (where it should be (0,0,1) or (0,0,-1)) or more than 90° (where it has to be (0,-1,0)).

So what you really need for lookAt is your position, a point to look at (or the direction to look in) and a vector pointing "up". That just happens to be the 4th, 3rd and 2nd column of the cameras transformation matrix, but we just want fps style and take some shortcuts:

 

//Don't try to use this for a flight simulator without stocking up on aspirin first
float pitch = 0;
float yaw = 0;
 
if (left) yaw -= rotationSpeed * mouseDelta;
if (right) yaw += rotationSpeed * mouseDelta;
if (up) pitch -= ...
...
while (yaw < 0) yaw += 360;
while (yaw > 360) yaw -= 360;
 
if (pitch > 89) pitch = 89;
...
 
matrix rotation = createRotationMatrix(pitch, 1,0,0) * createRotationMatrix(yaw, 0,1,0);
 
//At this point, you can either transform some default view direction or just have the camera look right by default and extract the columns of this matrix.
//For simplicity, we use lookAt
viewMatrix = lookAt(cameraPosition, cameraPosition + rotation.3rdColumn, rotation.2ndColumn);

 

Probably a lot more reusable and not really much more complicated:

 

matrix rotation;
vector translation;
 
//Note that left/right and up/down are a different order to use the global "up" and the local "right"
if (left/right) rotation = rotation * createRotation(mouseDelta, 0,1,0);
if (up/down) rotation = createRotation(mouseDelta, 1,0,0) * rotation;
 
if (forward/backward) translation += distance * rotation.3rdColumn;
if (strafeLeft/Right) translation += distance * rotation.1stColumn;
 
matrix transformation = rotation;transformation.4thColumn = translation;
viewMatrix = inverse(transformation); //Use the simple version (same as in lookAt)


You can also keep it all in one matrix, which makes translation easier, but requires extra steps when rotating around a global axis.

 

matrix transformation;
 
if (left/right) {
     vector position = transformation.4thColumn;
     transformation = transformation * createRotation(yaw, 0,1,0); //This will also rotate our position around the origin
     transformation.4thColumn = position;
}
if (up/down) transformation = createRotation(mouseDelta, 1,0,0) * transformation;  //This will rotate around our current position
 
if (movement) transformation = createTranslation(x,y,z) * transformation; //This will use local x,y,z
 
if (gettingPushed) transformation = transformation * createTranslation(x,y,z); //This will use local x,y,z
 
viewMatrix = inverse(transformation);

 

This is obviously pseudo code. Since 3D graphics is basically nothing but vector and matrix math, there simply is no way to get around learning and embracing them. The absolute minimum to start out is getting familiar with rotation and translation matrices and getting an intuitive grasp of what the components represent and what multiplication will do to them.

You might notice that the last versions will also work just fine for all your other game objects and won't fall apart when you start turning things upside down. So it's pretty much the most generic way if you stay away from scaling (or keep it separated). There is also no lookAt, because most of what that does would be completely redundant.

One thing to keep in mind is that you need to occasionally reorthonormalize the rotation/transformation matrix, as floating point errors will accumulate and start creating some trippy effects. When using lookAt, that happens automatically, since it is reconstructing the whole matrix every frame.

Eventually people will tell you how quaternions are essentially the "one true way of rotation" or even the second coming, but in the end they are completely equivalent to rotation matrices and the only important thing is to know when they are more efficient. There is however absolutely no reason to worry about them at this point (and no, they are not the "only solution to gimbal lock", that's "don't use frigging Euler angles").

 

edit: ok, great. Never edit a post with code, unless you want every single line break to disappear...


Edited by Trienco, 03 February 2013 - 01:51 AM.

f@dzhttp://festini.device-zero.de

#17 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 03 February 2013 - 05:22 PM

For an fps camera, you probably want to limit your up/down rotation to +/-89°, so in your case, the otherwise ugly Euler Angles are the easiest approach. Why not 90° you might ask? Because of something many people don't quite understand about the lookAt helper function. "Up" must be roughly your local up (ie. imagine sticking a pencil in the top of your head, that's the "up" lookAt needs. Now, simply using (0,1,0) works great, until you rotate exactly +/-90° up/down (where it should be (0,0,1) or (0,0,-1)) or more than 90° (where it has to be (0,-1,0)).

Then it locks two rotation axis? Forward vector and up vector become the same. A gimbal lock.

 

I implemented the first approach you showed (since I don't know how to do the inversions without going all crazy with cofactors).

 

This is getting frustrating but I think I'm almost there.  See, now moving works fine. If say, I look to the right and press forward, I advance in whatever direction that is instead of just along z.

 

Looking around works fine too, I can look upwards and all around. But when I move, it seems like the camera is "orbiting" the (0,0,0) coord of the world. It doesn't rotates on itself but around the world.

 

I thought I just mixed up the translation/orientation multiplication inside lookAt. The lookAt method I'm using is the one from the article Khatharr linked.

 

Changed the order of the multiplication from (translation * orientation) to (orientation * translation), which makes more sense since that's how I was doing it when I had all the matrices in the shader.

 
Aaaand looking and translating works! I can move and look around. Except for one little thing, now I can't look at a direction and advance "forward" along that direction. Instead the movement is fixed to X,Y,Z axis.
 
I'm missing something (moar rotations!) or I just messed it up again?
        

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#18 Trienco   Crossbones+   -  Reputation: 2089

Like
2Likes
Like

Posted 03 February 2013 - 11:02 PM

You already are doing the inversion in the last block of your lookAt function, when you transpose the rotation part and invert the translation part. This method only works for special cases, but unless you "break" things by adding scaling to your matrix, you have that special case.


f@dzhttp://festini.device-zero.de

#19 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 04 February 2013 - 11:24 AM

Ah I see. Everywhere I read said "and you dont have to do the inversion!" but never mentioned why (transpose the rotation, then invert translation).

 

Any idea why I can either move accordingly but rotate around world (0,0) or move always in the same directions but rotating around camera's (0,0) ? I'm very annoyed right now because it's been a week and I can't make it work.

 

There is something missing that should translate accordingly to where I'm looking at. How do you update your eye position? Just plain "if W is pressed, add 10 to Z component of eye position" ? Or there is more math involved?

 

I can't figure it out. Forward is always -Z, right is always +Y, etc, no matter where I'm looking at.


Edited by TheChubu, 04 February 2013 - 11:32 AM.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator


#20 TheChubu   Crossbones+   -  Reputation: 3973

Like
0Likes
Like

Posted 07 February 2013 - 02:21 PM

Finally got that part to work a few days ago.

 

 

    public void updateMovement ()
    {
        float deltaMovement = movementSpeed * (UtilsMisc.getTime() - timer);
        /*
         * Movement vectors.
         */
        forwardVec.setValueAt(0, 0, orientationMatrix.getValueAt(0, 2)*deltaMovement);
//        forwardVec.setValueAt(1, 0, orientationMatrix.getValueAt(1, 2)*deltaMovement);
        forwardVec.setValueAt(2, 0, - orientationMatrix.getValueAt(2, 2)*deltaMovement);
        
        rightVec.setValueAt(0, 0, orientationMatrix.getValueAt(0, 0)*deltaMovement);
//        rightVec.setValueAt(1, 0, orientationMatrix.getValueAt(1, 0)*deltaMovement);
        rightVec.setValueAt(2, 0, - orientationMatrix.getValueAt(2, 0)*deltaMovement);
        
//        upwardVec.setValueAt(0, 0, orientationMatrix.getValueAt(0, 1)*deltaMovement);
        upwardVec.setValueAt(1, 0, orientationMatrix.getValueAt(1, 1)*deltaMovement);
//        upwardVec.setValueAt(2, 0, - orientationMatrix.getValueAt(2, 1)*deltaMovement);
    }

I created unit vectors out of the orientation matrix (rotation along X * rotation along Y) so they're based on the camera coordinates instead of the world ones.

 

Now if I press W, I just add the forward vector to the camera's position and it will advance forward in whatever direction is looking at.

 

I commented out certain vectors from affecting some axes (ie, right/strafe vector can't affect the vertical/Y axis) otherwise the camera can move in some weird ways that aren't "FPS like".

 

Now I got mouse input working too, a little jerky but its ok for now.

 

Thanks for all the answers! smile.png


Edited by TheChubu, 07 February 2013 - 02:21 PM.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

 

My journals: dustArtemis ECS framework and Making a Terrain Generator





Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS