Tuesday, April 2, 2013

Let there be light...

Now that I can load up SDK Meshes into the world, the next step is to light it up. There are so many different techniques out there for lighting up the world. I decided that I will implement a few of them and blog about the results for each of them.

In this post, I will talk about the traditional forward lighting technique. This basically means that whenever we render a model into the world, we apply the principles of lighting at the same time. Following is the Phong's Lighting Equation

Amount of Lighting  = Ambient Contribution+ Diffuse Contribution+ Specular Contribution
Ambient Contribution= Kambient * Ambient Light
Diffuse Contribution = Kdiffuse * (dot (Normal, Light Direction))
Specular Contribution = Kspecular * pow((dot(Reflected Light, View Vector)), phong coeff )

There is another type of specular contribution called the Blinn Phong where
Specular Contribution = Kspecular * pow((dot(Half View Vector, Normal)),blinn phong coeff )
where Half View Vector is (View + Light Vector) / 2

Now, with DX11 you can use Structured Buffers to represent any number of lights. So in my shader, the following is the point light structure


struct sPointLight
{
float3 Position;
float3 Color;
float4 SpecularColor;
float attenExp;
float size;
};
and this is the Structured Buffer


StructuredBuffer<sPointLight> g_PointLights :register(t1);

I added the code to handle the setting of the structured buffers based on the number of lights. Following is the constant buffer that contains the total number of lights


cbuffer cbLights: register(b1)
{
float numLights;
float numPointLights;
float2 dummyLights;
}


For now I will write the function for doing only the ambient + diffuse.  Specular shall be left as an exercise.
In the vertex shader, we shall calculate the world Position and world normal and pass it down to the pixel shader


TexNormal_PS_Input TexNormal_VS_Common( TexNormal_VS_Input input )
{
    TexNormal_PS_Input output = (TexNormal_PS_Input)0;
    output.pos = mul( float4(input.pos, 1.0f), World );
output.worldPos = output.pos.xyz;
    output.pos = mul( output.pos, View );
    output.pos = mul( output.pos, Projection );
output.tex = input.tex;
output.worldNormal = mul(input.normal, (float3x3)World);
    return output;
}

In the Pixel shader, once you get the interpolated positiona and normal, you can pass it to the following function to calculate the lighting


float3 CalculateForwardLighting(float3 worldPos, float3 worldNormal)
{
float3 accumColor = 0.0f;

//Ambient Lights
const float3 ambientColor = float3(0.1f, 0.1f, 0.1f);
//const float3 ambientColor = float3(1.1f, 1.1f, 1.1f);
accumColor += ambientColor;

//Point Lights
for(int i=0; i<numPointLights; i++)
{
sPointLight currentPointLight = g_PointLights[i];
float3 posToLightVector = currentPointLight.Position - worldPos;
float dist = sqrt(dot(posToLightVector, posToLightVector));
float NDotL = dot(posToLightVector / dist, worldNormal); //Diffuse lighting
float atten = pow(saturate((currentPointLight.size - dist) / currentPointLight.size), currentPointLight.attenExp);
accumColor += g_PointLights[i].Color * NDotL * atten;
}

return saturate(accumColor);

}

Whats good about this function is that it does the attenuation based on distance. Following is the effect without attenuation:

On applying attenuation, you get the following image as the result
It gives a nice feeling that the light dies out after a distance. The light structure specifies the size and the falloff exponent

To recap, you need the following to perform the lighting in the Pixel Shader:
1. World Position and Normal from Vertex Shader
2. Structured Buffer containing the lights and number of lights
3. Lighting equations

Next Stop: Implement Spot Lights, Capsule Lights and Area Lights



No comments: