This post is going to show an implementation of deferred shading using SGL.
What is Deferred Shading?
Well first lets talk about forward shading. Forward shading is when light calculations are done for every fragment in the scene. Now the key problem here is that the calculations are done for every fragment even if that fragment doesn’t end up on the screen. In a complex scene, with a lot of overlapping objects (called depth complexity), this can add up to a lot of wasted lighting calculations.
This is where deferred shading comes in. Deferred shading seperates the rendering of geometry and the light calculations. There are two passes, the first is the geometry pass, where the information needed for lighting is calculated and stored away in textures. The next pass is the lighting pass, where the calculations actually take place. The cool part is that, because of the geometry pass, only fragments that have passed the depth test are store in the textures, meaning there are no wasted light calculations.
Time for some code!
I’ll just cover the SGL code required for deferred shading and not context creation. I’m using GLFW and Visual Studio 2013.
So what do we need? Two shader programs, one for each pass. And a new class to hold the output texture data from the geometry pass, this is called the GBuffer.
We’re also going to be using some 2D rendering for visualization.
Let’s take a look at the init function, then we’ll look at the GBuffer class.
Alright, so pretty simple. We load a shader program from a set of files and bind vertex attributes to their locations.
Something important here is the bindFragOutput function. The GBuffer (shown below) uses Multiple Render Targets to store data from the fragment shader. The output textures in the GBuffer are attached to specfic output locations in the fragment shader, its important to explicitly set the outputs so that everything matches.
The GBuffer class is a container for the output textures from the geometry pass shader. We will be using textures to store position, normal and diffuse information from the geometry pass.
The GBuffer contains a underlying frame buffer object that we will render, along with 4 textures that will be used to store data shading data from the fragment shader.
The depth buffer is required as we need to do depth testing.
The magic happens where textures are added to the frame buffer as a render target.
SGL makes it easy to added a texture as a render target.
Last, the function bindForWriting() simply binds the frame buffer in draw mode (drawing to the output render targets). And bindForReading simply binds all of the render textures to their corresponding positions.
Thats all the setup complete, lets look at the rendering code.
The first phase is to collect the required geometry data and store in the G-buffer, so we’ll have a function to perform the first pass.
Wait a second?! Where is the code to render to multiple textures?
It’s right here:
Thats it. The only thing left is to make sure that the appropriate data is written to its output in the fragment shader.
Our main render function looks like this:
The next part is really easy. we just render to a full screen quad. We’ll will call this the lighting pass (because in this phase we do the light calculations).
Here we bind the G-buffer in read mode (binding our textures), set the required textures to the light pass shader and then draw a full screen quad to access all the pixels store in the G-buffer’s frame buffer.
In the light pass fragment shader we do a fairly common diffuse light lighting calculation using the provided textures.
Our main render function:
As an aside, lets render all the textures at the sametime to get a idea of whats happening.
Here’s what the different output targets look like: