**CU_MDX_Camera.zip** (736.9 KiB, 4,073 hits)

**CUnitFramework.zip** (101.1 KiB, 20,443 hits)

inside the project (not solution) directory.

In this tutorial, we’ll learn how to move around in a 3D world. The camera style that we will implement is a classic first person shooter camera. The user will be able to move forward and backwards and strafe left and right. We’ll also add mouse look so users can look around with the mouse.

A camera is really just composed of a single matrix: an inverted world transform matrix (a.k.a. a view matrix). The view matrix controls where in 3D space to place our viewpoint and what coordinates to look at, so it makes sense that a camera can be represented by a view matrix. Why is a view matrix an inverted world transform matrix? Say you want to move to the right. Believe it or not, the camera is always positioned at the origin of a 3D coordinate system, pointing down the positive Z-axis. So, to move to the right we have to move everything in our world to the left. This has the same effect as moving a “camera” to the right. Luckily, there is a method that creates this matrix for us: Matrix.LookAtLeftHanded. All we need to pass in is a position, a view target, and an up vector.

If you look at the matrix that Matrix.LookAtLeftHanded creates, you’ll notice that it is created with 4 different vectors: a right vector, an up vector, a look vector, and a position. These vectors respectively refer to the camera’s local X-axis, local Y-axis, local Z-axis, and position in world space. Therefore, it makes sense to store these vectors separately since we transform the camera with respect to these individual vectors. For example, to look up, the up and look vectors are rotated about the right vector. To move right, we just move in the direction of the right vector. To calculate the new view matrix, we just call Matrix.LookAtLeftHanded with the stored position and up vectors along with a target vector that can be calculated by adding the look vector to the position vector.

Moving and strafing the camera is really simple since we’re storing the camera’s right, up, and look vectors. To move forward and backward, we just move in the direction of the look vector. To strafe left and right, we move in the direction of the right vector.

In order to implement mouse look properly, we need to be able to rotate around an arbitrary axis in world space as shown below. The arbitrary axes that we will rotate about are the right, up, and look vectors of the camera. The Matrix class has a method that creates this rotation matrix for us: Matrix.RotationAxis. We just tell it what vector to rotate around and how many radians to rotate and we get a rotation matrix in return. Once we have this rotation matrix, we need to rotate the camera’s local axes. We’ll also need to rotate our vectors with Vector3.TransformNormal.

To implement the camera, we will create_ a new Camera class that encapsulates all the view data, which includes a view matrix and projection matrix.

using Microsoft.DirectX;

using Microsoft.DirectX.Direct3D;

namespace CUnit

{

public class Camera

{

private Matrix m_projection;

private Vector3 m_right;

private Vector3 m_up;

private Vector3 m_look;

private Vector3 m_position;

private Vector3 m_lookAt;

private Vector3 m_velocity;

private Plane[] m_frustum;

private float m_yaw;

private float m_pitch;

private float m_maxPitch;

private float m_maxVelocity;

private float m_fov;

private float m_aspect;

private float m_nearPlane;

private float m_farPlane;

private bool m_invertY;

private bool m_enableYMovement;

///

public Camera()

m_maxPitch = Geometry.DegreeToRadian( 89.0f );

m_maxVelocity = 1.0f;

m_invertY = false;

m_enableYMovement = true;

m_position = new Vector3();

m_velocity = new Vector3();

m_look = new Vector3( 0.0f, 0.0f, 1.0f );

CreateProjectionMatrix( (float)Math.PI / 3.0f, 1.3f, 1.0f, 1000.0f );

Update();

}

///

/// Field of view

/// Aspect ratio

/// Near plane

/// Far plane

private void CreateProjectionMatrix( float fov, float aspect, float near, float far )

m_aspect = aspect;

m_nearPlane = near;

m_farPlane = far;

m_projection = Matrix.PerspectiveFieldOfViewLeftHanded( m_fov, m_aspect, m_nearPlane, m_farPlane );

}

///

/// Amount to move

public void MoveForward( float units )

{

}

else

{

moveVector.Normalize();

moveVector *= units;

m_velocity += moveVector;

}

}

///

/// Amount to move

public void Strafe( float units )

}

///

/// Amount to move.

public void MoveUp( float units )

}

///

/// Radians to yaw.

public void Yaw( float radians )

{

return;

}

Matrix rotation = Matrix.RotationAxis( m_up, radians );

m_right = Vector3.TransformNormal( m_right, rotation );

m_look = Vector3.TransformNormal( m_look, rotation );

}

///

/// Radians to pitch.

public void Pitch( float radians )

{

return;

}

radians = (m_invertY) ? -radians : radians;

m_pitch -= radians;

if ( m_pitch > m_maxPitch )

{

}

else if ( m_pitch < -m_maxPitch )

{

}

Matrix rotation = Matrix.RotationAxis( m_right, radians );

m_up = Vector3.TransformNormal( m_up, rotation );

m_look = Vector3.TransformNormal( m_look, rotation );

}

///

/// Radians to roll.

public void Roll( float radians )

{

return;

}

Matrix rotation = Matrix.RotationAxis( m_look, radians );

m_right = Vector3.TransformNormal( m_right, rotation );

m_up = Vector3.TransformNormal( m_up, rotation );

}

///

public void Update()

if ( Vector3.Length( m_velocity ) > m_maxVelocity )

{

}

// Move the camera

m_position += m_velocity;

// Could decelerate here. I’ll just stop completely.

m_velocity = new Vector3();

m_lookAt = m_position + m_look;

// Calculate the new view matrix

Vector3 up = new Vector3( 0.0f, 1.0f, 0.0f );

m_view = Matrix.LookAtLeftHanded( Position, LookAt, up );

// Calculate new view frustum

BuildViewFrustum();

// Set the camera axes from the view matrix

m_right.X = m_view.M11;

m_right.Y = m_view.M21;

m_right.Z = m_view.M31;

m_up.X = m_view.M12;

m_up.Y = m_view.M22;

m_up.Z = m_view.M32;

m_look.X = m_view.M13;

m_look.Y = m_view.M23;

m_look.Z = m_view.M33;

// Calculate yaw and pitch

float lookLengthOnXZ = (float)Math.Sqrt( m_look.Z * m_look.Z + m_look.X * m_look.X );

m_pitch = (float)Math.Atan2( m_look.Y, lookLengthOnXZ );

m_yaw = (float)Math.Atan2( m_look.X, m_look.Z );

}

///

/// Build the view frustum planes using the current view/projection matrices

///

public void BuildViewFrustum()

// Left plane

m_frustum[0].A = viewProjection.M14 + viewProjection.M11;

m_frustum[0].B = viewProjection.M24 + viewProjection.M21;

m_frustum[0].C = viewProjection.M34 + viewProjection.M31;

m_frustum[0].D = viewProjection.M44 + viewProjection.M41;

// Right plane

m_frustum[1].A = viewProjection.M14 – viewProjection.M11;

m_frustum[1].B = viewProjection.M24 – viewProjection.M21;

m_frustum[1].C = viewProjection.M34 – viewProjection.M31;

m_frustum[1].D = viewProjection.M44 – viewProjection.M41;

// Top plane

m_frustum[2].A = viewProjection.M14 – viewProjection.M12;

m_frustum[2].B = viewProjection.M24 – viewProjection.M22;

m_frustum[2].C = viewProjection.M34 – viewProjection.M32;

m_frustum[2].D = viewProjection.M44 – viewProjection.M42;

// Bottom plane

m_frustum[3].A = viewProjection.M14 + viewProjection.M12;

m_frustum[3].B = viewProjection.M24 + viewProjection.M22;

m_frustum[3].C = viewProjection.M34 + viewProjection.M32;

m_frustum[3].D = viewProjection.M44 + viewProjection.M42;

// Near plane

m_frustum[4].A = viewProjection.M13;

m_frustum[4].B = viewProjection.M23;

m_frustum[4].C = viewProjection.M33;

m_frustum[4].D = viewProjection.M43;

// Far plane

m_frustum[5].A = viewProjection.M14 – viewProjection.M13;

m_frustum[5].B = viewProjection.M24 – viewProjection.M23;

m_frustum[5].C = viewProjection.M34 – viewProjection.M33;

m_frustum[5].D = viewProjection.M44 – viewProjection.M43;

// Normalize planes

for ( int i = 0; i < 6; i++ )

{

}

}

///

/// Position of the sphere.

/// Radius of the sphere.

///

public bool SphereInFrustum( Vector4 position, float radius )

{

{

return false;

}

}

return true;

}

///

public Matrix View

}

///

public Matrix Projection

}

///

public Vector3 Position

set { m_position = value; }

}

///

public Vector3 LookAt

set

{

m_look = Vector3.Normalize( m_lookAt – m_position );

}

}

///

public float FOV

set { CreateProjectionMatrix( value, m_aspect, m_nearPlane, m_farPlane ); }

}

///

public float AspectRatio

set { CreateProjectionMatrix( m_fov, value, m_nearPlane, m_farPlane ); }

}

///

public float NearPlane

set { CreateProjectionMatrix( m_fov, m_aspect, value, m_farPlane ); }

}

///

public float FarPlane

set { CreateProjectionMatrix( m_fov, m_aspect, m_nearPlane, value ); }

}

///

public float MaxVelocity

set { m_maxVelocity = value; }

}

///

public bool InvertY

set { m_invertY = value; }

}

///

public float CameraPitch

}

///

public float CameraYaw

}

///

public float MaxPitch

set { m_maxPitch = value; }

}

///

public bool EnableYMovement

set { m_enableYMovement = value; }

}

}

}

The Camera class performs all the calculations described at the beginning of the tutorial. To move, we increase a velocity vector in the direction that we want to move. When we want to rotate the camera, we create_ the rotation matrix with Matrix.RotationAxis and then rotate the corresponding vectors with Vector3.TransformNormal. Notice that the pitch is constrained to a range that prevents the camera from looking straight up or straight down. Since we are implementing a first person camera, we don’t want to be able to rotate all the way around when we look up or down.

The view matrix is created in the Camera.Update method. To move the camera, we simply add the velocity vector to the camera’s position. To create_ the view matrix we call Matrix.LookAtLeftHanded. Notice that the up vector we pass in to this method is the world up vector (0.0f, 1.0f, 0.0f), instead of the camera’s up vector. We do this because we are implementing a first person camera where up is always (0.0f, 1.0f, 0.0f). If we were to use the camera’s up axis, we would create_ a space ship type of camera where we could roll around wherever we wanted to.

After the view matrix is created we extract the new camera axes from it and calculate the camera’s pitch and yaw. The right, up, and look vectors are respectively located in the 1st, 2nd, and 3rd columns of the view matrix. The pitch value is calculated by projecting the look vector onto the XZ-plane and then using the Math.Atan2 method to get the angle between the XZ-plane and the Y value of the camera’s look vector. The yaw is calculated by getting the angle between the X and Z values of the camera’s look vector.

Since the projection matrix also affects how we view geometry, the Camera class contains its own projection matrix, which is formed with Matrix.PerspectiveFieldOfViewLeftHanded.

The Camera class also keeps track of its view frustum, but we’ll learn about that in a later tutorial. For a better understanding of all this math, I recommend reading Real-Time Rendering.