2.2 Lighting Calculations
In
previous versions of Direct3D, the fixed function pipeline included
support for a fixed number of built-in lights. With Direct3D10, that
support is gone, giving you the freedom to handle as many lights as you
care to do the math for. In most situations, this is beneficial as the
previous pipeline couldn’t take into account the myriad options people
really wanted with lighting.
Regardless of
the type of lights you choose to employ, they can affect your scene
however you want because you have the ability to directly control them
in the shader.
Ambient
Ambient
light is an all powerful light. It doesn’t have a fixed source and
seems to come from all around you. Calculating lighting based on
ambient light is the simplest of the lighting calculations. Ambient
lighting is determined by multiplying the object’s material color by
the color of the ambient light.
outputColor = objectColor * ambientColor
Note
An
object’s material is used to describe how light coming off the object
behaves. The material can affect the color of the light being reflected
or even the amount of light. For instance, a red ball being lit by a
blue light will have its reflected color a combination of the two.
The following function shows how you would determine the output color based on ambient lighting in a shader.
/*********************************
* CalculateAmbient -
* inputs -
* vKa material's reflective color
* lightColor - the ambient color of the lightsource
* output - ambient color
*********************************/
float3 CalculateAmbient(float3 vKa, float3 lightColor)
{
float3 vAmbient = vKa * lightColor;
return vAmbient;
}
Diffuse
Diffuse
lighting calculates the color of an object based on any directional
lights within your scene. Directional lights don’t have a fixed
position but definitely have a direction. Because directional lights
come from a single direction, this means that they will be brighter on
one side of your objects and dimmer on the other side. This effect will
give your objects that sense of depth.
The CalculateDiffuse function shown next demonstrates how this is calculated.
The CalculateDiffuse
function takes four parameters: the base color of the object, the color
of the directional light, the surface normal, and the light vector.
The
first step is taking the dot product using the normal and the light
vector. This value is then clamped between 0 and 1 using the saturate
function. The resulting value is then multiplied by the object’s base
color and the color of the directional light. As a reminder, this will
calculate the angle between the incoming light and the surface of the
object.
/*********************************
* CalculateDiffuse
* inputs -
* material color
* The color of the direct light
* the local normal
* the vector of the direct light
* output - diffuse color
*********************************/
float3 CalculateDiffuse(float3 baseColor, float3 lightColor, float3 normal,
float3 lightVector)
{
float3 vDiffuse = baseColor * lightColor * saturate(dot(normal,
lightVector));
return vDiffuse;
}
Specular Highlights
Specular
is the shininess of an object. Take a lamp and place it next to a
glossy object like a piece of glass. Notice not only how the object is
lit, but you’ll probably also see what looks like light spots on the
object; this is an example of a specular highlight. Specular highlights
add that little bit of extra realism to a scene and help show how the
light really affects the items in your scene.
Calculating
the specular highlight requires a few things from your application.
First, you need the view vector. This is a vector that describes the
camera’s location in relation to the object. Second is the light
vector. This is the vector that describes where the light is coming
from. Lastly is the surface normal.
The first step is creation of the reflection vector using the reflect
function. This gives you the direction the light is coming off the
surface of the object. Next, the specular value is determined by first
taking the dot product of the reflection vector and the view vector.
This value is then clamped between 0 and 1 using the saturate function.
Finally, the specular value is raised to a power value specified in the
specpower variable. This value is tunable and can be changed until you obtain the look you like.
float specpower = 80.0f;
/*********************************
* CalculateSpecular -
* inputs -
* viewVector
* the light vector
* the normal
* output - specular highlight
*********************************/
float CalculateSpecular(float3 viewVector, float3 lightVector, float3 normal)
{
float3 vReflect = reflect(lightVector, normal);
float fSpecular = saturate(dot(vReflect, viewVector));
fSpecular = pow(fSpecular, specpower);
return fSpecular;
}
Combining the Lighting
Now
that you have the three lighting values, you need to combine them. This
will give you an object that takes into account ambient, diffuse, and
specular lighting. Be aware that the lighting in this case in not
dependent on the camera’s point of view. The LightingCombine function shown next demonstrates how the lighting methods are brought together.
/*********************************
* LightingCombine -
* inputs -
* ambient component
* diffuse component
* specular component
* output - final color
*********************************/
float3 LightingCombine(float3 vAmbient, float3 vDiffuse, float fSpecular)
{
float3 vCombined = vAmbient + vDiffuse + fSpecular.xxx;
return vCombined;
}
All
four of these calculations can be placed into a utility shader file and
included when needed in your own effect files. These functions contain
the generic lighting calculations that you’ll be using over and over.
These calculations are just one of many ways that lighting can be
implemented within your application and are by no means perfect for
every instance.