Lighting (XNA Game Studio 4.0 Programming) Part 2

Emissive Lighting

Some objects not only receive light but also give off light. Emissive light is the light given off by the object.The emissive light is added to other light sources.

To add emissive light to the continuing example is quite simple.You need one additional color value for each object you want to draw. Add the following to your game class member variables:

tmp14-37_thumb[2]

You will store a separate emissive color for each object like you did for the diffuse color. In the game’s Initialize method, add the following lines of code:

tmp14-38_thumb[2]

Next, pass this color value into your effect. Just after you set the DiffuseColor, set the EmissiveColor for the effect.


tmp14-39_thumb[2]

In the effect file, you need an additional global variable that will store the emissive color.

tmp14-40_thumb[2]

Finally, in the pixel shader, add the emissive color before you return the finalColor.

tmp14-41_thumb[2]

Running the example now shows the objects with an inner glow of the emissive colors like Figure 8.15.

Although the objects look like they are emitting light, they don’t have any effect on other objects.This emissive light is used only on the object itself and does not create a halo around the object.

Emissive lighting on the objects

Figure 8.15 Emissive lighting on the objects

For the remainder of the topic, turn off the emissive lighting by setting the emissive color to zero.

Specular Lighting

In the real world, not all objects are flat shaded. Some objects are shinny and reflect light very well.Think of a bowling ball and how shinny it is. If you look at a bowling ball, notice how there might be bright spots on the ball where the lights in the bowling alley are better reflected.

The shinny spots appear where the angle of the reflected angle from the light about the vertex normal is close to the vector to the camera position. Figure 8.16 shows how the specular light on the triangle is dependent on the viewer angle.

Phong Shading

There are a number of ways to model this shinny appearance of objects. One method is called Phong shading, which is named after its inventor Bui Tuong Phong.

Phong shading uses two new vectors R and V. R is the unit reflection vector of the light about the vertex normal. V is the unit vector of the camera position to the vertex rendered called the viewer vector.The intensity of the specular highlight is then calculated by taking the dot product between R and V. Different materials have different levels of shini-ness to achieve different results a specular power values are used to raise the R dot V to different powers.This calculated specular intensity value is then multiplied by the object’s specular color and then added to the final color of the pixel.

The equation for the specular shading value using the Phong lighting model is

tmp14-43_thumb[2]

 

 

Specular light on a triangle

Figure 8.16 Specular light on a triangle

Blinn-Phong Shading

Calculating the reflection vector requires a number of calculations.To eliminate some of these calculations, Jim Blinn created another specular lighting model based on Phong, called Blinn-Phong in 1977.

Blinn-Phong differs from Phong by using a new vector H called the half vector. The half vector is the vector halfway between the viewer vector V and the light direction L. The half vector can be calculated by adding the view vector V and the light direction L and normalizing the vector to unit length. The H vector is dot multiplied with the vertex normal and raised to a specular power. This is similar to how the R and V vectors are used in the Phong lighting model.

The equation for the specular shading value using the Blinn-Phong lighting model is

tmp14-45_thumb[2]

Let’s add some Blinn-Phong to the previous example. Add the following member variables to your game:

tmp14-46_thumb[2]

The first array specularColorPower stores the specular color and specular power for each of the objects.The color uses the X, Y, and Z components of the vector while the W component stores the specular power.The specularLightColor variable stores the specular color of the light source.The final value cameraPosition stores the camera location.

In the game’s Initialize method, add the following values to set the specular color of the objects and the light. Also set the camera position that you used to make the view matrix.

tmp14-47_thumb[2]

These values need to be set on the effect.The SpecularLightColor and CameraPosition can be set with other effect wide properties.

tmp14-48_thumb[2]

The SpecularColorPower needs to be set with other per object effect values such as the diffuse color.

tmp14-49_thumb[2]

That is it for the game code changes. Now, you need to update the effect file to add the Blinn-Phong calculations.

First, add some additional global variables to your effect.

tmp14-50_thumb[2]

The next change is to the output vertex structure where you add the view vector V, which you calculate in the vertex shader.

