Example 01.03: Phong reflection model – diffuse lighting

After the basic ambient lighting factor from the previous example, we are now actually going to apply some shading to our model. The diffuse lighting component we will implement assumes we are describing a perfect matte surface that scatters the incoming light equally in all directions.

diffuse_reflection

Figure 1. Diffuse reflection

The Code

The first thing that has changed in the code is the initialization of the extra parameters needed for the diffuse lighting equation. These are the diffuse color (Dc) the diffuse color intensity (Di) and a normalized vector for the direction the light is coming from (vLightDirection).

 _effect.Parameters["Di"].SetValue(1.0f);  
 _effect.Parameters["Dc"].SetValue(new Vector4(0.0f, 0.0f, 0.3f, 1.0f));  
 Vector3 lightDirection = new Vector3(0.0f, 1.0f, 1.0f);  
 lightDirection.Normalize();  
 _effect.Parameters["vLightDirection"].SetValue(lightDirection);  

Code Snippet 1. Setting the required parameters

As we are doing the diffuse lighting calculation in world space we need to have the world matrix separately available in the shader. Typically the surface normals of a model are given for each vertex in model space. The light vector we use here is defined in world space so to convert the normal vectors from model to world space we need the world matrix.

 _effect.Parameters["matWorld"].SetValue(modelPartWorldMatrix);  

Code Snippet 2. Setting the world matrix

In the shader we start by adding the vertex normal to the input structure for our vertex shader function. By adding the semantic ‘: NORMAL’ we kindly ask the API that if normal data is available for the vertex, please fill it in here.

struct VS_IN  
{  
  float4 Pos : POSITION; // Position (Model Space)  
  float3 N : NORMAL; // Normal (Model Space)  
};  

Code Snippet 3. Vertex shader input

In the vertex shader function we simply convert the normal vector from model to world space and pass it along to the pixel shader.

VS_OUT VertexShaderFunction(VS_IN input)  
{  
  VS_OUT output;  
  output.Pos = mul(input.Pos, matWorldViewProj);  
  output.N = normalize(mul(input.N, matWorld));  
  return output;  
}  

Code Snippet 4. Vertex shader function

As with most things you pass down in the graphics pipeline, the vertex normal you receive in the pixel shader input will be interpolated. As the interpolation process can make the normal vector denomalized, we need to normalize it again to correctly calculate the diffuse lighting factor (or Lambert factor).

triangle_normals

Figure 2. A triangle with normal vectors defined for each vertex

normal_interpolation

Figure 3. Denormalization of normal vector due to interpolation

The Lambert factor is the scalar that the diffuse light must be multiplied with to get the correct light intensity. It’s the cosine of the angle between the light direction and the surface normal. As both the surface normal and light vector are normalized, the cosine of the angle between them is actually the same as the dot product of the vectors. A formula for getting the dot product of vectors N and L is: |N| . |L| . cos(α)

diffuse_vectors

As our nomal and light vectors are normalized, their magnitudes are 1 and the dot product just gives us the cosine of the angle between them that we were after.

One of the best explanations on vector dot products I’ve ever read is in chapter 2.11 of ‘3D Math Primer for Graphics and Game Development‘. The geometric interpretation, seeing it as a ‘projection’, is very usefull.

Shading languages have performant implementations of vector operations so using the built-in HLSL functions this translates to: saturate(dot(L, N)).  Where L is the vector that points to the light source and N is the normal vector of the surface.  The saturate function clamps the value between 0 and 1 as we don’t want negative lighting values.

float4 PixelShaderFunction(VS_OUT input) : COLOR  
{  
  float3 N = normalize(input.N);  
  float3 L = normalize(vLightDirection);  
  return Ai * Ac + Di * Dc * saturate(dot(L, N));  
}  

Code Snippet 5. Pixel shader function

So now our model is actually beginning to look like something with a little depth to it.

Have fun!

previous post – next post

Downloads

Download XNA code
Download MonoGame code
Blob model

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment