As we saw in the last chapter we couldn't view our model without first setting
up a view, and it would have rendered all black if we didn't have any lights.
In this chapter we will create a Camera class to control the view, and also
a Light Manager class to simplify creating and keeping track of multiple lights.
Topics Covered in this Chapter:
Creating A Camera Class
Creating A Light Class
Using our Camera and Light Classes.
Camera's and lights are fairly simple to create and control. A camera consists
of 2 vectors, a position, and a target. A spotlight also consists of a position
and target vector, and a point light only requires a position. We are going
to write 2 classes, a camera class, and a light manager class. To keep it simple,
the light manager class is only going to control point lights. However with
a few simple modifications you could have it controlling directional lights
as well.
Start by taking a look at the camera class in the chapter 5 code. The first
function after the constructors, is create. Create
takes the device and a near and far clipping range for the camera. The near
and far clipping determines the depth at which the view starts and stops rendering.
The main function of create is to create a projection
matrix which sets up how the perspective is going to be calculated. We create
the projection matrix with D3DXMatrixPerspectiveFovLH.
This requires the angle of the field of view, (in the source below it is set
to D3DX_PI / 4.0f, IE PI/4 or 90 degrees), The aspect ratio of the view (for
simplicity I have set it to 4/3 which is the standard monitor ratio), and the
near and far clipping discussed above. Once we have created our projection matrix,
we use SetTransform, with D3DTS_PROJECTION to set
the projection.
bool Camera::create(LPDIRECT3DDEVICE9 device,
float nearView, float farView)
{
myDevice = device;
nearClip = nearView;//nearest point at which the objects stop rendering
farClip = farView;//farthest point at which the objects stop rendering
//the position of our camera
position.x = 0.0f;
position.y = 0.0f;
position.z = 0.0f;
//the lookat target of our camera
target.x = 0.0f;
target.y = 0.0f;
target.z = 0.0f;
float aspect = 1.333f; // the aspect ratio of the screen
//Setup the Projection Matrix
D3DXMatrixPerspectiveFovLH(&projectionMatrix, D3DX_PI / 4.0f, aspect,
nearClip, farClip);
//Set the Projection
myDevice->SetTransform(D3DTS_PROJECTION, &projectionMatrix);
return true;
}
The next 2 functions are setPostion, and setTarget.
Both take a vector to set the position in world space at where the camera is
located at, or the position it is pointing at. Once the position or target is
set, resetView is called to update the view on
the device.
void Camera::setPosition(D3DXVECTOR3 newPosition)
{
position = newPosition;
resetView();
}
The last function in the Camera class is resetView.
resetView sets up the view matrix with a call to
D3DXMatrixLookAtLH, and then sets it with SetTransform,
and D3DTS_VIEW. The view matrix basically tells the device which direction it
is looking in, and which direction is up.
void Camera::resetView(){
//Setup the View
D3DXMatrixLookAtLH(&viewMatrix,
&position, //camera's position
&target, //camera's target
&D3DXVECTOR3(0.0f, 1.0f, 0.0f));// the up direction
//Set the View
myDevice->SetTransform(D3DTS_VIEW, &viewMatrix);
}
Next we look at the LightManager class. The LightManager class is just
about as simple as the Camera class, the only complication being that we are
going to allow for multiple lights, and store them using a standard vector storage
class. The vector class is nice because we can push and pull objects on and
off the vector and the memory is managed for us. Mallocing memory to dynamically
add objects to arrays is not really something I enjoy. However the vector storage
class is not to be confused with the D3DXVECTOR, which I have been referring
to as a vector which is just points in space; hopefully the context will tell
you which one I am talking about. Anyway, as you saw in the previous chapter,
lights have half a dozen important properties we need to set when we create
them. First, we have the color values, for Diffuse (basic color), Specular (highlight
color) and Ambient properties, then the position and range and attenuation (how
the light changes over distance) properties. The LightManager has functions
to set all of these properties, as well as manage as many lights as we want.
Generally each light adds a performance hit, so I wouldn't recommend more than
a couple.
I have setup the constructor in the LightManager to take and save its own pointer
to the device so that it isn't needed every time you make a call to the light
manager. The constructor also turns lighting on in the render state.
LightManager::LightManager(LPDIRECT3DDEVICE9
device){
//save the device for future use
myDevice = device;
//Turn on lighting
myDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
}
createLight is the next function, and it
sets up a new point light and sets default values for the color values as well
as position, range and attenuation. The newly created light, is pushed onto
our lights vector, and then SetLight, and LightEnable
are called to set the light on the device, and then turn it on. Although SetLight
looks like it is being called with something rather complicated it is just the
index of the light, and the light pointer.The index happens to be the size of
the vector - 1, and the pointer I am getting back out of the vector and needs
to be cast as type D3DLIGHT9.
int LightManager::createLight(){
D3DLIGHT9 *newLight = new D3DLIGHT9;
newLight->Type = D3DLIGHT_POINT;
//Create a new light, with some default settings, full diffuse and specular
with no ambient.
newLight->Diffuse.r = newLight->Diffuse.g = newLight->Diffuse.b =
1.0f;
newLight->Specular.r = newLight->Specular.g = newLight->Specular.b
= 1.0f;
newLight->Ambient.r = newLight->Ambient.g = newLight->Ambient.b =
0.0f;
newLight->Position = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
newLight->Range = 60.0f;
newLight->Attenuation0 = 1.0f;
newLight->Attenuation1 = 0.0f;
newLight->Attenuation2 = 0.0f;
//push the light onto our light vector
lights.push_back((D3DLIGHT9*) newLight);
//set and enable the light
myDevice->SetLight((int)lights.size()-1, (D3DLIGHT9 *)lights[((int)lights.size()-1)]);
myDevice->LightEnable((int)lights.size()-1, true);
return ((int)lights.size() - 1);
}
The next 2 functions in the LightManger are enableLight,
and disableLight. Both take the number of the light
you wish to enable or disable, and call LightEnable
with true or false respectively to turn the light on or off.
The next 3 functions are setDiffuse, setSpecular,
and setAmbient, all three take the same parameters
(light number and red/green/blue value) and set their respective color values.
void LightManager::setDiffuse(int lightNumber,
float r, float g, float b){
lights[lightNumber]->Diffuse.r = r;
lights[lightNumber]->Diffuse.g = g;
lights[lightNumber]->Diffuse.b = b;
//after we change the light we need to reset it.
myDevice->SetLight(lightNumber, (D3DLIGHT9 *)lights[lightNumber]);
}
void LightManager::setSpecular(int lightNumber, float r, float g, float b){
lights[lightNumber]->Specular.r = r;
lights[lightNumber]->Specular.g = g;
lights[lightNumber]->Specular.b = b;
//after we change the light we need to reset it.
myDevice->SetLight(lightNumber, (D3DLIGHT9 *)lights[lightNumber]);
}
void LightManager::setAmbient(int lightNumber, float r, float g, float b){
lights[lightNumber]->Ambient.r = r;
lights[lightNumber]->Ambient.g = g;
lights[lightNumber]->Ambient.b = b;
//after we change the light we need to reset it.
myDevice->SetLight(lightNumber, (D3DLIGHT9 *)lights[lightNumber]);
}
The last 2 functions in the light manager are setPosition,
and setRange. setPosition
takes a position vector to specify the x,y,z position of the light in world
space. setRange sets the range of the light (objects
outside the range of the light do not get lit).
void LightManager::setPosition(int lightNumber,
D3DXVECTOR3 newPosition){
lights[lightNumber]->Position = newPosition;
//after we change the light we need to reset it.
myDevice->SetLight(lightNumber, (D3DLIGHT9 *)lights[lightNumber]);
}
void LightManager::setRange(int lightNumber, float newRange){
lights[lightNumber]->Range = newRange;
//after we change the light we need to reset it.
myDevice->SetLight(lightNumber, (D3DLIGHT9 *)lights[lightNumber]);
}
We now have a Camera class, and a Light Manager class. To test both of
them, we are going to replace the call we made to quickAndDirtyViewSetup
in the last chapter with a camera object, and a couple of lights managed by
our LightManger.
We start by adding the classes to the GameMain header, and defining a Camera
and LightManager object.
#pragma once
class dxMgr;
class dxText;
class Surface;
class Model; class LightManager;
class Camera; ....
Last we remove the call to quickAndDirtyViewSetup,
and add calls to create and set a Camera, and a Light Manager to the init function
in GameMain. I have called create light 3 times and have set lights 2 and 3
to the left and right of the center. Light 1 remains at the default start location,
which happens to be the center of our scene.
bool GameMain::init(HWND wndHandle)
{
....
//Create a new Camera
camera = new Camera();
camera->create(dxManager->getD3DDevice(), 1.0f, 1000.f);
camera->setLookAt(D3DXVECTOR3(0.0f,0.0f,0.0f));
camera->setPosition(D3DXVECTOR3(0.0f,0.0f,50.0f));;
//Create a light manager
lights = new LightManager(dxManager->getD3DDevice());
int light_one = lights->createLight();
int light_two = lights->createLight();
int light_three = lights->createLight();
lights->setPosition(light_two, D3DXVECTOR3(25.0f,0.0f,25.0f)); //set the
light 25 units in the x, and 25 in the z
lights->setPosition(light_three, D3DXVECTOR3(-25.0f,0.0f,5.0f)); //set
the light -25 in the x, and 25 in the z
return true;
}
Thats all we need to do our test. Compiling and running the chapter 5
code shows us the 2 spheres from chapter 4, now lit by 3 lights, and viewing
for farther back.
Summing Up - Chapter 5
In this chapter we created a Camera, and a Light Manager Class. Both of which
were not overly complicated, but will definitely be handy when we start programming
an actual game. Before moving on I recommend playing with some of the light
settings, such as colors and attenuation to get a feel for how they work. If
you feel up to it try adding directional light support to the light manager,
that's a light of type D3DLIGHT_DIRECTIONAL, that has an additional parameter
vector of direction.