Advertisement

Having trouble with view matrix and camera direction

Started by November 01, 2016 07:45 PM
1 comment, last by Acar 8 years, 1 month ago

Hi. I'm having some trouble with view matrix and forward direction of the camera. With below code I get the correct forward vector so long as I'm facing towards negative z axis. But once I'm facing positive z axis (x,y) of forward starts getting incorrect.


Mat4f createViewMat(Vec3f *eye, float pitch, float yaw)
{
             Mat4f rot = matRot4f(-pitch, -yaw, 0.f); // returns a rotation matrix
             Mat4f tra = { 1.f, 0.f, 0.f, -eye->x,  // I use row-major matrices
                          0.f, 1.f, 0.f, -eye->y,
                          0.f, 0.f, 1.f, -eye->z,
                          0.f, 0.f, 0.f, 1.f };
             return matMult4m(&rot, &tra);
}
void updateCamera(Vec3f *eye, float pitch, float yaw, Mat4f *view_mat, Vec3f *forward)
{
             Mat4f rot;
             Vec3f dir;
             pitch = min(pitch, 90.f);
             pitch = max(pitch, -90.f);
             yaw = (yaw < 0.f) ? (yaw + 360.f) : ((yaw > 360.f) ? (yaw - 360.f) : yaw);
             *view_mat = createViewMat(eye, pitch, yaw);
             rot = matRot4f(pitch, yaw, 0.f);
             dir = vec3f(0.f, 0.f, -1.f);
             *forward = matMult4v(&rot, &dir);
}

I've already spent over 10 hours trying to get it right but couldn't manage to do it so far. My question is, is there a more clear and certain way of generating view matrix and camera directions?

For starters, the view matrix is always inverted. So, operations on it are always backwards, which can be confusing if you don't know that.

The first question is, "What language (API) are we in?" This code looks very familiar and yet very foreign to me at the same time.

If you want to rebuild the view matrix every frame, use the LookAt() method. Pretty much every framework out there has one.

Anytime I see the words "pitch", "yaw", and "roll" I tend to cringe. There are some functions that use them very well, but when I start seeing it all over the code, I know you're going to have serious gimbal lock problems.

For OpenGL, I setup my camera like this
"View = glm::lookAt(glm::vec3(0.0f, CameraHeight, 2.0f), glm::vec3(0.0f, CameraHeight, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); //1.63 meters is roughly the height of the average man's eyes.""

I update it like this (Which out of the 3 examples I'm posting here is the one I wrote when I had the greatest number of years of experience):
bool Game::Initialize()
{
	bool GameObjectInitializedProperly = true;		//Must be set to true to keep the program from closing.
	

	CameraHeight = 1.68f;		//Roughly the average eye height in meters of an average man. Our camera will stay at this level to make it feel like we are in the scene.
	CameraTilt = 0.0f;			//Will tilt the camera up and down.


	View = glm::lookAt(glm::vec3(0.0f, CameraHeight, 2.0f), glm::vec3(0.0f, CameraHeight, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));	//1.63 meters is roughly the height of the average man's eyes.
	Projection = glm::perspective(0.96f, OperatingSystem.AspectRatio(), 0.1f, 700.0f);	//0.96 is 55 degrees and 1.7708333 is the width to height ratio on my computer.

	return GameObjectInitializedProperly;
}
  
void Game::Update()
{
	const float MaxTiltAngle = glm::radians(45.0);


        //Camera Controls with keyboard.
        if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_W && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
            View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, 0.05f)) * View;
        if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_S && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
            View = glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, -0.05f)) * View;
        if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_E && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
            CameraTilt += 0.1;
        if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_Q && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
            CameraTilt -= 0.1;
        if (OperatingSystem.Keyboard.ModePressed == GLFW_MOD_SHIFT)
        {
            //Keys while Shift keys are also held down.
            if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_A && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
                View = glm::translate(glm::mat4(), glm::vec3(0.1f, 0.0f, 0.0f)) * View;
            if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_D && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
                View = glm::translate(glm::mat4(), glm::vec3(-0.1f, 0.0f, 0.0f)) * View;
        }
        else
        {
            //Keys when shift keys are not being held down.
            if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_D && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
            View = glm::rotate(glm::mat4(1.0f), 0.05f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
            if (OperatingSystem.Keyboard.KeyPressed == GLFW_KEY_A && OperatingSystem.Keyboard.ActionPressed != GLFW_RELEASE)
            View = glm::rotate(glm::mat4(1.0f), -0.05f, glm::vec3(0.0f, 1.0f, 0.0f)) * View;
        }

		if (CameraTilt > MaxTiltAngle) CameraTilt = MaxTiltAngle;
		if (CameraTilt < -MaxTiltAngle) CameraTilt = -MaxTiltAngle;

}

