How to add countless lights in framebuffer

371 Views Asked by At

Following the learnopengl tutorial (https://learnopengl.com/Advanced-Lighting/Deferred-Shading) the author leaves fixed the amount of light (32 lights) as shown by the GLSL:

 #version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gAlbedoSpec;

struct Light {
    vec3 Position;
    color;
};
const int NR_LIGHTS = 32;
uniform Light lights [NR_LIGHTS];
uniform vec3 viewPos;

void main ()
{
    // retrieve data from G-buffer
    vec3 FragPos = texture (gPosition, TexCoords) .rgb;
    vec3 Normal = texture (gNormal, TexCoords) .rgb;
    vec3 Albedo = texture (gAlbedoSpec, TexCoords) .rgb;
    float Specular = texture (gAlbedoSpec, TexCoords) .a;

    // then calculate lighting as usual
    vec3 lighting = Albedo * 0.1; // hard-coded ambient component
    vec3 viewDir = normalize (viewPos - FragPos);
    for (int i = 0; i <NR_LIGHTS; ++ i)
    {
        // diffuse
        vec3 lightDir = normalize (lights [i] .Position - FragPos);
        vec3 diffuse = max (dot (Normal, lightDir), 0.0) * Albedo * lights [i] .Color;
        lighting + = diffuse;
    }

    FragColor = vec4 (lighting, 1.0);
}

And when it comes to applying the lights:

glBindFramebuffer (GL_FRAMEBUFFER, 0);

        // 2. lighting pass: calculate lighting by iterating over screen filled quad pixel-by-pixel using the gbuffer's content.

        glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        shaderLightingPass.use ();
        glActiveTexture (GL_TEXTURE0);
        glBindTexture (GL_TEXTURE_2D, gPosition);
        glActiveTexture (GL_TEXTURE1);
        glBindTexture (GL_TEXTURE_2D, gNormal);
        glActiveTexture (GL_TEXTURE2);
        glBindTexture (GL_TEXTURE_2D, gAlbedoSpec);
        // send light relevant uniforms
        for (unsigned int i = 0; i <lightPositions.size (); i ++)
        {
            shaderLightingPass.setVec3 ("lights [" + std :: to_string (i) + "] .Position", lightPositions [i]);
            shaderLightingPass.setVec3 ("lights [" + std :: to_string (i) + "] .Color", lightColors [i]);
            // update attenuation parameters and calculate radius
            const float constant = 1.0; // note that we do not send this to the shader, we assume it is always 1.0 (in our case)
            const float linear = 0.7;
            const float quadratic = 1.8;
            shaderLightingPass.setFloat ("lights [" + std :: to_string (i) + "] .Linear", linear);
            shaderLightingPass.setFloat ("lights [" + std :: to_string (i) + "] .Quadratic", quadratic);
        }
        shaderLightingPass.setVec3 ("viewPos", camera.Position);
        // finally render quad
        renderQuad ();

but I would like to be able to add as many lights as I want, because my project will have countless lights (laser guns, bonfire, blast), so I made some changes:

GLSL:

uniform Light light;
uniform vec3 viewPos;

void main()
{             
    // retrieve data from gbuffer
    vec3 FragPos = texture(gPosition, TexCoords).rgb;
    vec3 Normal = texture(gNormal, TexCoords).rgb;
    vec3 Diffuse = texture(gAlbedoSpec, TexCoords).rgb;
    float Specular = texture(gAlbedoSpec, TexCoords).a;

    // then calculate lighting as usual
    vec3 lighting  = Diffuse * 0.1; // hard-coded ambient component
    vec3 viewDir  = normalize(viewPos - FragPos);

        // diffuse
        vec3 lightDir = normalize(light.Position - FragPos);
        vec3 diffuse = max(dot(Normal, lightDir), 0.0) * Diffuse * light.Color;
        // specular
        vec3 halfwayDir = normalize(lightDir + viewDir);  
        float spec = pow(max(dot(Normal, halfwayDir), 0.0), 16.0);
        vec3 specular = light.Color * spec * Specular;
        // attenuation
        float distance = length(light.Position - FragPos);
        float attenuation = 1.0 / (1.0 + light.Linear * distance + light.Quadratic * distance * distance);
        diffuse *= attenuation;
        specular *= attenuation;
        lighting += diffuse + specular;        

    FragColor = vec4(lighting, 1.0);
}

And then I passed the values one by one and rendered a quad:

for (unsigned int i = 0; i < lightPositions.size(); i++)
        {
            shaderLightingPass.use();
            shaderLightingPass.setInt("gPosition", 0);
            shaderLightingPass.setInt("gNormal", 1);
            shaderLightingPass.setInt("gAlbedoSpec", 2);
            shaderLightingPass.setVec3("light.Position", lightPositions[i]);
            shaderLightingPass.setVec3("light.Color", lightColors[i]);

            const float constant = 1.0; // note that we don't send this to the shader, we assume it is always 1.0 (in our case)
            const float linear = 0.7;
            const float quadratic = 0.08;
            shaderLightingPass.setFloat("light.Linear", linear);
            shaderLightingPass.setFloat("light.Quadratic", quadratic);
            shaderLightingPass.setVec3("viewPos", camera.Position);

            renderQuad();

            glUseProgram(-1);

        }

and also added a new shader to render the framebuffer on the screen:

screenShader.use();
renderQuad();

but my code renders only the first light: Result could anyone tell me what I am doing wrong and how to add the lights in the end result?

2

There are 2 best solutions below

3
On

Please include code like below

void renderDeferredPass(int i)
{
glUseProgram(ps[Passes::Deferred]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, g_fbo);
glDepthMask(GL_TRUE);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDisable(GL_BLEND);
//mat4 model = glm::scale(mat4(1.0f), vec3(3.1f, 3.1f, 3.1f));
   model = glm::translate(mat4(1.0f), vec3(-150.0f, -600.0f, -800.0f+camera));
   model = glm::rotate(model, 30.0f, vec3(0.0f, 1.0f, 0.0f));

    mat4 view = glm::lookAt(glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 0.0, -5.0), glm::vec3(0.0, 1.0, 0.0));

    glUniformMatrix4fv(modelLocation, 1, GL_FALSE, &model[0][0]);
    glUniformMatrix4fv(viewLocation, 1, GL_FALSE, &view[0][0]);
    glUniformMatrix4fv(projLocation, 1, GL_FALSE, &projection[0][0]);
    glUniform1i(textureLocation, 0);

    quad->Render();

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
    glUseProgram(0);
    glDepthMask(GL_FALSE);
    glDisable(GL_DEPTH_TEST);
   } 

