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:
Fig 18.1
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:
float4 LightColor;
float4 LightColor2;
float4 LightColor3;
float3 vecLightPos;
float3 vecLightPos2;
float3 vecLightPos3;
float LightRange;
float LightRange2;
float LightRange3;
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.
// diffuse
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);
// reflection
float3 R = normalize(2 * D * N – LightDir); // R
float3 R2 = normalize(2 * D2 * N – LightDir2); // R
float3 R3 = normalize(2 * D3 * N – LightDir3); // R
// specular
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
effect.Parameters["vecLightPos"].SetValue(vLightPosition);
effect.Parameters["vecLightPos2"].SetValue(vLightPosition2);
effect.Parameters["vecLightPos3"].SetValue(vLightPosition3);
// Set the light range and color for each of the lights
effect.Parameters["LightRange"].SetValue(3.0f);
effect.Parameters["LightColor"].SetValue(vLightColor);
effect.Parameters["LightRange2"].SetValue(3.0f);
effect.Parameters["LightColor2"].SetValue(vLightColor2);
effect.Parameters["LightRange3"].SetValue(3.0f);
effect.Parameters["LightColor3"].SetValue(vLightColor3);
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.
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!
YouTube – XNA Shader programming, Tutorial 18 – Multiple Point lights
Download: Executable + Source
Can you post a code for put on it "n" lights?Becouse i\’ve try to modify you\’re code with adding some array for create [100] lights.But it give me an error: there are more value that registry.Can you post the code?Thanks.
If you want that many lights, this approach will be too slow. You should take a look at the following tutorial by Catalin Zima to implement that many lights:http://www.ziggyware.com/readarticle.php?article_id=155🙂
That site is down. 😦
Hello, I followed most of the serie and I asked myself the following question… Is it possible to create an array of float3 in the shader without knowing the size of it. Obviously, it would be sent in the C# part as an effetParameter…
any tutorial for multiple point lights for xna 4.0?