Tutorial 18, Multiple Point Lights
Source and executable can be found on the bottom of the article.
Multiple Point Lights
To implement multiple( n ) point lights, we need to use our light equation n times in our shader. So, if we have 3 lights, we have to calculate diffuse, specular, self-shadow and attenuation for each of those three lights. Let’s take a look at this light equation:
As you can see, this is the exact same light equation as in tutorial 17, except that we sum the shadow, diffuse, specular and attenuation for each lightsource.
Say we have 3 lights, one violet, one green and one blue positioned around our object, this would be the result:
Not very hard ey?
To sum up, we just do what we have learned previously n-times!
Implementing the shader
We need to calculate each step in our lighting equation n-times. In this example, I have implemented 3 light sources. This means I need to send in 3 light positions, ranges and colors in to the shader:
Next, in the vertex shader, we need to calculate the distance between the surface and the light source, transform our lights to tangent space( normal mapping ) and calculate the attenuation for each of our lights:
// calculate distance to light in world space
float3 L = vecLightPos – PosWorld;
float3 L2 = vecLightPos2 – PosWorld;
float3 L3 = vecLightPos3 – PosWorld;
// Transform light to tangent space
Out.Light.xyz = normalize(mul(worldToTangentSpace, L)); // L, light
Out.Light2.xyz = normalize(mul(worldToTangentSpace, L2)); // L2, light
Out.Light3.xyz = normalize(mul(worldToTangentSpace, L3)); // L3, light
// Add range to the light, attenuation
Out.Light.w = saturate( 1 – dot(L / LightRange, L / LightRange));
Out.Light2.w = saturate( 1 – dot(L2 / LightRange2, L2 / LightRange2));
Out.Light3.w = saturate( 1 – dot(L3 / LightRange3, L3 / LightRange3));
Nothing new here right? Do the same thing you did in tutorial 17 three times!
Let’s take a look at the pixel shader. First of all we need to calculate the light direction for each light:
// Get light direction/view from vertex shader output
float3 LightDir = normalize(L.xyz); // L
float3 LightDir2 = normalize(L2.xyz); // L2
float3 LightDir3 = normalize(L3.xyz); // L3
Then we calculate the diffuse, specular and self-shadowing, using the parts/equations in 18.1.
float D = saturate(dot(N, LightDir));
float D2 = saturate(dot(N, LightDir2));
float D3 = saturate(dot(N, LightDir3));
// Self shadow – used to avoid light artifacts
float Shadow = saturate(4.0 * LightDir.z);
float Shadow2 = saturate(4.0 * LightDir2.z);
float Shadow3 = saturate(4.0 * LightDir3.z);
float3 R = normalize(2 * D * N – LightDir); // R
float3 R2 = normalize(2 * D2 * N – LightDir2); // R
float3 R3 = normalize(2 * D3 * N – LightDir3); // R
float S = min(pow(saturate(dot(R, ViewDir)), 3), Color.w);
float S2 = min(pow(saturate(dot(R2, ViewDir)), 3), Color.w);
float S3 = min(pow(saturate(dot(R3, ViewDir)), 3), Color.w);
Now we got what we need in our light equation. Let’s use 18.2 to implement find our final color:
// calculate three point lights:
// Ambient + Shadow*((Diffuse + Specular)*Attenuation in L.w);
float4 light1final = Shadow*((Color * D * LightColor + S*LightColor) * (L.w));
float4 light2final = Shadow2*((Color * D2 * LightColor2 + S2*LightColor2) * (L2.w));
float4 light3final = Shadow3*((Color * D3 * LightColor3 + S3*LightColor3) * (L3.w));
return 0.1 * Color + light1final + light2final + light3final;
And thats it! So, basically, you need to calculate our light equation n-times to get n lights.
Using the shader
There ain’t much difference to using this shader and the one we implemented in tutorial 17. But this time we need to pass the light position, light color and light range for each of the three lights we are implementing, instead of just one:
// Set the light positions for each of the lights
// Set the light range and color for each of the lights
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.
Now, go ahead and look at the source and play around with it so you learn it by practice. If you got any questions or feedback, please leave a comment or cantact me!
Download: Executable + Source