Multiple lights shader

I wrote in this forum some days ago about a point light normal map shader. (This is the post)

That post was about a single point light, and thanks to your help I got it working fine. Essentially, the light computation code is this:

// input.PosWorld how has the Position of this Pixel in World Space
float3 lightdir = normalize(input.PosWorld - currentLightPosition); // this is now the direction of light for this pixel

// Introduce fall-off of light intensity
float diffuseLighting = saturate(dot(normal, -lightdir));
diffuseLighting *= (LightDistanceSquared / dot(currentLightPosition - input.PosWorld, currentLightPosition - input.PosWorld));

// Compute lighting
float lightAmount = saturate(dot(normal, -lightdir));
input.Color.rgb *= AmbientColor + (lightAmount * LightColor * diffuseLighting);

Now, my concern is: how to compute multiple lights? As a C# progammer I thought I could just pass as argument an array of Vector3 with each light I want to compute and then sum multiply each result, in a way similar to this:

float4 PS(VertexShaderOutput input) : COLOR0
{
	// Look up the normalmap value
	float3 normal = normalize((2 * NormalTexture.Sample(NormalSampler, input.TexCoords)) - 1);
	normal.y *= -1;

	// Look up the texture value
	float4 tex = ScreenTexture.Sample(TextureSampler, input.TexCoords);

	// Compute multiple lighting sources
	for (uint i = 0; i < LightPositionCount; ++i)
	{
		float3 currentLightPosition = LightsPositions[i];

		// input.PosWorld how has the Position of this Pixel in World Space
		float3 lightdir = normalize(input.PosWorld - currentLightPosition); // this is now the direction of light for this pixel

		// Introduce fall-off of light intensity
		float diffuseLighting = saturate(dot(normal, -lightdir));
		diffuseLighting *= (LightDistanceSquared / dot(currentLightPosition - input.PosWorld, currentLightPosition - input.PosWorld));

		// Compute lighting
		float lightAmount = saturate(dot(normal, -lightdir));
		input.Color.rgb *= AmbientColor + (lightAmount * LightColor * diffuseLighting);
	}

	return input.Color * tex;
}

Where LightPositions is an array defined like this:

uint LightPositionCount;
float3 LightsPositions[20]

I cannot test this shader because it gives a compiler error: Compiled shader code uses too many arithmetic instruction slots (308). Max allowed by target ps_2_0 is 64. (I don’t know why it says ps_2_0, since at the end of the file I put PixelShader = compile ps_4_0_level_9_1 PS();)

I also tried to keep the single point light shader and apply it multiple times from code, essentially I did the lights loop by code and not in the shader. But I don’t know how to “sum” all the results to the final render target.

I really don’t know how to get rid of this.
What to do you suggest to apply the normal map when you have multiple shadows?

Thanks!

Have a look at this

2 Likes

If you do mulitple passes you basically use the result of the pass before as a target for the next and do the usual blending in the shader (you can just use 2 textures and use them in an alternating manner)

you main error you told in the first post comes, because the shader optimizer actually unrolls your loop (which is faster during runtime) and therefore you hit the instruction limit for that shader. You can now either raise the shader version (if that’s possible for your target devices) to get a higher instruction limit (not sure why it expects 2_0 for you, do you use HiDef profile or reach?), lower the amount of lights (so the unrolled loop results in less instructions) or tell the optimizer to keep the loop as a loop:

[loop]
for (uint i = 0; i < LightPositionCount; ++i)
{
}

never tried this myself but it should work … even if I wonder if/how the optimizer is unrolling the loop when there is no fixed length supplied … maybe someone knows here

1 Like

Thanks a lot guys! I tried the muilti-pass rendering and I worked! What I was missing was the Additive BlendState.

This is a sample of the result implemented in the game I’m developing, Mark in DeDark:

Again, thanks!