CU_MDX_Camera.zip (736.9 KB, 2,411 hits)
CUnitFramework.zip (101.1 KB, 12,576 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;
/// <summary>Creates a new Camera</summary>
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();
}
/// <summary>Creates the projection matrix.</summary>
/// <param name=”fov”>Field of view</param>
/// <param name=”aspect”>Aspect ratio</param>
/// <param name=”near”>Near plane</param>
/// <param name=”far”>Far plane</param>
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 );
}
/// <summary>Moves the camera forward and backward.</summary>
/// <param name=”units”>Amount to move</param>
public void MoveForward( float units ) 
{
}
else
{
moveVector.Normalize();
moveVector *= units;
m_velocity += moveVector;
}
}
/// <summary>Moves the camera left and right.</summary>
/// <param name=”units”>Amount to move</param>
public void Strafe( float units ) 
}
/// <summary>Moves the camera up and down.</summary>
/// <param name=”units”>Amount to move.</param>
public void MoveUp( float units ) 
}
/// <summary>Yaw the camera around its Y-axis.</summary>
/// <param name=”radians”>Radians to yaw.</param>
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 );
}
/// <summary>Pitch the camera around its X-axis.</summary>
/// <param name=”radians”>Radians to pitch.</param>
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 );
}
/// <summary>Roll the camera around its Z-axis.</summary>
/// <param name=”radians”>Radians to roll.</param>
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 );
}
/// <summary>Updates the camera and creates a new view matrix.</summary>
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 );
}
/// <summary>
/// Build the view frustum planes using the current view/projection matrices
/// </summary>
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++ )
{
}
}
/// <summary>Checks whether a sphere is inside the camera’s view frustum.</summary>
/// <param name=”position”>Position of the sphere.</param>
/// <param name=”radius”>Radius of the sphere.</param>
/// <returns>true if the sphere is in the frustum, false otherwise</returns>
public bool SphereInFrustum( Vector4 position, float radius ) 
{
{
return false;
}
}
return true;
}
/// <summary>Gets the view matrix</summary>
public Matrix View 
}
/// <summary>Gets the projection matrix</summary>
public Matrix Projection 
}
/// <summary>Gets and sets the position of the camera</summary>
public Vector3 Position 
set { m_position = value; }
}
/// <summary>Gets and sets the position of the camera</summary>
public Vector3 LookAt 
set
{
m_look = Vector3.Normalize( m_lookAt - m_position );
}
}
/// <summary>Gets and sets the field of view</summary>
public float FOV 
set { CreateProjectionMatrix( value, m_aspect, m_nearPlane, m_farPlane ); }
}
/// <summary>Gets and sets the aspect ratio</summary>
public float AspectRatio 
set { CreateProjectionMatrix( m_fov, value, m_nearPlane, m_farPlane ); }
}
/// <summary>Gets and sets the near plane</summary>
public float NearPlane 
set { CreateProjectionMatrix( m_fov, m_aspect, value, m_farPlane ); }
}
/// <summary>Gets and sets the far plane </summary>
public float FarPlane 
set { CreateProjectionMatrix( m_fov, m_aspect, m_nearPlane, value ); }
}
/// <summary>Gets and sets the maximum camera velocity</summary>
public float MaxVelocity 
set { m_maxVelocity = value; }
}
/// <summary>Gets and sets whether the y-axis is inverted.</summary>
public bool InvertY 
set { m_invertY = value; }
}
/// <summary>Gets the camera’s pitch</summary>
public float CameraPitch 
}
/// <summary>Gets the camera’s yaw</summary>
public float CameraYaw 
}
/// <summary>Gets and sets the maximum pitch in radians.</summary>
public float MaxPitch 
set { m_maxPitch = value; }
}
/// <summary>Gets and sets whether the camera can move along its Y-axis.</summary>
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.