void Game::Draw()
{
	glm::mat4 TiltedView = glm::rotate(glm::mat4(), CameraTilt, glm::vec3(1.0, 0.0, 0.0)) * View;


	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
	Triangle.Draw(TiltedView, Projection, &Shader, DiffuseLightDirection, AmbientLightColor, DiffuseLightColor);
	Cube.Draw(TiltedView, Projection, &Shader, DiffuseLightDirection, AmbientLightColor, DiffuseLightColor);
	Ground.Draw(TiltedView, Projection, &Shader, DiffuseLightDirection, AmbientLightColor, DiffuseLightColor);
}



For DX11, I update it every frame (which is a method I've somewhat fallen out of favor with) like so:
    XMVECTOR LookAtVector;                                        //Position for camera to look at.
    XMVECTOR CameraLocation;                                    //Where the camera is positioned.
    XMVECTOR CameraMovement = XMVectorZero();                    //The change in position this frame as a 3D vector.


if (Keyboard.KeyPressed(DIK_LSHIFT))    //If these keys are pressed while the left shift key is pressed...
    {
        if (Keyboard.KeyPressed(DIK_D)) CameraMovement += XMVector4Transform(CameraFacingNormal,XMMatrixRotationZ(XM_PIDIV2));    //Move right. Pi over 2 is 90 degrees.
        if (Keyboard.KeyPressed(DIK_A)) CameraMovement += XMVector4Transform(CameraFacingNormal,XMMatrixRotationZ(-XM_PIDIV2));    //Move left.
    }
    else
    {
        if (Keyboard.KeyPressed(DIK_ESCAPE)) PostQuitMessage(0);    //Stop program if escape is pressed.
        if (Keyboard.KeyPressed(DIK_W)) CameraMovement = CameraFacingNormal;        //Move forward.
        if (Keyboard.KeyPressed(DIK_S)) CameraMovement = -CameraFacingNormal;        //Move backward.
        if (Keyboard.KeyPressed(DIK_D))
        {
            CameraFacingNormal = XMVector4Transform(CameraFacingNormal, XMMatrixRotationZ(0.0010f * TimeDelta));    //Spin right.
        }
        if (Keyboard.KeyPressed(DIK_A)) CameraFacingNormal = XMVector4Transform(CameraFacingNormal,XMMatrixRotationZ(-0.0010f * TimeDelta));//Spin left.
        if (Keyboard.KeyPressed(DIK_E)) CameraTilt += (-0.001f * TimeDelta);    //Look up.
        if (Keyboard.KeyPressed(DIK_Q)) CameraTilt += (0.001f * TimeDelta);        //Look down.
        if (Keyboard.KeyPressed(DIK_I)) YellowCube.Transform(XMMatrixTranslation(0.0f, 0.0f, 0.01f));        //Move the Yellow Cube forward down the Z axis.
        if (Keyboard.KeyPressed(DIK_K)) YellowCube.Transform(XMMatrixTranslation(0.0f, 0.0f, -0.01f));        //Move the Yellow Cube backward down the Z axis.
    }

   CameraPosition += CameraMovement * TimeDelta * 0.002f;    //Add the camera movement this frame scaled up or down by the amount of time that has passed since the last frame and a scale number you can change to the current camera position to give a new position.

    CameraLocation = XMVectorSet(XMVectorGetX(CameraPosition), CameraHeight, XMVectorGetY(CameraPosition), 1.0f);    //Throw away the height value of the CameraPosition and use the CameraHeight for the Y value.
    LookAtVector = CameraLocation + XMVectorSet(XMVectorGetX(CameraFacingNormal), 0.0f, XMVectorGetY(CameraFacingNormal), 1.0f);    //Camera should look at a spot that is in the direction of CameraFacingNormal (ignoring any Y value) and the distance in front of the camera position that is the length of CameraFacingNormal.
    
    View = XMMatrixLookAtRH(CameraLocation, LookAtVector, XMVectorSet(0.0f, 1.0f, 0.0f, 1.0f));    //Create a right handed LookAt matrix for our view matrix putting the camera at CameraLocation looking towards the LookAt point and using the cheat for the Up direction.
    if (CameraTilt > XM_PIDIV2) CameraTilt = XM_PIDIV2;        //Don't allow the camera to tilt beyond 90 degrees upward.
    if (CameraTilt < -XM_PIDIV2) CameraTilt = -XM_PIDIV2;    //Don't allow the camera to tilt beyond 90 degrees downward.
    View = View * XMMatrixRotationX(CameraTilt);            //Apply camera tilt.
 
The OGL code is a lot more efficient and elegant, but I had a couple years more experience by the time I wrote the OGL code. The way I write this code has evolved significantly over the years as I've learned more. I started out writing it heavily based on vectors (as evidenced somewhat in the DX code, but actually probably even originally more so) and then moved to using matrices for everything unless otherwise needed. In OGL, I managed to limit the pitch movement by keeping my camera and view matrix separate and building the view matrix last minute before drawing using the two. I called the view matrix I actually draw with "TiltedView" to include the limited pitch of the camera. And the code looks like this: "glm::mat4 TiltedView = glm::rotate(glm::mat4(), CameraTilt, glm::vec3(1.0, 0.0, 0.0)) * View;" I might also mention that I have not gotten my game timer really implemented with OGL yet, and so I did not include that in the math, although it really should be there.


In XNA the code looked something like this:
   Matrix ProjectionMatrix;    //Camera's conversion formula to project onto a 2D screen (your computer monitor).
        Matrix ViewMatrix;  //The camera. Passed to all drawable game components so that they can draw with the same camera.
        Vector2 CameraPosition; //2D position of the camera on the 2D X,Z plane. (Converted to 3D using terrain altitude.)
        Vector2 CameraFacing;   //2D arrow representing the direction the camera is facing on the X,Z plane.
        float CameraPitch;  //Used to look up or down.
        TexturedGridComponent TheGrid;  //The terrain grid object.
        CameraMatricesInterface[] CameraUpdateArray;    //Array used to call all drawable game components.
        const float CameraRotationSpeed = 0.01f;    //Radians per frame that the camera will rotate.
        const float CameraMoveSpeed = 0.11f;     //Units/meters per frame that the camera will travel at.
        const float MaxCameraPitchAngleInRadians = MathHelper.PiOver4;  //Amount that the camera can look up. (Also, amount it can look down.)

//Camera controls.
            if (KB.IsKeyDown(Keys.E))   //Look up.
            {
                CameraPitch += CameraRotationSpeed;
                if (CameraPitch > MaxCameraPitchAngleInRadians) CameraPitch = MaxCameraPitchAngleInRadians;
            }
            if (KB.IsKeyDown(Keys.Q))   //Look down.
            {
                CameraPitch -= CameraRotationSpeed;
                if (CameraPitch < -MaxCameraPitchAngleInRadians) CameraPitch = -MaxCameraPitchAngleInRadians;
            }
            if (KB.IsKeyDown(Keys.W))   //Forward.
            {
                CameraPosition += CameraFacing * CameraMoveSpeed;
                FootStepsShouldPlay = true;
            }
            if (KB.IsKeyDown(Keys.S))   //Backwards.
            {
                CameraPosition += CameraFacing * -CameraMoveSpeed;
                FootStepsShouldPlay = true;
            }
            if (KB.IsKeyDown(Keys.D)) CameraFacing = Vector2.Transform(CameraFacing, Matrix.CreateRotationZ(CameraRotationSpeed));
            if (KB.IsKeyDown(Keys.A)) CameraFacing = Vector2.Transform(CameraFacing, Matrix.CreateRotationZ(-CameraRotationSpeed));
            if (KB.IsKeyDown(Keys.C))
            {
                CameraPosition += new Vector2(-CameraFacing.Y, CameraFacing.X) * CameraMoveSpeed;    //Vector "trick". With vectors, when you switch X and Y, and negate Y, you get a vector 90 degrees clockwise to the first.
                FootStepsShouldPlay = true;
            }
            if (KB.IsKeyDown(Keys.Z))
            {
                CameraPosition += new Vector2(CameraFacing.Y, -CameraFacing.X) * CameraMoveSpeed;    //Vector "trick". With vectors, When you switch X and Y, and negate X, you get a vector 90 degrees counter clockwise to the first.
                FootStepsShouldPlay = true;
            }

  Camera3DPosition = new Vector3(CameraPosition.X, HeightOfTerrain + 1.65f, CameraPosition.Y);    //Convert the 2D camera position to 3D since it's really in a 3D world.
            CameraLooksTowards = new Vector3(CameraFacing.X, 0f, CameraFacing.Y);   //The camera's facing is also maintained in 2D and must be converted to 3D to actually use.
            CameraLooksTowards = Camera3DPosition + CameraLooksTowards; //CreateLookAt's "LookAt" parameter is a point in 3D space. We instead want it to be a point relative to Camera3DPosition. In other words, "look from the camera to this point". Try it without this line and you'll see; it will look from the feet rather than from the eyes without this.
            ViewMatrix = Matrix.CreateLookAt(Camera3DPosition, CameraLooksTowards, Vector3.Up);     //Set the camera. Up is "kinda" a cheat.
            ViewMatrix = ViewMatrix * Matrix.Invert(Matrix.CreateRotationX(CameraPitch));   //Do a rotation to simulate the camera, or user's head, looking up or down.
 
The LookAt() function is probably mostly what you're looking for though.
Advertisement

Much appreciated for the detailed answer. Looking at your examples, I realised I've been going at it the wrong way. Now that I've removed pitch and yaw variables and apply rotation/transformation whenever a new input is given, I no longer have the issue and all works well. Thanks!

This topic is closed to new replies.

Advertisement