Search code examples
arraysopenglstructglsl

glsl multi light, best practice of passing data (array of structs?)


working myself from step to step I am now trying to figure out more about multi lights in glsl. I read some tutorials so far but none seems to have THE answer for this.

Lets say I have such a struct for my lighting:

struct LightInfo                                                           
{  
    vec4 LightLocation;                                                                    
    vec3 DiffuseLightColor;
    vec3 AmbientLightColor;
    vec3 SpecularLightColor;
    vec3 spotDirection;
    float AmbientLightIntensity;
    float SpecularLightIntensity;
    float constantAttenuation;
    float linearAttenuation;
    float quadraticAttenuation;
    float spotCutoff;
    float spotExponent;
};
uniform LightInfo gLight;

my first idea would be to make it something like

uniform LightInfo gLight[NumLights];

but then I read a lot about that passing data that way to the shader wouldn't work, since it can't get the location of that. Now I have to admit that I didn't try it myself yet, but I found a couple of pages mentioning this, so it's probably not that wrong - or is this maybe just outdated information?

The other idea would be to make it just:

uniform[NumOfArgs]

and split it in the shader again, but if I take my example struct above I have an immense huge array very soon, and taking the information out of it with a for loop probably will be quite expensive too- and that only if I want to use a similar number of lights like the max of 8 when using gl_LightSource - while I wanted to avoid using that because of the advantage having an own struct with all information needed at once.

Of course not any light in question would require that many parameters, but any light COULD require them (and even if stripping it quite some it will grow very soon also). Yet again I could use some qsort first to determine the closest lights, then limiting the maxlights to something like 3 (something which is also suggested on many places), but here again I have to say that I expect a bit more from nowadays glsl and modern hardware although there is no contradiction in using this as well, unrelated to the chosen solution.

So my question now, what's best practice here, what's really fast? Or should I stay with gl_LightSource and passing the additional information then via some uniform array? Although this doesn't seem to make more sense to me either.


Solution

  • The idea of a light struct is just fine. For forward rendering - passing all lights into the one shader which processes your actual geometry - an array is just fine.

    1. You may have an array of structs as uniforms (uniform LightInfo gLight[NumLights], where NumLights is compile-time constatnt), but arrays are not so different to just declaring uniform LightInfo gLight0, gLight1....

      You get the uniform location via the full name, eg:

      glGetUniformLocation(program, "gLight[3].spotExponent")
      

      Note that glGetActiveUniform will return just the string with element zero but the size will give the number of elements.

    2. Uniform buffers will be important with lots of lights and attributes. You can store all the data for the structs on the GPU, so it doesn't get sent every time with individual calls to glUniform*. You can use glMapBuffer to modify parts of the buffer if the rest doesn't need changing.

      Be very aware of how the structs and arrays get packed (it's not always intuitive)! Related issues occur in non-uniform/uniform block cases too.

      See: Sub-section 2.15.3.1.2 - Standard Uniform Block Layout

      To get the byte offset from the beginning of the block, use the GL_UNIFORM_OFFSET​ enum

      See: Uniform Buffer Object

    3. Elements are aligned to whopping big 16 byte boundaries (vec4 size). To make that struct more efficient, you should pair the vec3s with the floats.

    You're right, if you have more lights than there are in the shader you'll have to chop and choose. Lights that are close are important, but you might also want to prioritize lights in the direction you're facing (those whose area of influence touches the viewing volume formed by the projection matrix) and bigger/brighter lights (eg. sun/directional).

    Ultimately if you have too many lights this method ceases to work. Your next step is to swap to deferred shading, which brings with it a few more issues (eg. blending/transparency).