How to handle multiple lights?

Started by
7 comments, last by Zylann 9 years ago

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?

Advertisement
You can either send all the lights if you know the maximum count ahead of time. Or you can render the mesh multiple time using additive blending for light 1 to n.

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);

}

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.

o3o

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.

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

I recommend using deferred lighting if you want A LOT of lights.

I recommend trying deferred rendering just because its more fun to do.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator

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.

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.

This topic is closed to new replies.

Advertisement