tmp14-51_thumb[2]

In the vertex shader, the View value is calculated by subtracting the calculated world position from the camera position, which is also in world space.

tmp14-52_thumb[2]

The final change updates the pixel shader to calculate the specular lighting value and adds it to the final pixel color.

tmp14-53_thumb[2]

The first line normalizes the View vector, which needs to be unit length and can change as it is interpolated across the triangle.The next line calculates the half vector by adding the view and light direction vectors and then normalizes the result. The dot product of N and H are then taken and clamped between 0 and 1. Finally, its specular value is calculated by using the pow intrinsic function that raises the NdotH value to the specular power, which passed in as the w component of the SpecularColorPower variable and is then multiplied by the light’s specular color.

The last bit of code adds the specular color to the final pixel color using the calculated secular intensity and the object’s specular color stored in the xyz channels of SpecularColorPower.

Running the sample now should produce results with a shinny spot on each of the objects in the scene as shown in Figure 8.17.

Try adjusting all of the lighting and color values in the examples thus far and see how they change the results. Notice that lowering the specular power makes the specular highlight larger but less crisp around its edges.

Fog

Real-world objects that are farther away are not only smaller but also obscured by the amount of atmosphere between the viewer and the object. On most days when the air is clear, the visibility of objects far away are not obscured too much, but on other days a layer of fog might cover the ground lowering the visibility.

Blinn-Phong specular lighting on objects

Figure 8.17 Blinn-Phong specular lighting on objects

In games, you can simulate the fog that obscures objects by interpolating the color of an object as it moves farther into the distance with a fog color. Fog can be any color, but all objects should use a similar fog color; otherwise, some objects will stand out more than others.

There are multiple ways to interpolate the fog value as distance increases. The first method is to use a simple linear interpolation as distance increases.The equation you will use to calculate the linear fog value is

Fog = (Distance — Fog Start) / (Fog End — Fog Start)

The distance is from the camera to the current pixel you are drawing. The fog start is the distance that the fog can start to be seen or where the interpolation should start. The fog end is the distance where the object will be in complete fog.The calculated fog is a floating point value, which is in the range of 0 and 1.Values over 1 should be treated as 1, which means fully in fog.The fog value is then used as the interpolation value of how much of the fog color to use with the final calculated pixel color.

To add linear fog to the ongoing sample, you will add three new member variables to your game.

tmp14-55_thumb[2]

These values store the fog color and distance values you need to send to your custom effect.

These values need to be set to some defaults, so in the game’s Initialize method, add the following lines of code:

tmp14-56_thumb[2]

Use a color value that is the same as your clear color, so that the objects appear to fade away as they move into the distance. In your game, set the color based on the time of day. If your game is at night, fade to a dark almost black color. If it is daytime, then use a lighter gray color. The fog color can also be used for special effects to simulate some type of toxic gas in a city so you can use some other interesting colors.

The other two values are set to sensible values for the scene. Depending on the size and depth of your scene and the camera’s view, update these values to match the scales.

In the game’s Draw method, send the new values to your custom effect.

tmp14-57_thumb[2]

Finally, update how you are drawing the meshes in sample so that you have many more objects that move farther and farther away from the camera.

tmp14-58_thumb[2]

 

 

 

tmp14-59_thumb[2]

Loop five times to draw the model multiple times and farther and farther depths. This should enable you to get a better idea of the effect of the fog as the distance from the camera increases.

With all of the game code changes complete, you just need to make a few simple changes to your custom effect file to support the linear fog. First, you need the global variables for the fog color and distance properties.

tmp14-60_thumb[2]

Next, you need to calculate the fog value per vertex, so you need to pass the fog value into the pixel shader. To do this, add the following value to your vertex output structure.

tmp14-61_thumb[2]

In the vertex shader, calculate the fog value before outputting the vertex.

tmp14-62_thumb[2]

Use the same equation described previously. In this code, the distance is calculated by subtracting the world position of the vertex from the camera position.Then, use the length intrinsic function to calculate the length of the resulting vector.The saturate intrinsic function is used to clamp the calculated for value from 0 to 1.

The final change is to update the pixel shader to use the newly calculated fog value. Just before returning the final color from the pixel shader, add the following line of code:

tmp14-63_thumb[2]

The lerp intrinsic function interpolates between two vectors given the interpolation value, which should be between 0 and 1. Interpolate between the already calculated final color and the fog color depending on the fog value that you calculated in the vertex shader.

Running the sample now should display multiple versions of the models that are farther and farther away from the camera that slowly fade to the background color.This should look similar to Figure 8.18.

Objects obscured by fog as they move farther into the distance

Figure 8.18 Objects obscured by fog as they move farther into the distance

The fog value does not have to interpolate linearly with distance.You can also use other types of interpolation such as an exponential falloff to achieve difference results. Because you have the control in the vertex shader, there are many different ways that you can use the distance to calculate the fog value.

Point Lights

All of the examples up to this point use one or more directional lights when calculating the light’s influence on the color of the triangles. Although directional lights work well to simulate the type of light that is coming from distant objects such as the sun, they don’t work to simulate how objects look indoors when lit from multiple smaller artificial lights such as light bulbs. In computer graphics, we call these types of light sources point lights.

Point lights have a known position in 3D space, which differs from directional lights that only have a direction. The direction to the point light must be calculated because it changes depending on the location of the object drawn.

The light that comes from smaller lights like the ones in your home tends to lose its brightness the farther away an object is from the light.You can see this in your home when you turn off the lights in a room and have only a single table lamp to light the room. Notice that the light is brightest close to the lamp, but falls off quickly with distance from the light.This falloff from the light source is called attenuation and differs depending on the size and type of light.

There are multiple ways in which you can calculate the attenuation when simulating the light that comes from point light sources. Use the following attenuation equation:

Attenuation = 1 – (((Light Position – Object Position) / Light Range) * ((Light Position – Object Position) / Light Range))

In the previous equation, the light position is the location of the point light.The object position is the location you are currently drawing in world space, and the light range is how far the light rays will travel from the light source.

To demonstrate how point lights work in the game, update the previous specular lighting example, which uses directional lights.

First, you need some different member variables for the game. Remove the vector that represents the light direction and update it with the following code:

tmp14-65_thumb[2]

The light now has a position and a float value that represents the distance from the light that objects can be lit from.

In the game’s Initialize method, give the point light the following values:

tmp14-66_thumb[2]

In the same location where you set the light direction for the effect in your game’s Draw method, update the effect with the new point light properties.

tmp14-67_thumb[2]

Most of the changes to support the point light occur in the effect file.You need two new global variables to store the light position and range.

tmp14-68_thumb[2]

You need the position of the pixel you are rendering in the pixel shader in world space. To do this, add an additional value to the vertex output structure to store the position in world space.

tmp14-69_thumb[2]

The vertex shader needs to be updated to save the calculated world space position of the vertex.

tmp14-70_thumb[2]

The pixel shader should be updated to the following:

tmp14-71

 

 

 

tmp14-72

Although it appears to be a lot of code at first, it is actually close to the pixel shader used for the specular lighting example. Let’s walk though the important differences.

The input world position is interpolated for each pixel giving the location of the current pixel in world space, which is what you need to calculate the distance to the light source.The light variable is stored with the vector form the pixel to the light.The attenuation is then calculated using the equation from earlier in the topic.The light vector is then normalized because you use this value as the direction of the light as you would if this was a directional light.The new normalized light is then used in the calculations of NdotL and half. Finally the attenuation is multiplied when calculating the diffuse and specular intensity values.

Running the sample now shows the objects in the scene lit from a point location where the light falls off the farther the objects are away from the light. Rotating the light or moving the light source from each frame can make this much more visible. Figure 8.19 shows the objects in the scene lit from a single point light.

Adding multiple point lights works in a similar way as adding additional directional lights.The lighting influence of each additional light source needs to be calculated for each light and added into the diffuse and specular intensity values.You can also mix and match by supporting a directional light and point lights in your effect.

Next post:

Previous post: