Thursday, April 4, 2013

Putting the scene under the spotlight

Implementing spot lights is pretty simple, once you have point lights in place. It is possible to use the same lights list to store spot light information and process the spot lights in the same light loop for forward lighting.

Following is the update to the structure:


struct sLight //name changed from Point light to Light
{
float3 Position;
float3 Color;
float3 SpecularColor;
float3 Direction;
float attenExp;
float size;
float lightConeScale;
float lightConeOffset;
};

Direction, lightConeScale and lightConeOffset are the new parameters added for the spot light. We can use the same parameters for the point lights and make sure it's effects get Zeroed out when you calculate the lighting. I'll explain how to do this later.

Light Cone Scale and Offset, are special parameters used for getting light attenuation with inner cone angle and outer cone angle. The attenuation will start only after the inner cone angle and will completely fade out once we reach the outer cone angle. This is how we calculate the scale and offset parameters for the spot light.


const float cosOuterAngle = cos(pLight->GetOuterAngle());
float lightConeScale = 1.0f / max(cos(pLight->GetInnerAngle()) - cosOuterAngle, 0.000001f);
float ightConeOffset = -cosOuterAngle * this->lightConeScale;

The following are the changes in the light shader:


for(int i=0; i<numLights; i++)
{
sLight currentLight = g_Lights[i];
float3 posToLightVector = currentLight.Position - worldPos;
float dist = sqrt(dot(posToLightVector, posToLightVector));
float3 normalizedLightVec = posToLightVector / dist;
float NDotL = dot(normalizedLightVec, worldNormal); //Diffuse lighting
float atten = pow(saturate((currentLight.size - dist) / currentLight.size), currentLight.attenExp);
float angularFallOff = saturate(dot(normalizedLightVec, -currentLight.Direction) * currentLight.lightConeScale + currentLight.lightConeOffset);
accumColor += saturate(g_Lights[i].Color * NDotL * atten * angularFallOff);
}

The angular falloff reduces the intensity of the light when we move from inner cone angle the outer cone angle. You can add intensity as an option, but the only issue with that is you should make sure you use an HDR target.

For point lights to use the same structure, you have to make sure the angularFallOff calculation is set to one. For that you have to set Scale = 0.0f, and Offset = 1.0f, and direction can be set to any value.

Following is the result when adding one white point light and 4 spot lights:


Next Stop: Either Capsule Lights, Area Lights .. or .. Deferred Lighting



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