Pixel
shaders give you access to every pixel being put through the pipeline.
Before anything is drawn, you’re given the chance to make changes to
the color of each pixel. In some cases you’ll simply return the pixel
color passed in from the vertex or geometry shaders, but in most cases
you’ll apply lighting or textures that affect the
color of the resulting pixel. The code shown next demonstrates the
simplest form of pixel shader, which just passes the input color along
to be drawn.
///////////////////////////////////////////////
// Pixel Shader
///////////////////////////////////////////////
float4 PS(PS_INPUT psInput) : SV_Target
{
return psInput.Color;
}
Just
to give you an idea of one manipulation you can perform on the input
color, the following shader divides the color by two. This causes the
color to dim as compared to the input color.
///////////////////////////////////////////////
// Pixel Shader
///////////////////////////////////////////////
float4 PS(PS_INPUT psInput) : SV_Target
{
/* psInput.Color / 2 is equivalent to
psInput.Color.r = psInput.Color.r / 2;
psInput.Color.g = psInput.Color.g / 2;
psInput.Color.b = psInput.Color.b / 2;
psInput.Color.a = psInput.Color.a / 2;
*/
return (psInput.Color / 2);
}
2. Lighting
If
you look around, chances are there’s a light source somewhere in your
vicinity. It could be a lamp, the sun, or even just your monitor. Every
bit of light, no matter how small, affects how we see the world around
us. Some objects are dull and seem to absorb light while others are
shiny causing light to be readily reflected. Everything you’ve done up
to this point has assumed that your scene is taking place in a world
where there is an all-powerful ambient light source causing everything
to be lit from all directions. While it helps to be able to see what
you’ve created, it isn’t very realistic.
In
this section you’re going to learn a little about how lights affect a
scene and how to get lighting up and running. The first step down the
lighting path is the generating of normals.
2.1 Generating Normals
A
normal is a vector that is perpendicular to the plane of a polygon and
is used when adding lighting to a scene. The job of the normal is to
help in determining the amount of light a polygon may be receiving.
Essentially, what this means is you have to calculate what direction
the different pieces of your objects are facing so you know how much to
light it.
Normals are calculated by
obtaining the cross product of two of the polygon’s edges. The vectors
representing the edges of the polygon are determined using the
polygon’s vertices. The normal calculation is shown below. An example
of a normal vector is shown in Figure 1.
NormalVector = CrossProduct( (vertex2 - vertex0), (vertex1 - vertex0) )
You
can think of the returned value from the normal calculation as the
amount of light hitting the object from the three axes directions.
Updating the Code to Support Normals
Like
texture coordinates and position, normals are stored in the vertex
structure and that means updating the custom vertex structure. Normals
have an X, Y, and Z component and can be stored in a D3DXVECTOR3 variable. As you can see in the following structure, a variable called Normal has been added. The name of the structure was also changed to more accurately reflect the type of data it’s storing.
struct VertexPosColorNormalStruct
{
D3DXVECTOR3 Pos;
D3DXVECTOR4 Color;
D3DXVECTOR3 Normal;
};
After
the vertex structure is updated, the input layout also needs to be
changed. This will enable the application to successfully send the
vertices to the shader. In the new layout, the information for the
normals is added to the end after position and color. This keeps
everything in sync with the order in the vertex structure.
// The vertex input layout
D3D10_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
D3D10_INPUT_PER_VERTEX_DATA, 0},
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12,
D3D10_INPUT_PER_VERTEX_DATA, 0},
{ "NORMAL", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 28,
D3D10_INPUT_PER_VERTEX_DATA, 0},
};
The
normals themselves are calculated using the array of indices that
define all the faces in the object. By looping through the array of
indices, the appropriate vertices can be updated for each face. After
the normals are calculated, the resulting value is written to the Normal variable for each of the vertices.
// compute normals for each face in the model
for (unsigned int i = 0; i < modelObject->numIndices; i+=3)
{
D3DXVECTOR3 v0 = vertices[indices[i]].Pos;
D3DXVECTOR3 v1 = vertices[indices[i + 1]].Pos;
D3DXVECTOR3 v2 = vertices[indices[i + 2]].Pos;
D3DXVECTOR3 normal;
D3DXVECTOR3 cross;
D3DXVec3Cross(&cross, &D3DXVECTOR3(v2 - v0), &D3DXVECTOR3(v1 - v0));
D3DXVec3Normalize(&normal, &cross);
// assign the computed normal to each vertex in this face
vertices[indices[i]].Normal = normal;
vertices[indices[i + 1]].Normal = normal;
vertices[indices[i + 2]].Normal = normal;
}
Now that you have normals defined for all the vertices in your object, lighting can be applied within the pixel shader.