Sign in to follow this  
Zylann

How to handle multiple lights?

Recommended Posts

Hello,

 

I'm currently adding lighting to my game engine, and it's already fine with 1 light (directional, point or spot...).

 

However, how can I handle multiple lights efficiently?

 

I know I can write a shader with uniform arrays where each element is a light struct, or multiple arrays or primitives.

Then in a for loop, each light would contribute to the final pixel color.

uniform Light u_Lights[32];
//...
for(int i = 0; i < 32; ++i)
{
   outputColor = /* light contribution */
}

However, if I want to support N lights, would I have to write my shaders so they take N lights in those uniform arrays?

If I create 2 lights only, will I have to loop through 32 lights just to avoid branching? Unless branching is OK if N remains constant?

Then, what if N can change at runtime?

Do I have to recompile every shader that uses lights just to reset the number of lights as a compile-time constant?

 

That's a lot of questions, but they generally refer to the same problem: compile-time VS runtime performance.

What would be the best, general purpose approach?

Share this post


Link to post
Share on other sites

make a unifom for getting light data in the size of max light numbers then make a bool uniform in the size of max light numbers to know which lights are enabled then calculate only the lights that are enabled and finally add all the colors from different lights and multiply it by texture color.

#define Max_Light 32
uniform Light Lights[Max_Light];
uniform bool Light_Enabled[Max_Light];

...

void main()
{

...
vec4 FinalColor=vec4(0,0,0,0);
for (int I=0;I<Max_Light;I++)
if (Light_Enabled[I])
{

...

FinalColor+=calculated_light_color;
}

. . .

gl_Fragcolor=vec4(FinalColor.x*TexColor.x,FinalColor.y*TexColor.y,FinalColor.z*TexColor.z,FinalColor.w*TexColor.w);

}

Edited by IYP

Share this post


Link to post
Share on other sites

When it comes to branching, fragments will be processed in groups. If all fragments in a group branch the same way, everything is fine. If they do NOT branch the same way, EVERY fragment will calculate BOTH paths of the branch (so you wont get performance advantages here). So if there is coherence in branching, only the taken path computes, but if there is not coherence, both paths will compute.

 

I assume branches have some overhead even if theyre coherent (not sure), so you probably dont want too many, even if you have coherence.

 

However, something like:

 

process lights 0 to 3

if more than 4 lights

{

process lights 4 to 31

}

 

might be good if you have large areas with <=4 lights, and some spots with lots.

 

I dont have experience to confirm if this would be a good idea though, or what the usual approach is.

 

EDIT:

Though 31 lights would probably not be practical. If you need many lights, consider 'deferred rendering'. For forward rendering the light count will probably be small enough that you can just always loop through all N lights where N is constant.

Edited by Waterlimon

Share this post


Link to post
Share on other sites
Most of the time you'll manage with 8 positional lights, assuming you just pick the lights that actually affect the to be rendered renderable (which you can find out on the CPU side by simple sphere/sphere check between a object/ renderable and a light's radius).

Keep in mind that this might not work if you use "large renderables", for example, terrain should be cut into pieces, depending on your lighting approach.

Share this post


Link to post
Share on other sites

I recommend you implement 1 normal forward light, then implement shadow-mapping, then decide whether you want a limited number of accent lights (which can be done with a forward lighting array), or if you want lots of dynamic lights. In the latter case, you want to implement deferred lighting / deferred shading. 

Share this post


Link to post
Share on other sites

Ok, so I think I'm going to implement a deferred system.

After a few researches I found this post, which confirms the need for that technique.

 

I like the idea to separates passes, it makes shader code easier to maintain and "encapsulate".

However, it changes the way they need to be written (if I'm not mistaken?)

 

There seem to be various techniques, generally involving to smash attributes on framebuffers, and them transform them into the final pixel color through fragment shader passes.

I wonder what would be the best memory/performance tradeoff?

 

Having a data-based configurable pipeline could be a cool thing to implement too, because I try to be generic when building my engine.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this