1
\$\begingroup\$

So I've implemented deferred rendering in GLSL and OpenGL for my game engine. I want to blend together multiple lights, but the performance is a bit sub-par. How I'm doing it now is the following:

  1. Render all the objects in the scene to a G-buffer storing the position, color and normals
  2. Take that data and render a fullscreen quad for the sun.
  3. Enable additive blending with glEnable(GL_BLEND) and glBlendFunc(GL_SRC_ALPHA, GL_ONE)
  4. For ever additional light in the scene render a fullscreen quad and send the shader all the data for each light.

The lighting looks great, but the performance is bad. I have been under the impression that deferred rendering allows for very little performance loss when rendering additional lights, and it is possible to render a few thousand lights with relatively little performance hit. Im sure the way Im doing this isn't efficient at all, so I need a bit of input. The first thing that comes to mind is making it so I don't have to blend so many quads and just make a loop in the shader and only render 1 extra quad. I store all my light data in structs, and the only way I've seen to pass structs to GLSL is by setting each individual variable using glUniform.

Here is my current code:

void World::renderWorldAndLight()
    {
        //first thing we need to do is render the sun's shadow.
        if (Lighting.Sun.needsShadowUpdate == true)
        {
            Lighting.setupDepthImage();
            //render all the objects.
            for (int i = 0; i < objects.size(); i++)
                {
                    Lighting.renderLightPos(&Lighting.Sun);
                    objects[i].renderObjectForDepth();
                }
            Lighting.Sun.needsShadowUpdate = false;
        }

        //render the rest of the light shadows

        //reder the Gbuffer

        glBindFramebuffer(GL_FRAMEBUFFER, Lighting.gbuffer.frameBuffer);
        GLenum buffers[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3};
        glDrawBuffers(4, buffers);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glClearColor(0, 0, 0, 0);
        for (int i = 0; i < objects.size(); i++)
            objects[i].renderObjectWithProgram(Lighting.gbufferShader);

        //now we have the gbuffer, lets render the sun
        glBindFramebuffer(GL_FRAMEBUFFER, 0);

        glLoadIdentity();
        //render the buffers for debugging
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, ENGINE_WIDTH, ENGINE_HEIGHT, 0, -1, 1);
        glMatrixMode(GL_MODELVIEW);

        glDisable(GL_CULL_FACE);
        //glDisable(GL_BLEND);

        //render the full screen quad for the sun

        glUseProgram(Lighting.sunShader);

        glUniform1i(glGetUniformLocation(Lighting.sunShader,"normalMap"),0);
        glUniform1i(glGetUniformLocation(Lighting.sunShader,"albedoMap"),1);
        glUniform1i(glGetUniformLocation(Lighting.sunShader,"positionMap"),2);
        glUniform1i(glGetUniformLocation(Lighting.sunShader,"shadowMap"),3);
        glUniform3fv(glGetUniformLocation(Lighting.sunShader,"sunPos"), 1, &glm::vec3(-Lighting.Sun.position.x,-Lighting.Sun.position.y,-Lighting.Sun.position.z)[0]);
        glUniformMatrix4fv(glGetUniformLocation(Lighting.sunShader, "shadowMatrix"), 1, GL_FALSE, &Lighting.Sun.lightMatrix[0][0]);

        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, Lighting.gbuffer.textures[1]);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, Lighting.gbuffer.textures[2]);
        glActiveTexture(GL_TEXTURE2);
        glBindTexture(GL_TEXTURE_2D, Lighting.gbuffer.textures[0]);
        glActiveTexture(GL_TEXTURE3);
        glBindTexture(GL_TEXTURE_2D, Lighting.sunlight.depth);

        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        glBegin(GL_QUADS);
        glTexCoord2f(0, 1);
        glVertex2f(0, 0);
        glTexCoord2f(1, 1);
        glVertex2f(ENGINE_WIDTH, 0);
        glTexCoord2f(1, 0);
        glVertex2f(ENGINE_WIDTH, ENGINE_HEIGHT);
        glTexCoord2f(0, 0);
        glVertex2f(0, ENGINE_HEIGHT);
        glEnd();

        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE);
        glUseProgram(Lighting.lightShader);

        glUniform1i(glGetUniformLocation(Lighting.lightShader,"normalMap"),0);
        glUniform1i(glGetUniformLocation(Lighting.lightShader,"albedoMap"),1);
        glUniform1i(glGetUniformLocation(Lighting.lightShader,"positionMap"),2);

        glDisable(GL_DEPTH_TEST);

        for (int i = 0; i < Lighting.Lights.size(); i++)
        {

            glUniform3fv(glGetUniformLocation(Lighting.lightShader,"light"), 1, &Lighting.Lights[i].position[0]);
            glUniform3fv(glGetUniformLocation(Lighting.lightShader,"lightcolor"), 1, &Lighting.Lights[i].color[0]);

            glBegin(GL_QUADS);
            glTexCoord2f(0, 1);
            glVertex2f(0, 0);
            glTexCoord2f(1, 1);
            glVertex2f(ENGINE_WIDTH, 0);
            glTexCoord2f(1, 0);
            glVertex2f(ENGINE_WIDTH, ENGINE_HEIGHT);
            glTexCoord2f(0, 0);
            glVertex2f(0, ENGINE_HEIGHT);
            glEnd();

        }
        glEnable(GL_DEPTH_TEST);
    }

If there is any information, tips, tricks advice, it would be greatly appreciated. Thanks.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ As usual with optimisation, there are multiple things you can try and/or improve. Here are a few ideas. Use VBO's instead of the immediate mode (glVertex*). Don't render fullscreen quads for each light - determine what area will each light take up on the screen and use glViewport. Also, as always, inspect your code and make sure you make as little state changes as possible. \$\endgroup\$
    – NPS
    May 8, 2014 at 1:56
  • \$\begingroup\$ @NPS I've tried to keep the state changes to a minimum, and I'm also using display lists which turned out to have better performance than VBOs for me. \$\endgroup\$
    – BlueSpud
    May 8, 2014 at 2:04
  • \$\begingroup\$ That last part is surprising but ok, good. Still, implement the scissor test - as glampert said it's probably the main issue. \$\endgroup\$
    – NPS
    May 8, 2014 at 12:05

1 Answer 1

2
\$\begingroup\$

One thing you should avoid for sure is to query the uniform locations every frame with glGetUniformLocation. This might be hurting the performance a little bit. Cache the locations in any data structure of your choice or plain variables once the shader program is created.

Also you've mentioned that for each light a fullscreen quad is drawn. This is likely to be the main frame-rate killer, because you are probably wasting a lot of fragment processing. Imagine that you have a point light that is affecting half of the screen. 50% of the fragments being processed are not going to impact the visual appearence of the scene, but they are still being processed all the way.

One approach to tackle this is to apply a scissor testing to the lights bounds and 'cull' the area affected to minimize wasted fragments. Eric Lengyel discusses an algorithm for a similar use with stencil-shadows in here. The same algo can be applied in your case. This article is also presented in his book. I recommend you take a look.

\$\endgroup\$
4
  • \$\begingroup\$ Wouldn't using a stencil buffer require the whole scene to be rendered again after rendering the g-buffer? \$\endgroup\$
    – BlueSpud
    May 7, 2014 at 21:35
  • 1
    \$\begingroup\$ The scissors optimisation has nothing to do with the stencil buffer. It uses glScissor to disable rendering to a given area of the screen. \$\endgroup\$
    – glampert
    May 7, 2014 at 22:06
  • 1
    \$\begingroup\$ @BlueSpud: It is worth mentioning that defining the light bounds using a scissor test will produce a rectangular region. It is not a true proxy for light volume coverage (thus does not completely minimize unnecessary fragment processing) but it definitely avoids fooling with the stencil buffer. \$\endgroup\$ May 8, 2014 at 15:47
  • \$\begingroup\$ @AndonM.Coleman Then why not just render some low poly spheres instead of quads so almost no fragments are wasted. Wouldn't that be much more efficient than using glScissor? \$\endgroup\$
    – BlueSpud
    May 8, 2014 at 18:50

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .