I did not have any OGL code ready to play with this on and I knew I was going to have to write the code in order to make certain it worked. So, I modified a MonoGame program to test this. But this is just pure math, so it doesn’t really matter what you use to code it.
I have a strong preference for using matrices over vectors or quaternions when ever possible. I did an entire video on gimbal lock showing why storing orientation as pitch, yaw, and roll is bad and how quaternions don’t actually avoid gimbal lock in and of themselves. The main reason I’m such an advocate of using matrices is because that’s what the shader is going to want to be fed when you’re done in the form of the worldviewprojection matrix. I figure, the more I can stick with matrices the less work my code has to do.
Anyway, regard this as pseudo-code. I’m mostly just pointing out the math here.
But I’ve heard a couple recent questions on how to implement a 3rd person chase camera. And I wanted to see if I could do it without using a LookAt function. I use the LookAt function to start the camera looking in the right direction, but it’s not called each frame, just once at startup.
ChaseCamera = Matrix.CreateLookAt(new Vector3(0f, 2f, 5f) , Vector3.Zero, Vector3.Up);
Then it’s all reacting to keyboard commands and building a view matrix out of it.
if (KBState.IsKeyDown(Keys.Left)) ChaseCamera = Matrix.CreateRotationY(0.01f) * ChaseCamera; //Orbit horizontally.
if (KBState.IsKeyDown(Keys.Right)) ChaseCamera = Matrix.CreateRotationY(-0.01f) * ChaseCamera;
if (KBState.IsKeyDown(Keys.Up)) ChaseCamera = ChaseCamera * Matrix.CreateTranslation(new Vector3(0f, 0f, 0.01f)); //Zoom
if (KBState.IsKeyDown(Keys.Down)) ChaseCamera = ChaseCamera * Matrix.CreateTranslation(new Vector3(0f, 0f, -0.01f));
if (KBState.IsKeyDown(Keys.Home)) ChaseCamera = ChaseCamera * Matrix.CreateRotationX(0.01f); //Pitch camera (Similar to Left/Right but the multiplication order means it rotates on its local axis rather than the object's global axis)
if (KBState.IsKeyDown(Keys.End)) ChaseCamera = ChaseCamera * Matrix.CreateRotationX(-0.01f);
if (KBState.IsKeyDown(Keys.PageUp)) ChaseCamera = Matrix.CreateTranslation(new Vector3(0f, -0.01f, 0f)) * ChaseCamera; //Raise and lower the camera.
if (KBState.IsKeyDown(Keys.PageDown)) ChaseCamera = Matrix.CreateTranslation(new Vector3(0f, 0.01f, 0f)) * ChaseCamera;
ViewMatrix = Matrix.Invert(YellowCubesWorldMatrix) * ChaseCamera;
YellowCubesWorldMatrix is the world matrix for the object my camera is chasing. By multiplying the object’s matrix times the camera matrix, I’m making the camera a child of (relative to) the object. So, it will follow the object everywhere. If you move the main object (yellow cube in this case) by changing it’s matrix, the camera will remain in the same place relative to the object. So, if the object moves, the camera will stay attached to it through the movement.
The keyboard controls are just to move the camera. You could set it once and forget about it and then this would just be one line of code at initialization to set the camera position relative to the object and the line that sets the view matrix. All the other code is allowing you to move the camera. It allows the camera to orbit the object around the up/down axis and the left/right axis.
I was afraid I was going to have to make the camera look back at the object after the rotation, but in its orbit it stays facing the origin, which in this case is the center of my yellow cube object. I also added code to move the camera up and down, which does cause a problem with camera facing, but I fixed this by allowing the Home and End keys to rotate the camera to pitch up and down, allowing the user to decide how much pitch up or pitch down they want from the camera.
The arrow keys allow the camera to zoom in and out and orbit the object horizontally.
I’m sure glm has functions to do all of this and this could be rewritten pretty easily for glm.
There’s no error checking in this code. So, it allows you to zoom in until you are on the other side of the object and then things go wonky (that’s a technical term, I’m pretty sure). But in what limited testing I did with it, it seems to be working pretty well. I have to play with the multiplication order myself to get it to work right. One way is the local axis and the other is the global axis and I never have been able to keep straight which is which. But assuming *= can cause problems if the multiplication order is not right.
EDIT: I went ahead and added code to keep the camera from zooming in too close or to the opposite side. The camera is in the negative Z locally starting out. This just says to not let it move closer to the origin than -4Z. It appears to work even when you rotate around to the opposite side.
if(ChaseCamera.Translation.Z <= -4f) ChaseCamera = ChaseCamera * Matrix.CreateTranslation(new Vector3(0f, 0f, 0.01f));
I don’t see where GLM has a function to get the position from a matrix, but it shouldn’t be that difficult to extract. Maybe with GLM_GTC_matrix_access? MonoGame makes it very easy to decompose a matrix. But this is just the z axis positional value. That should be one cell in the matrix although you may have to take into account scale, I’m not sure.
You’ll notice I inverted the object’s world matrix before combining it with the camera matrix. The view matrix is an inverted matrix. Other than that it can be treated just like a world matrix. But the inversion intentionally does everything backwards. You have to invert the object’s matrix to get one that will work correctly as a view matrix. I think you probably have to do the same with the translations and rotations, but if you say rotate right and get the wrong results you just flip the positive to negative and voila you have the results you want without doing an inverted matrix.
The entire implementation of a chase camera boils down to
ViewMatrix = Matrix.Invert(YellowCubesWorldMatrix) * ChaseCamera;
I’ve been known to throw in additional matrices as “boom arms”. For example, what if I don’t want it to be relative to yellow cube’s center but rather its top? I could throw in another matrix as an offset or mounting point on yellow cube. The child is relative to the parent. You can have any number of these relationships in the chain so that you have great-great-great-great-grandchildren. Children can be moved independent of their parents, but children always follow the parent. If I were doing a chase camera on a humanoid, I might have an offset matrix. But in that case the model probably has bone matrices including a head bone and you could just make the camera a child of that head bone.