And

 void renderLightPass()
{
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_ONE, GL_ONE);

    glUseProgram(ps[Passes::LightPass]);
    glBindVertexArray(quadVAO);
    bindUbo();

    for (unsigned int i = 0; i < NUM_GBUFFER_TEXTURES; i++) 
     {
           glActiveTexture(GL_TEXTURE1 + i);
           glBindTexture(GL_TEXTURE_2D, 
           g_textures[POSITION_TEXTURE + i]);
      }


    glUniform1i(mapLocations[POSITION_TEXTURE], 1);
    glUniform1i(mapLocations[DIFFUSE_TEXTURE], 2);
    glUniform1i(mapLocations[NORMAL_TEXTURE], 3);
    glUniform1i(mapLocations[TEXCOORD_TEXTURE], 4);

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0);

    glUseProgram(0);
    glBindVertexArray(0);

    glEnable(GL_DEPTH_TEST);
    glBindTexture(GL_TEXTURE_2D, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

   } 

And your draw function should look like

void display()
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glGenerateMipmap(GL_TEXTURE_2D);
    glEnable(GL_MULTISAMPLE);

    //for (int i = 0; i < quad->m_Entries.size(); i++)
        {
          renderDeferredPass(0);
          renderLightPass();
        }

    glutSwapBuffers();
    glutPostRedisplay();
    } 

For complete implementation refer following

https://github.com/PixelClear/Deferred-renderer

I have above code where we store light information in SSBO so this demo has 32 lights but can easily extended to many.

1
On

The problem is due to the fixed "ambient" term being repeated for the # of lights that overlap the scene.

The shader contains:

vec3 lighting  = Diffuse * 0.1; // hard-coded ambient component

This effectively re-adds albedo everytime a light overlaps.

The old code had the following sum:

vec3 lighting = Diffuse * 0.1;
foreach (Light l : lights)
    lighting += Diffuse * (l's diffuse lighting)

But now with additive blending you have:

foreach (Light l : lights)
    lighting += Diffuse * 0.1;
    lighting += Diffuse * (l's diffuse lighting)

As such you got the overbrightening of ambient in https://i.ibb.co/gMBtM6c/With-Blend.png

To fix this you need to separate the (Diffuse * 0.1) term into a separate shader. You would have 1 draw call to apply ambient, then n draw calls for n lights.

The algorithm on the C++ side would then look like: Make sure you have additive blend still.

  1. Clear Screen
  2. Set Ambient shader, Draw Quad.
  3. Set Light shader, Do your lighting loop and Draw n Quads for n lights.

EDIT: Also it looks like you aren't reading the right Albedo texture based off of your screenshots. It looks like you are reading the position buffer based off of the colors you're getting.