Lighting with Direct3D

Direct3D provides four different types of light, ambient, directional, point and spot. I describe these lights below but you are also advised to look in the DirectX help for the more advanced details. Lights are added to your scene using the Direct3D device interfaces. Whenever geometry is rendered it may be lit by these lights.

Lights can be any colour you want. Lights and materials work together so you should also read the associated material notes: materials. The material can be set for each 3D object you draw and determines how an object will be lit (you could also use more than one material per object). So if you have a white light and an objects material is green it will appear as bright green. If the light is red it will come out as the result of red on green - a muddy sort of colour :)

Ambient Light

Ambient light represents the background light in a scene. In the real world light bounces off many objects and creates a low light level so even geometry facing away from any directional light is still partially lit. Ambient light is very inexpensive in terms of frame rate - almost free. It is applied to all surfaces irrespective of the direction they are facing. To set the ambient light level in Direct3D you use one of the render states like this:

gD3dDevice->SetRenderState( D3DRS_AMBIENT, D3DCOLOR_XRGB(50,50,50) );

The last value is a D3DCOLOR value to set the colour of the ambient light. The above sets a low background white light. For a full list of render states see this page: Render States

Directional Lights

Directional light is used to simulate distant light sources like the sun. It has a direction but no position. e.g. you could set it to point downward (0,-1,0) which would simulate the sun being directly overhead. Directional light is relatively inexpensive although if you add lots of them you may find your frame rate dropping somewhat. The lighting effect on objects depends on which way they are facing relative to the light direction (defined in the vertex normal). So a triangle with normal pointing straight up (0,1,0) would be lit by our sun (0,-1,0) fully - it would receive the maximum amount of light. As the angle of the normal to light increases less light is applied. If the triangle normal faces away from the light it is not lit at all.

Directional Light

In order to add a light to our scene we use the same method for directional, spot and point lights. We fill in a light structure and call the device SetLight function to set the light parameters. Depending on the type of light we fill in different structure variables so it is always a good idea to start by clearing the structure to defaults (to 0) before filling in the required values for a particular light type.

We must remember to turn the light on by calling LightEnable. The number of lights we can have in our scene depends on the hardware support (8 is common with today's hardware).

E.g. to create a sun like directional light with the sun quite low in the sky we could do this:

void CreateSunLight()
{

      // Fill in a light structure defining our light
      D3DLIGHT9 light;
      ZeroMemory( &light, sizeof(D3DLIGHT9) );
      light.Type       = D3DLIGHT_DIRECTIONAL;
      light.Diffuse.r  = 1.0f;
      light.Diffuse.g  = 1.0f;
      light.Diffuse.b  = 1.0f;
      light.Diffuse.a  = 1.0f
      light.Range      = 1000.0f;

      // Create a direction for our light - it must be normalized 
      D3DXVECTOR3 vecDir;
      vecDir = D3DXVECTOR3(0.0f,-0.3f,0.5);
      D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &vecDir );

      // Tell the device about the light and turn it on
      gD3dDevice->SetLight( 0, &light );
      gD3dDevice->LightEnable( 0, TRUE );

}

Important Note: one of the common issues people have with lights is forgetting to turn them on! In order to turn lighting on globally you must set a render state: e.g.

gD3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );

Point Lights

A point light represents a point source. It has a position in the world and radiates light in all directions. A bare bulb on a stand would be an example of this. It radiates light in every direction and has a position in the world. This is a more expensive type of light in terms of frame rate than a directional light.

Point Light

A point light has an attenuation (how the light level decreases over distance) and a range. The range is the maximum distance the light will travel. Look in the DirectX help for the complete equations used. An example of positioning a point light in our world is shown below:

void CreatePointLight(const D3DVECTOR &pos)
{

      // Fill in a light structure defining our light
      D3DLIGHT9 light;
      ZeroMemory( &light, sizeof(D3DLIGHT9) );
      light.Type       = D3DLIGHT_POINT;
      light.Diffuse.r  = 1.0f;
      light.Diffuse.g  = 1.0f;
      light.Diffuse.b  = 1.0f;
       
      // Point lights have no direction but do have a position
      light.Position = pos
       
      // Tell the device about the light and turn it on
      light.Attenuation0 = 0.1f;
      light.Range=200.0f
      gD3dDevice->SetLight( 1, &light );
      gD3dDevice->LightEnable( 1, TRUE );

}

Spot Lights

The final light type in Direct3D is the spot light. This is the most complex light and is the most expensive type in terms of frame rate so use sparingly. A spot light has a position in space and a direction. It emits a cone of light with two degrees of intensity. It has a central brightly lit section and a surrounding dimly lit section that merges with the surrounding shadow. Only objects within the light cone are illuminated.

Spot Light

To set a spot light you need to provide position, direction, cone size and a number of parameters determining the spread of the cone. Theta is the angle that defines the inner cone while Phi defines the outer cone. Falloff defines the decrease in illumination between the outside of the inner cone and the outside of the outer cone. Example code for creating a spot light is shown below:

void CreateSpotLight(const D3DVECTOR &pos,const D3DVECTOR &dir)
{

      // Fill in a light structure defining our light
      D3DLIGHT9 light;
      ZeroMemory( &light, sizeof(D3DLIGHT9) );
      light.Type       = D3DLIGHT_SPOT;
      light.Diffuse.r  = 1.0f;
      light.Diffuse.g  = 1.0f;
      light.Diffuse.b  = 1.0f;
      light.Diffues.a  = 1.0f;
       
      // Spot lights have direction and a position
      light.Position = pos
      light.Direction = dir
       
      // Tell the device about the light and turn it on
      light.Range=1.0f;
      light.Theta=0.5f;
      light.Phi=1.0f
      light.Falloff=1.0f;
      light.Attenuation0= 1.0f;

      gD3dDevice->SetLight( 1, &light );
      gD3dDevice->LightEnable( 1, TRUE );

}

Editing Light Values

If you wish to change your lights dynamically there is no need to recreate them each time. Instead you can modify an existing light. You could call the GetLight function to get the values, alter the one you want and then call SetLight:

E.g. say I wanted to set the colour of an existing light index 1 so its red component is 0.5:

D3DLIGHT9 light;

HRESULT hr=gD3dDevice->GetLight(1,&light);
light.Diffuse.r=0.5f;
gD3dDevice->SetLight(1,&light);

Advanced Lighting Notes

Even though you are limited to 8 lights by the graphic card you can still define as many lights as you want but you can only have 8 switched on at a time. So, for example, you could define 50 lights and turn them on and off as a player travels through a level.

Direct3D directional and ambient lighting is often used in games. Spot lights are used less often because how good they appear depends totally on the density of the scenes vertex. Since the lighting changes the colour at a vertex, if there are very large vertex (on the floor for example), you will not get a pure cone but rather a jagged effect. Various methods exist in games to create lighting in other ways to get around this problem e.g. light maps.

It is quite nice to attach a light to a 3D model (you may also attach other things to models like particle systems). One way of doing this might involve the addition of a light entity to your visualisation component and a link in your world model between world entity and the light entity graphic.

You can have negative colours in your lights. This means that in affect they 'suck' light out of the scene - I have yet to find much use for this :)

Further Reading

On this site: Materials, Render States, Shaders
Other: MSDN Lights & Materials



© 2004-2016 Keith Ditchburn