Specular light

Hi, and welcome to Tutorial 3 of my XNA Shader Programming tutorial. Today we are going to implement an other lighting algorithm called Specular Lighting. This algorithm builds on my Ambient and Diffuse lighting tutorials, so if you haven't been trough them, now is the time. :)

Before we start: In this tutorial, you will need some basic knowledge of shader programming, vector math and matrix math. Also, the project is for XNA 4.0 and Visual Studio 2010..

Specular light

So far, we got a nice lighting model for making a good looking lighting on objects. But, what if we got a blank, polished or shiny object we want to render? Say a metal surface, plastic, glass, bottle and so on.

To simulate this, we need to implement a new vector to our lighting algorithm: The eye vector.

Whats "the eye" vector, you might think? Well, it's a pretty easy answer to this. It's the vector that points from our camera position to the camera target.We already got this vector in our application code:

Not much new in the vertex shader since last time, except for the V vector. V is calculated by subtracting the transformed position from the Eye vector. Since V is a part of the OUT structure, and we have defined OUT Out, we can calculate V with the following code:

float4 PosWorld = mul(Pos,matWorld);
Out.L = vecEye - PosWorld;

where vecEye is a vector passed into the shader trough a shader-parameter (the camera position).

And then its time to implement the pixel shader. We start with normalizing the Normal, LightDir and ViewDir to make calculations a bit simpler. The pixel shader will return a float4, that represents the finished color, I, of the current pixel, based on the formula for specular lighting described earlier.Then, we will calculate direction of the diffuse light as we did in Tutorial 2.

The new thing in the Pixel Shader for Specular Lighting is to calculate and use a reflection vector for L by N, and using this vector to compute the specular light.So, we start with computing the reflection vector of L by N: R = 2 * (N.L) * N – L

As we can see, we have already computed the Dotproduct N.L when computing the diffuse light. Lets use this and write the following code:

float3 Reflect = normalize(2 * Diff * Normal - LightDir);

Note: We could also use the reflect function that is built in to HLSL instead, taking an incident vector and a normal vector as parameters, returning a reflection vector:float3 ref = reflect(L, N);

Now, all there is left is to compute the specular light. We know that this is computed by taking the power of the dot-product of the reflection vector and the view vector, by n: (R.V)^n You can think of n as a factor for how shiny the object will be. The more n is, the less shiny it is, so play with n to get the result you like.

As you might have noticed, we are using a new HLSL function pow(a,b). What this does is quite simple, it returns a^b.

float Specular = pow(saturate(dot(Reflect, ViewDir)), 15);

Phew, we are finally ready to put all this together and compute the final pixel color:

return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular;

This formula should no longer be a surprise for anyone, right?

We start by calculating the Ambient and Diffuse light, and add these together. Then we take the specular light color and multiply it with the Specular component we just calculated, and add it with the Ambient and Diffuse color.

Using the shader

There is almost nothing new when it comes to using the shader in an application since my last tutorial, except for setting the vecEye parameter to the shader. We just take the position of the camera and pass it to our shader. If you are using a camera-class, there might be a function for getting the camera position. It's really up to you how you decide to get it.

In my example, i use the same variables for setting the camera position, and creating a vector that is passed to the shader.

Vector4 vecEye = new Vector4(x, y, zHeight, 0);

and pass it to the shader:

effect.Parameters["vecEye"].SetValue(vecEye);

Setting parameters in a shader from the application and how to implement the shader should not be a new topic for you if you're at this stage, so I won't go into further detail about this. Please refer to Tutorial 2 and Tutorial 1 about this, or send me an e-mail.

We also have to remember to set the technique to "SpecularLight".

Exercises

1. Make a new global variable in the shader that specifies the "shininess" of the object. You should be able to set this variable from the application that is using the shader. 2. In this tutorial, you don't have so much control over the light settings (like setting Ai and Ac, Di and Dc). Make this shader to support setting Ai, Ac, Di, Dc, Si and Sc where Si and Sc is the color and intensiveness for the specular light.

Thanks for reading this tutorial, hope I covered it enough for you to understand what this is all about! If you have any comments, feedback or questions, please ask me on petriw(at)gmail.com.

Next time I'm going to cover Normal mapping, and how to use textures in shaders.

NOTE:You might have noticed that I have not used effect.commitChanges(); in this code. If you are rendering many objects using this shader, you should add this code in the pass.Begin() part so the changed will get affected in the current pass, and not in the next pass. This should be done if you set any shader paramteres inside the pass.