Deferred Rendering in Modern OpenGL – Part 2

During the first part of this little series, we looked at the top-level overview of Deferred Rendering, it’s strengths and weaknesses. Sorry for the massive delay by the way, i’ve been caught up with work. Anyways this part will be more like a devlog where i’ll just be talking about the implementation process and thoughts on areas of improvement.

So to start off i created a new Framebuffer object with multiple render targets, with internal formats as follows :

  1. Position  : GL_RGB16F
  2. Normals : GL_RGB16F
  3. Albedo + Specular : GL_RGBA16F
  4. Depth : GL_DEPTH_COMPONENT

Note that the depth attachment is NOT a Renderbuffer Object but a Framebuffer Attachment like the others. This is because Renderbuffer Objects can only be written to and not sampled later on. This is a limitation for us, especially since a Depth texture is useful for certain Post-Process effects such as SSAO.

In the Framework i’ve created, each Model is stored in a container named ModelInstance which stores the Model, it’s transform as well as a Shader. For Deferred Rendering the Shader is NOT the Lighting Shader, but the G-Buffer Shader, and it stays the same for all Models unless a certain Model requires a specific Shader.

#version 440 core

layout (location = 0) in vec3 position;
layout (location = 1) in vec2 texcoord;
layout (location = 2) in vec3 normal;

uniform mat4 Model;
uniform mat4 Projection;
uniform mat4 View;

out vec2 TexCoord;
out vec3 FragPos;
out vec3 Normal;

void main()

{
       vec4 worldPos = Model * vec4(position, 1.0f);
       FragPos = worldPos.xyz;
       gl_Position = Projection * View * worldPos;
       TexCoord = texcoord;
       Normal = normalize(mat3(transpose(inverse(Model))) * normal);
}

Figure 1 : G-Buffer Vertex Shader

Vertex Shader isn’t anything special. Simply takes in the Models’ MVP matrices (individually for now), calculates World Space Position of current vertex, transforms Normal into World Space and passes through the TexCoord to the Fragment Shader.

#version 440 core

layout (location = 0) out vec3 g_Position;
layout (location = 1) out vec3 g_Normal;
layout (location = 2) out vec4 g_AlbedoSpecular;

in vec3 FragPos;
in vec2 TexCoord;
in vec3 Normal;
in float FragDepth;

uniform sampler2D Texture_Diffuse1;
uniform sampler2D Texture_Specular1;

void main()
{
       g_Position.rgb = FragPos;
       g_Normal = normalize(Normal);
       g_AlbedoSpecular.rgb = texture(Texture_Diffuse1, TexCoord).rgb;
       g_AlbedoSpecular.a = texture(Texture_Specular1, TexCoord).r;
 }

Figure 2 : G-Buffer Fragment Shader

If you recall the little GLSL snippet from the first part, this is the Shader it belongs to. All this does is merely output the World Space Position, Normal, Albedo and Specular, which is everything required to perform lighting, into their respective Render Targets.

To actually render to the G-Buffer all that is required is to simply iterate through the list of Models and Drawing them using the G-Buffer Shader like so;

BindGBuffer();
foreach Model
{
       GBufferShader.Bind();
       BindTextures();
       Model.Draw();
}

Figure 3 : Pseudo Code of Draw Loop

Afterwards it’s only a matter of binding a Lighting Shader of your choice, binding the necessary textures from the G-Buffer pass and Drawing everything onto a fullscreen quad with Default Framebuffer or another FBO if you wish to do some Post-Process.

void main()
{
       vec3 FragPos = texture(g_Position, TexCoords).rgb;
       float FragDepth = LinearizeDepth(texture(g_Depth, TexCoords).r);
       Normal = texture(g_Normal, TexCoords).rgb;
       vec3 Albedo = texture(g_AlbedoSpecular, TexCoords).rgb;
       float Specular = texture(g_AlbedoSpecular, TexCoords).a;

       // Perform Lighting as usual
}

Figure 4 : Simplified main method of  Lighting Fragment Shader

And there you have it! Deferred Rendering! Not so hard now is it? Now let’s take a look at some screenshots of each texture of the G-Buffer, along with one extra!

Terminus 2016-03-05 22-14-06-92

Image 1 : Final Composition

Terminus 2016-03-05 22-13-54-82

Image 2 : Position

Terminus 2016-03-05 22-13-56-15

Image 3 : Normals

Terminus 2016-03-05 22-13-57-15

Image 4 : Albedo

Terminus 2016-03-05 22-14-02-98

Image 5 : Depth

Terminus 2016-03-05 22-14-04-47

Image 6 : SSAO (Surprise!)

SSAO is something i recently implemented and it will definitely get it’s own post in the future as it is a technique that really makes use of the G-Buffer. I had to leave the Specular Texture out as this particular scene used full Specular on all the Models so i ended up with a plain white image.

Further Improvements

This implementation is by no means perfect, or even good. Remember i am still a beginner at this and this post was only meant to give you an understanding of how you could go about implementing your own Deferred Renderer. Anyways as for improvements, one thing i am considering is to reconstruct World Space Positions using the Depth Buffer, because this will allow us to remove the Position Buffer from the G-Buffer thus making it smaller. Matt Pettineo from Ready-At-Dawn has some great tutorials about it on his blog.

After writing the last post, i went ahead and implemented some basic PBR into the engine using a Microfacet BRDF and Image-Based Lighting which will get it’s post in the future. And for PBR to work as intended, each Models materials require a Roughness and Metalness value (A workflow that uses these two values is called a Metalness Workflow, with it’s alternative being Specular Workflow). Now if i am to use these values in the Lightning Shader they have to be stored in the G-Buffer as well, and since these two values are merely floats,one of them can be stored in place of Specular inside the Albedo Texture’s w component, and another can be stored inside of the Normal Textures w component. Another addition to the G-Buffer would be a ‘Velocity Buffer’ which will is used for Motion Blur.

Okay, that is a LOT of improvements! They will surely take a while but i will definitely get around creating posts for each of them along with some of the other things i promised. Until then, Happy Coding Folks!

Advertisements

One thought on “Deferred Rendering in Modern OpenGL – Part 2

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s