Color, material and light
Last updated: 17-06-2006
Project: Game Programming with DirectX 9
Prerequisites: Completion of all the preceding tutorials in this project is required.
Downloads: DirectXLight.zip
In this version of our application we are going to add the light to our world.
The source code that comes with this tutorial (see Downloads section above) extends the previous solution.
To see all the tutorials that belong to this project click on the project name above.
DirectX supports the following 3 types of light:
Point light – emits light in all directions and has position in the world. For example: light that comes from burning a candle.
Directional light – emits light in a single direction without having position in the world (position is not needed; the light is always present in the world and shines in a given direction). For example: you are lying on the beach under the direct DirectX sunlight.
Spot light - emits light through a conical shape. For example: a flash light.
Our world can include any combination of light sources from the specified above types. Selecting the light sources is only part of our job. We also have to decide what the color of each object will be. Color of an object is determined by the material which is assigned to that object.
Material is determined by 4 types of light that our object can absorb and the sharpness of specular highlights. These types of light are: diffuse, ambient, specular and emissive.
To start with we will define some colors that we can use. Notice that the colors are constants. Color is defined in RGB values (that represent the Red, Green and Blue components of the color). If you are not familiar with the RGB color system, you can use any graphical or photo editing software (like Photoshop for example) to easily obtain the RGB values of a desired color.
const D3DXCOLOR WHITE( D3DCOLOR_XRGB(255, 255, 255) );
const D3DXCOLOR BLACK( D3DCOLOR_XRGB(  0,   0,   0) );
const D3DXCOLOR RED( D3DCOLOR_XRGB(255,   0,   0) );
const D3DXCOLOR GREEN( D3DCOLOR_XRGB(  0, 255,   0) );
const D3DXCOLOR BLUE( D3DCOLOR_XRGB(  0,   0, 255) );
Before we can create materials we will write a very simple function initMaterial which will do all the initialization work for us:
D3DMATERIAL9 initMaterial(D3DXCOLOR ambient, D3DXCOLOR diffuse, 
    D3DXCOLOR specular, D3DXCOLOR emissive, float power)
{
    D3DMATERIAL9 material;
    material.Ambient  = ambient;
    material.Diffuse  = diffuse;
    material.Specular = specular;
    material.Emissive = emissive;
    material.Power    = power;
    return material;
}
Now we are ready to create the materials for our objects.
const D3DMATERIAL9 MATERIAL_WHITE  = initMaterial(WHITE, WHITE, WHITE, BLACK, 2.0f);
const D3DMATERIAL9 MATERIAL_RED    = initMaterial(RED, RED, RED, BLACK, 2.0f);
const D3DMATERIAL9 MATERIAL_GREEN  = initMaterial(GREEN, GREEN, GREEN, BLACK, 2.0f);
const D3DMATERIAL9 MATERIAL_BLUE   = initMaterial(BLUE, BLUE, BLUE, BLACK, 2.0f);
Notice that materials are also defined as constants and that they call the initMaterial function that simply accepts the color of each type of light that an object can absorb and the power parameter that represents the sharpness of specular highlights. In a similar manner you can define your own colors and materials.
Once we have our materials and colors set, we can define the material of each object. In the next step we are going to set the wireframe mode to false:
bool isWireframe = false
We do that because we want to demonstrate that you can actually combine the wireframe mode with the solid mode. Before you render the object you have the ability to decide which mode to use. In our case we will keep the flying box and the asteroids in wireframe mode while the fireballs fired by the flying box will be rendered as solid objects. You can change it if you wish but be aware that rendering objects as solids takes much more time. For testing purposes it is often enough to render your scene in a wireframe mode.
In initDevice() method we delete the following line since it is no longer relevant:
directXDevice->SetRenderState(D3DRS_LIGHTING, false);
We have decided to use a directional light in our world. The following method was added to initialize the directional light:
void initLight(){
    //
    // Setup a directional light.
    //
    D3DXVECTOR3 direction(0.0f, 1.0f, 0.25f);
    D3DXCOLOR color = WHITE;
    D3DLIGHT9 light;

    ::ZeroMemory(&light, sizeof(light));
    light.Type      = D3DLIGHT_DIRECTIONAL;
    light.Ambient   = color * 0.6f;
    light.Diffuse   = color;
    light.Specular  = color * 0.6f;
    light.Direction = direction;
    
    directXDevice->SetLight(0, &light);
    directXDevice->LightEnable(0, true);

    directXDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true);
    directXDevice->SetRenderState(D3DRS_SPECULARENABLE, false);
}
It first defines the direction of light as a vector, the color of the light is WHITE (defined earlier as a constant). Notice that light is a D3DLIGHT9 structure and that it has his Type property set to D3DLIGHT_DIRECTIONAL. Light can also accept the ambient, diffuse and specular types of light as parameters and of course the direction of light. The first parameter in SetLight method of our device is 0 because it is the first light we are going to add to our world. Finally we enable the light we have created and set the render state of our device.
As it was mentioned before, we would like to keep rendering the asteroids in a wireframe mode, therefore in the drawAsteroidMove() method we modify the following lines:
// set asteroid material
directXDevice->SetMaterial(&MATERIAL_BLUE);

// set device to wireframe mode
directXDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);

mesh->DrawSubset(0);

// reset device to solid mode for other objects
directXDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
As you can see we did add the SetMaterial() to MATERIAL_BLUE and SetRenderState() to wireframe mode before the DrawSubset() method and we did switch to solid mode immediately after drawing the asteroid, so other objects will be rendered as solids.
Inside drawBoxFireMove() method we add the following statement before DrawSubset():
// set fireball material
directXDevice->SetMaterial(&MATERIAL_RED);
This way fireballs fired by the flying box will be rendered as red solid objects.
Because we want to keep the flying box in a wireframe mode we modify the following inside drawBoxMove() method:
// set device to wireframe mode
directXDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
    
// set box material
directXDevice->SetMaterial(&MATERIAL_GREEN);

// draw the object using the previously created world matrix.
meshBox->DrawSubset(0);
    
// reset device to solid mode for other objects
directXDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
Notice that this is done exactly in the same way we have set up the color of asteroids, so it does not require additional explanation.
Finally we have to call the initLight() method inside WinMain() to initialize our directional light.
In the next tutorial we show how to add a "Game Menu".