There are generally two methods for dealing with this. Nowadays, they are called forward rendering and deferred rendering.
Forward rendering
Render each object once for every light that affects it. This includes the ambient light. You use an additive blend mode (glBlendFunc(GL_ONE, GL_ONE)
), so each light's contributions are added to each other. Since the contribution of different lights are additive, the framebuffer eventually gets the valu
You can get HDR by rendering to a floating-point framebuffer. You then take a final pass over the scene to down-scale the HDR lighting values to a visible range; this would also be where you implement bloom and other post-effects.
A common performance enhancement for this technique (if the scene has a lot of objects) is to use a "pre-pass", where you render all of the objects without drawing anything to the color framebuffer (use glColorMask
to turn off color writes). This just fills in the depth buffer. This way, if you render an object that is behind another, the GPU can quickly skip those fragments. It still has to run the vertex shader, but it can skip the typically more expensive fragment shader computations.
This is simpler to code and easier to visualize. And on some hardware (mainly mobile and embedded GPUs), it can be more efficient than the alternative. But on higher-end hardware, the alternative generally wins out for scenes with a lot of lights.
Deferred rendering
Deferred rendering is a bit more complicated.
The lighting equation you use to compute the light for a point on a surface uses the following surface parameters:
- Surface position
- Surface normals
- Surface diffuse color
- Surface specular color
- Surface specular shininess
- Possibly other surface parameters (depending on how complex your lighting equation is)
In forward rendering, these parameters get to the fragment shader's lighting function either by being passed directly from the vertex shader, being pulled from textures (usually through texture coordinates passed from the vertex shader), or generated from whole cloth in the fragment shader based on other parameters. The diffuse color may be computed by combining a per-vertex color with a texture, combining multiple textures, whatever.
In deferred rendering, we make this all explicit. In the first pass, we render all of the objects. But we don't render colors. Instead, we render surface parameters. So each pixel on the screen has a set of surface parameters. This is done via rendering to off-screen textures. One texture would store the diffuse color as its RGB, and possibly the specular shininess as the alpha. Another texture would store the specular color. A third would store the normal. And so on.
The position is usually not stored. It is instead reconstituted in the second pass by math that's too complex to get into here. Suffice it to say, we use the depth buffer and the screen-space fragment position as the input to figure out the camera-space position of the point on a surface.
So, now that these textures hold essentially all of the surface information for every visible pixel in the scene, we start rendering full-screen quads. Each light gets a full-screen quad render. We sample from the surface parameter textures (and reconstitute the position), then just use them to compute the contribution of that light. This is added (again glBlendFunc(GL_ONE, GL_ONE)
) to the image. We keep doing this until we run out of lights.
HDR again is a post-process step.
The biggest downside to deferred rendering is antialiasing. It requires a bit more work to antialias properly.
The biggest upside, if your GPU has a lot of memory bandwidth, is performance. We only render the actual geometry once (or 1 + 1 per light that has shadows, if we're doing shadow mapping). We never spend any time on hidden pixels or geometry that isn't visible after this. All of the lighting pass time is spent on things that are actually visible.
If your GPU doesn't have lots of memory bandwidth, then the light pass really can start to hurt. Pulling from 3-5 textures per screen pixel isn't fun.