3D DirectX10 Free Look Camera (Timer based)


Introduction:

Okay so I promised I’d write a tutorial on writing a simple free look vector based camera, this tutorial doesn’t only apply to DirectX10 but to  pretty much any graphics API. We want to keep things simple initially so the simplest possible camera we can implement short of a first person style camera is a free look camera (without roll) so basically only two degrees of freedom: left/right and up/down, we are also going to implement some basic movement controls forwards/backwards and Strafe Left/Right.

Camera Basics

First things first, as usual I’m not going to go into major detail about viewing and projections, if you want more info check out: http://en.wikipedia.org/wiki/Graphical_projection and http://en.wikipedia.org/wiki/3D_projection . Okay now to view any 3D scene we need a virtual camera with a field of view (FOV- theta) and near (a) and far (b) view planes, these together form a view frustum as show in figure 1. This frustum defines what is visible in the scene, all geometry clipping and hidden surface removal is based on this frustum.

Figure 1 - view Frustum (courtesy DX10 docs)

Now the shape of this frustum is controlled by the projection that we we to employ, there are several types of projective but chances are you will end up using a perspective projection. Now the projection is attached to our virtual camera (think of the projection as a camera lens), to orient ourselves in a scene we need to know the position that we are viewing the scene from, the direction in which we’re looking and which way is up (shown in figure 2), now this up direction is very important for movement calculations and obviously to display the scene the right way up 😉  These three parameters are called the eye position, the view vector and the up vector.

Figure 2 - Virtual Camera

For a free look camera, we need the up vector to be in the direction of the camera top, if we were doing a first person camera where the viewer is stuck on the ground then the up direction will always be towards the sky. So in conclusion to view a scene, you need a virtual camera positioned somewhere in a scene (pos/up/view) with some sort of lens (projection).

Now I need to quickly explain what happens to an object’s vertices when it travels through the rendering pipeline. When a object’s vertex gets sent down the rendering pipeline it has a position in its own model space, this position is now moved to wherever the object is required to be in the world by multiplying the vertex by the world matrix for the object. Now if you run through the world you would think of it as the world standing still and the camera moving, but for now it is beneficial to think of it in the opposite way, that the camera is stationary and its the world that’s moving. Cause mathematically that is exactly whats happening.

The camera never moves, the worlds position is just adjusted to give that impression. So we’ve positioned our object in the world, the next step it move it according to our view point, if we want to looking to the left of the object, we need to move the entire world to the right, so we multiply the vertex position with the view matrix to move it as necessary. Remember that even though each object will have its own world matrix, they will all use the exact same view matrix. The last step that needs to be applied to each vertex is the projection matrix. In a real camera, the lens affect the image displayed and can possible apply distortion to the scene (e.g. a fisheye lens), our virtual lens (the projection matrix) does exactly that, the vertices get transformed into clip space  according to the view frustum we have set. After our vertices are completely transformed they are then sent to the rasterization stage of the pipeline, where they are assembled into primitives and flattened into a 2D image.

Order of vertex transformations that occur in the Vertex Shader

The Camera Body: The Container Class

Figure 4 - Camera Class

Figure 4 shows the class structure for our camera, source code is below, we have the two matrices: view and projection, we have function to position the camera, change the view and so on…

#define TWO_PI 6.283185307179586476925286766559
#define DEG_TO_RAD 0.01745329251994329576923690768489

class camera
{
	/*******************************************************************
	* Members
	********************************************************************/
private:

	//view parameters
	float heading, pitch;					//in radians

	//matrices
	D3DXMATRIX viewMatrix;
	D3DXMATRIX projectionMatrix;
	D3DXMATRIX rotationMatrix;

	//view vectors
	const D3DXVECTOR3 dV, dU;				//default view and up vectors
	D3DXVECTOR3 eye, view, up;

	//movement vectors and movement toggles
	D3DXVECTOR3 forward, strafeRight;
	int movementToggles[4];					//fwrd, bck, strfLft, strfRght

	//camera timer for movement
	HRTIMER::timer camTimer;

	/*******************************************************************
	* Methods
	********************************************************************/
public:

	//constructor and destructor
	camera();
	virtual ~camera();

	//set projection methods
	void setPerspectiveProjection( float FOV, float aspectRatio, float zNear, float zFar );

	//camera positioning methods
	void setPositionAndView( float x, float y, float z, float hDeg, float pDeg );
	void adjustHeadingPitch( float hRad, float pRad );
	void setMovementToggle( int i, int v );

	//update camera view/position
	void update();

	//get methods
	D3DXMATRIX& getViewMatrix() { return viewMatrix; }
	D3DXMATRIX& getProjectionMatrix() { return projectionMatrix; }

private:

	//create view, forward, strafe vectors from heading/pitch
	void updateView();
};

The Camera Lens: Creating the Projection Matrix

Lets do the easiest thing first, creating the camera lens or projection matrix. Almost all graphical APIs have a helper functions to create this matrix for you. To create a left handed perspective projection  in D3D10, the function you need is: D3DXMatrixPerspectiveFovLH( outputMatrix, FOV, aspectRatio, nearPlane, farPlane)… The code to create this matrix is:

void camera::setPerspectiveProjection(float FOV, float aspectRatio, float zNear, float zFar)
{
	//convert FOV from degrees to radians
	FOV = FOV * (float) DEG_TO_RAD;

	D3DXMatrixPerspectiveFovLH( &projectionMatrix, FOV, aspectRatio, zNear, zFar );
}

I’m setting the projection matrix’s FOV using a degree parameter, since I only do it once and its easier to visualize a degree based FOV its fine, the helper function requires radians, so I simply convert to radians using the constant defined in the class header file. The aspect ratio is the ratio of width:height for your display window. If your window is 640×480 then the aspect ratio is 640:480 -> 4:3 or numerically 1.3333333333…

The Camera: Creating the View Matrix

Since we are using a camera with only 2 degree of freedom (axes of movement) ->heading (left/right) and pitch (up/down),  we store the direction in which we’re facing as a angle on each axis. These angles are stored as radians. The final view vector is calculated from these two variables and original position of the view and the up vector is adjusted accordingly. This mean that to get an accurate up vector we need to initialize the camera with correct values when we create it, so that the up vector is valid once the view changes. so the constructor for the camera class is:

camera::camera():	dV( D3DXVECTOR3(0,0,1) ),
					dU( D3DXVECTOR3(0,1,0) ),
					eye( D3DXVECTOR3(0,0,0) ),
					view( D3DXVECTOR3(0,0,1) ),
					up( D3DXVECTOR3(0,1,0) ),
					forward( D3DXVECTOR3(0,0,1) ),
					strafeRight( D3DXVECTOR3(1,0,0) ),
					heading(0),
					pitch(0)
{
	//set matrices to identity
	D3DXMatrixIdentity( &viewMatrix );
	D3DXMatrixIdentity( &projectionMatrix );

	//initialize movement toggles
	movementToggles[0] = 0;
	movementToggles[1] = 0;
	movementToggles[2] = 0;
	movementToggles[3] = 0;
}

This always initializes our camera at the position (0,0,0), facing the positive z axis. *NOTE: we will discuss the movement toggles later on. We also set our default view and up vectors to (0,0,1) and (0,1,0) respectively. We will use these default vectors to calculate the new view vector from the heading and pitch parameters when the view changes. So how do we adjust the view? Well we simple modify the heading  and pitch parameters and run the updateView() function which we’ll define later. Just to be safe we add hard limits to the values of the heading and pitch (0~2pi) just so over the course of the program we don’t overflow the variables.

void camera::adjustHeadingPitch( float hRad, float pRad )
{
	heading += hRad;
	pitch += pRad;

	//value clamping - keep heading and pitch between 0 and 2 pi
	if ( heading > TWO_PI ) heading -= (float) TWO_PI;
	else if ( heading < 0 ) heading = (float) TWO_PI + heading;

	if ( pitch > TWO_PI ) pitch -= (float) TWO_PI;
	else if ( pitch < 0 ) pitch = (float) TWO_PI + pitch;
}

We also said we would be adding movement controls: forwards, backwards  and strafing. To move we will need to know which direction is forward ( hint: the view vector 😉 ) and which way is directly right, backwards and left are obviously in the direct opposite direction. We now have the two unit vectors forward and strafeRight which also need to be updated as soon as the view changes. As we said forward is easy as it is the view vector and backward is the negative forward vector. How do we calculate the right vector? Well we have the forward direction and the up direction so right should be ortogonal (90 degrees) to both of them (ie the x axis, if forward is the z axis and u is the y axes), hm, we have two vectors and need to get another vector orthogonal to them, if we think back to basic calculas and we suddenly remember that the cross product of two vectors gives us an orthogonal vector, wiki the cross product for more details. We also need to unsure that the movement vectors are unit vectors to ensure consistant movement.

To create the new view and up vectors, we first create a rotation matrix which will rotate any point/vector by the degrees on each axis that we specify, we then rotate the default view and up vectors (du & dv) to get the new view and up vectors. Easy Peasy…

We now have all the info necessary to create our updateView() function.

void camera::updateView()
{
	//create rotation matrix
	D3DXMatrixRotationYawPitchRoll( &rotationMatrix, heading, pitch, 0 );

	//create new view and up vectors
	D3DXVec3TransformCoord( &view, &dV, &rotationMatrix );
	D3DXVec3TransformCoord( &up, &dU, &rotationMatrix );

	//create new forward and strafe vectors
	D3DXVec3Normalize( &forward, &view );
	D3DXVec3Cross( &strafeRight, &up, &view );
	D3DXVec3Normalize( &strafeRight, &strafeRight );

	//take into account eye position
	view = eye + view;

	//update view matrix
	D3DXMatrixLookAtLH( &viewMatrix, &eye, &view, &up );
}

Statically Positioning our Camera

void camera::setPositionAndView(float x, float y, float z, float hDeg, float pDeg)
{
	//set eye coordinates
	eye.x = x;
	eye.y = y;
	eye.z = z;

	//set heading and pitch
	heading = hDeg * (float) DEG_TO_RAD;
	pitch = pDeg * (float) DEG_TO_RAD;

	//update view
	updateView();
}

This simply positions the camera on a location and set the view according to the heading/pitch params specified.

Moving our Camera

There are two ways to move the camera, incrementally step by by, i.e. on each update (frame) we move the camera by a fixed amount  but there is a massive problem with this, variable framerates! Imagine if we moved the camera by 1 unit every frame, on a midrange PC getting around 45 frames per second  we will be moving at 45units a second, but now comes a guy with a monster machine and runs the game at 75 frames per second and our movement speed has increased as well, this is unacceptable and would ruin any game. I dont know if any of you remember how quake3’s physics was reliant on the FPS, and so guys getting higher FPS rates could run faster and jump further than guys with lower FPS rates. So how do we correct this? We need to ensure a fixed movement rate based on time and not frames, so we do just that, we make movement timer based! We firstly add movement toggles, which get activated when a player pushes a movement key, and deactivated when he releases it, this is necessary to ensure that if a player pushes both forward and back the camera doesnt move and then when he releases a single key it immediately starts moving in the still active direction.

The to work out the distance moved between updates, we create a simple timer and check the elapsed time in seconds, we then multiply this time by the movement rate per second. Both of these functions are shown below:

/*******************************************************************
* set camera movement toggles
********************************************************************/
void camera::setMovementToggle( int i, int v )
{
	movementToggles[i] = v;
}

/*******************************************************************
* update camera view and position - timer based for fluid movement
********************************************************************/
void camera::update()
{
	//get current elapsed time
	float t = (float) camTimer.getElapsedTimeSeconds();

	//update position - 1.5 unit per second
	eye +=	 t * ( movementToggles[0] + movementToggles[1] ) * 1.5f * forward +
			 t * ( movementToggles[2] + movementToggles[3] ) * 1.5f * strafeRight;

	//update view
	updateView();

	//reset timer
	camTimer.reset();
}

This update function is what gets called from the main game loop, also the reason why updateView() is declared private. On each frame update, you will process input, set the movement toggles appropriately, run the adjustHeadingPitch() function and then run the update() function to create the new view matrix.

NOTE: The timer used is based on my silly encapsulation of the tick timers in the windows API, look at my High Resolution Timers entry.

Conclusion

So there we have it, a super simple super basic free look camera implementation in less than a 100 lines of code. In the next blog entry I will be covering how to give the camera mouse control using Windows Raw Input. With those two tutorials you will have all the tools to create a basic input system for your game!!!

Hope you enjoyed it!

Advertisement

12 thoughts on “3D DirectX10 Free Look Camera (Timer based)

  1. Hi all,

    I tried to render with some of the meshes your wrote in DX Turorial 5.

    And I was wondering how you implemented the timer base library or its function.

    Thnks.
    Renderomut

  2. Hi, again.

    I also tried to implement the change in the viewmatrix within the render function and it didn’t succeeded.

    How do I chande the view matrix or move the camera within the main loop?

    or do I change the world matrix to the opposite direction.

    thanks,
    Renderomut

  3. Hey, I tried following your tutorial and implementing what you’ve taught me into a small program; on start, the camera seems to shake and the rotation becomes uncontrollable. Do you know why this is happening? How do I fix it?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s