Sunday 26 June 2016

Normal mapping (part 31)

Okay, we're taking a quick detour. I've been wanting to add this to the engine for a little while now but just didn't get around to it yet. As I was adding a model to the scene that had some normal maps I figured, why not...

Now I'll be the first to admit I'm not the greatest expert here. The last time I messed around with normal mapping was in the mid 90ies and that was completely faking it on the CPU because an 80386 didn't have the umph to do this.

The idea behind normal mapping is simple. The normal for the surfaces are the key thing that determines the lighting of our surface. It allows us to determine the angle at which light hits the surface and thus how to shade it. Now if there is a grove in our surface we need to introduce additional complexity to our model to properly light this. Take a brick wall for instance, we would really need to model each and every brick with all its jagged edges, to create believable lighting of the wall.

Normal mapping allows us to approximate this by still using a flat surface but adding a texture map that contains all the normals for the surface. So take our brick wall, at the edges of our bricks our normals won't be pointing outwards but sideways around the contours of the bricks. The end result is shading the bricks properly. Now this is very limited as we won't be casting any shadows or changing the geometry in any meaningful way so if you come very close you'll see that we're being tricked but it is very effective for convincingly representing a rough surface.

The biggest issue with normal mapping is that the texture assumes the surface is pointing in one direction (facing us) so we need to rotate all the normals to match the orientation of our surface.
Funny enough, single pass shaders optimise this by doing the opposite and rotating the lights to match our normal map, it's prevents a relatively expensive matrix calculation in our fragment shader.
In a deferred shader we don't have that luxury and will apply our matrix in our fragment shader.

To do this we need not only our normals for our vertices, we also need what are called the tangent and bitangent of our normal. These are two perpendicular vectors to our normals that further define the orientation of our surface. Most implementations I've seen will use the adjacent vertices of our vertex to calculate the tangent and bitangent for that vertex and add that to our data. I was planning on doing the same until I read this post: Deferred rendering and normal mapping.
I have no idea why this works, but it does, so I've used it.

Preparing your mesh

Now the first thing is that we actually need a normal map for our object. The house I've added to our scene had a normal map included but I've also added one for our tree using this brilliant site here: NormalMap Online
This little online tool is amazing. It lets you create all sorts of maps from source images. Now I've used our tree texture directly and we have this results:

A note of warning here, what is behind this is the assumption that darker colors are 'deeper' then lighter colors and that is what drives creating the normal map. It usually gives a convincing result but sometimes it pays to create a separate 'depth' image to create a much better result. Indeed I believe the normal map for the house was created by similar means and gives some incorrect results.
For our purposes today, it will do just fine.

We already had added a "bumpmap" texture to our shader structure for our heightmap implementation so we'll use that here as well. That means I just had to change the material file loader to support wavefronts map_bump directive and add that to our tree material file.

Changes to our shader

You'll see I've added two shaders to our list of shaders, BUMP_SHADER and BUMPTEXT_SHADER and both use our standard vertex shader and standard fragment shader. We simply added "normalmap" as a definition to add in the code we need. The first shader simply applies this on a single color material and the second on a textured material. We could easily combine our environment map into this as well though I have not made any changes to using the normal from our normal map instead of the vertex normal.

In our vertex shader we can see that we simply take parts of our normal-view matrix as our tangent and bitangent:
...
#ifdef normalmap
out vec3          Tangent;        // tangent
out vec3          Binormal;       // binormal
#endif

void main(void) {
  ...

  // N after our normalView matrix is applied
  Nv = normalize(normalView * N);
#ifdef normalmap
  Tangent = normalize(normalView[0]);
  Binormal = normalize(normalView[1]);
#endif 

  ...
}
As I said before, I have no idea why this works, I'm sure if I dive into the math I'll figure it out or find out it is not a complete solution but it seems to have the right effects. All in all I may get back to this and precalc all my tangents and bitangents and make it part of our vertex data.
Do note that the normal, tangent and bitangent get interpolated between our vertices.

In our fragment shader we simply replace the code that outputs our normal as it is to code that takes the normal from our normal map and applies the matrix we end up preparing:
...
#ifdef normalmap
uniform sampler2D bumpMap;                          // our normal map
in vec3           Tangent;                          // tangent
in vec3           Binormal;                         // binormal
#endif
...
void main() {
  ...
#ifdef normalmap
  // TangentToView matrix idea taken from http://gamedev.stackexchange.com/questions/34475/deferred-rendering-and-normal-mapping
  mat3 tangentToView = mat3(Tangent.x, Binormal.x, Nv.x,
                            Tangent.y, Binormal.y, Nv.y,
                            Tangent.z, Binormal.z, Nv.z);
  vec3 adjNormal = normalize((texture(bumpMap, T).rgb * 2.0) - 1.0);
  adjNormal = adjNormal * tangentToView;
  NormalOut = vec4((adjNormal / 2.0) + 0.5, 1.0); // our normal adjusted by view
#else
  NormalOut = vec4((Nv / 2.0) + 0.5, 1.0); // our normal adjusted by view
#endif
  ...
}
So we create a tangentToView matrix using our tangent, bitangent and normal, then get the normal from our normal map, apply our matrix and write it out to our normal geobuffer.

Note btw that the code for writing to our normal geobuffer has changed slightly to bring our -1.0 to 1.0 range into a 0.0 to 1.0 range. You'll see that I've updated our lighting shaders to reverse this. Without this change we lost our negative values in our normal map.  I'm fairly surprised this didn't give that much problem during lighting.

Anyways, here's the difference in shading between our tree trunk without normal mapping and with normal mapping:
Tree without normal mapping
Tree with normal mapping






And here are a few renders of the house I added, without normal mapping, normal mapping without our texturemaps and the full end result:
House without normal mapping

House with normal mapping but no textures

House with normal mapping and textures

Well, that's enough for today. Source code will be on github shortly :)




Saturday 25 June 2016

Deferred lighting rendering #3 (part 30)

So it's time to have a look at adding our point lights. Point lights are in many respects the simplest of localised lights. A light simply shines from a single point in space, the light slowly diminishing in strength as distance to that location increases.

In hindsight I should have added logic for at least one point light before we moved to a deferred rendering approach to better illustrate the differences but it is in handling these lights that deferred rendering starts to shine.

Traditionally in single pass shaders we would either find a loop that runs through all the lights or fixed logic that handles a fixed set of lights (usually generated by a parser). Because this logic is repeated for every fragment rendered to screen whether the fragment is lit by the light or not and whether the fragment will later be overwritten or not there is a lot of performance that is wasted.

Now with the speed of modern GPUs and only including lights that likely illuminate an object a single pass shader tip the balance back in its favour, I'm not sure.

Deferred rendering ensures that we prevent a lot of this overhead by doing the lighting calculation as few times as possible by working on the end result of rendering our scene to our geobuffer.

Our main light calculation

The basis of our light calculation remains the same for our point light as for our directional sunlight. I've skipped the ambient component as I feel either using the ambient sunlight as we have now or using some form of environment mapping gives enough results with this.
So we restrict our light calculation to diffuse and specular highlighting. Those calculations remain the same as with our directional light with the one difference that our light to fragment vector plays a much larger role.

The thing that is new is that the intensity of our light diminishes as we move further away from the light. To be exact, it diminishes by the square of the distance to our light.

For computer graphics however we find that we trick this a little, you can find much better explanations then I can possibly give but the formulas that we'll be using is the following:
float attenuation = constant + (linear * distance) + (exponential * distance squared);
fragcolor = lightcolor / attenuation
I've left out a few details there but lightColor is the color we calculated in the same way had with our sunlight and we divide it with our calculation based on our distance. There are 3 values that we input into this formula next to our distance:

  • a constant
  • a linear component we multiply with our distance
  • an exponential component we multiply with the our distance squared

You can create your engine to allow for the manual input of all 3 values to give loads of flexibility but in our engine I've simplified it. Note that when attenuation is 1.0 we get the color as is. Basically the distance at which our formula results 1.0 is where the light starts loosing its strength.

On a quick sidenote in my shader you'll see that if this formula returns larger then 1.0 I cap it. You can have some fun by letting it become overly bright by putting this threshold higher and add some bloom effects to your lighting, but thats a topic for another day.

I'm using the fact that our light starts to loose its intensity at attenuation = 1.0 to calculate our 3 values by specifying the radius at which I want this to happen and then calculating our 3 values as follows:

  • our constant is simply 0.2
  • our linear component is calculated as 0.4 / radius
  • our exponential component is calculated as 0.4 / radius squared

When distances equals radius our formula will be 0.2 + 0.4 + 0.4 = 1.0

Finally, in theory our light as unlimited range, the intensity will keep getting smaller and smaller but it will never reach 0. But there is a point where our intensity becomes so low that it won't have an effect on our scene anymore. In a single stage renderer you could use this to filter out which lights are close enough to your object to be evaluated, in our deferred renderer we use it to limit how much of our screen we update with our lighting color.
Now truth be told, I'm taking a shortcut here and pretending our linear component is 0.0 and our exponential component is 0.8 / radius squared. This makes the calculation slightly easier but I overestimate the range slightly.
Our range calculation simply becomes: range = radius * sqrt((maxIllum / threshold) - 0.2)
maxIllum is simply the highest of our 3 RGB values and threshold is a threshold at which our light has become to low.

Adding shadowmaps

This is where point lights get a bit ridiculous and why using spotlights can be way more effective. Point lights shine in every direction and thus cast shadows in every direction. The way we solve this is by mapping our shadowmaps on a cube and we thus create 6 individual shadowmaps. One for lookup up from the light, one for down, one for left, right, forwards and backwards.

Then when we do our shadow checks we figure out which of those 6 shadow maps applies. I have to admit, this bit needs some improvement, I used a fairly blunt force approach here mostly because I couldn't be bothered to figure out a better way.

Unlike our shadow maps for our directional lights we use a perspective projection for these shadowmaps. I'm using our distance calculations we performed just now to set our far value. Also these are static shadowmaps which means we calculate them once and reuse them unless our lights position changes instead of redoing them every frame. This saves a bunch of overhead especially if we have loads of lights. To be exact, you could save them and skip the first render step all together.

The problem with static shadowmaps is that they won't update if objects move around, so say your character walks past a point light he/she won't cast a shadow.
We'll deal with this in another article but in short we'll leave any object that moves or is animated out of our static shadowmaps, keep a copy, and render just the objects that move or are animated before rendering our frame.

Again as with our sunlight we can also reuse our shadow maps for both eyes.

The code for creating the shadow maps is nearly identical to the code for our directional light other then the added loop to update 6 maps and the change to calculating our projection and view matrices.
Also note that we only check the rebuild flag for the first map, if one map needs changing we assume all need to change (unlike our directional light where we check them individually):
void lsRenderShadowMapsForPointLight(lightSource * pLight, int pResolution, meshNode * pScene) {
  int i;

  vec3 lookats[] = {
       0.0, -100.0,    0.0, 
     100.0,    0.0,    0.0, 
    -100.0,    0.0,    0.0, 
       0.0,  100.0,    0.0, 
       0.0,    0.0,  100.0, 
       0.0,    0.0, -100.0, 
  };

  // as we're using our light position and its the same for all shadow maps we only check our flag on the first
  if ((pLight->shadowLA[0].x != pLight->position.x) || (pLight->shadowLA[0].y != pLight->position.y) || (pLight->shadowLA[0].z != pLight->position.z)) {
    vec3Copy(&pLight->shadowLA[0], &pLight->position);
    pLight->shadowRebuild[0] = true;
  };

  // we'll initialize our shadow maps for our point light
  if (pLight->shadowRebuild[0] == false) {
    // reuse it as is...
  } else if (pScene == NULL) {
    // nothing to render..
  } else {
    for (i = 0; i < 6; i++) {
      if (pLight->shadowMap[i] == NULL) {
        // create our shadow map if we haven't got one already
        pLight->shadowMap[i] = newTextureMap("shadowmap");
      };

      if (tmapRenderToShadowMap(pLight->shadowMap[i], pResolution, pResolution)) {
        mat4            tmpmatrix;
        vec3            tmpvector, lookat;
        shaderMatrices  matrices;

        // rest our last used material
        matResetLastUsed();

        // set our viewport
        glViewport(0, 0, pResolution, pResolution);

        // enable and configure our backface culling, note that here we cull our front facing polygons
        // to minimize shading artifacts
        glEnable(GL_CULL_FACE);   // enable culling
        glFrontFace(GL_CW);       // clockwise
        glCullFace(GL_FRONT);     // frontface culling

        // enable our depth test
        glEnable(GL_DEPTH_TEST);  // check our depth
        glDepthMask(GL_TRUE);     // enable writing to our depth buffer

        // disable alpha blending  
        glDisable(GL_BLEND);

        // solid polygons
        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);    

        // clear our depth buffer
        glClear(GL_DEPTH_BUFFER_BIT);      

        // set our projection
        mat4Identity(&tmpmatrix);
        mat4Projection(&tmpmatrix, 90.0, 1.0, 1.0, lightMaxDistance(pLight) * 1.5);
        shdMatSetProjection(&matrices, &tmpmatrix); // call our set function to reset our flags

        // now make a view based on our light position
        mat4Identity(&tmpmatrix);
        vec3Copy(&lookat, &pLight->position);
        vec3Add(&lookat, &lookats[i]);
        mat4LookAt(&tmpmatrix, &pLight->position, &lookat, vec3Set(&tmpvector, 0.0, 1.0, 0.0));
        shdMatSetView(&matrices, &tmpmatrix);

        // and render
        meshNodeShadowMap(pScene, &matrices);

        // now remember our view-projection matrix, we need it later on when rendering our scene
        mat4Copy(&pLight->shadowMat[i], shdMatGetViewProjection(&matrices));

        // we can keep it.
        pLight->shadowRebuild[i] = false;

        // and we're done
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
      };
    };
  };
};

Rendering our lights

Now it is time to actually render our lights. This is done by calling gBufferDoPointLight for each light that needs to be rendered. We make the assumption that our directional light has been rendered and we thus have content for our entire buffer. Each light is now rendered on top of that result by using additive blending. This means that instead of overwriting our pixel the result of our fragment shader is added to the end result.

gBufferDoPointLight assumes our blending has already been setup as we need the same settings for every light. Our loop in our render code therefor looks like this:
    // now use blending for our additional lights
    glEnable(GL_BLEND);
    glBlendEquation(GL_FUNC_ADD);
    glBlendFunc(GL_ONE, GL_ONE);

    // loop through our lights
    for (i = 0; i < MAX_LIGHTS; i++) {
      if (pointLights[i] != NULL) {
        gBufferDoPointLight(geoBuffer, &matrices, pointLights[i]);
      };
    };

As you can see for now we've just got a simple array of pointers to our lights and it currently holds 3 test lights. Eventually I plan to place the lights inside of our scene nodes so we can move lights around with objects (and accept the overhead in recalculating shadow maps). For now this will do just fine.

The rendering of our light itself is implemented in the vertex and fragment shaders called geopointlight. Most implementations I've seen render a full sphere with a radius of our maximum light distance but for now I've stuck with rendering a flat circle and doing so fully within our vertex shader (using a triangle fan):
#version 330

#define PI 3.1415926535897932384626433832795

uniform float   radius = 100.0;
uniform mat4    projection;
uniform vec3    lightPos;

out vec2 V;
out float R;

void main() {
  // doing full screen for a second, we're going to optimize this by drawing a circle !!
  // we're going to do a waver
  // first point is in the center
  // then each point is rotated by 10 degrees

  //           4
  //      3   ---   5
  //       /\  |  /\  
  //     /    \|/    \
  //   2|------1------|6
  //     \    /|\    /
  //       \/  |  \/  
  //      9   ---  7
  //           8

  if (gl_VertexID == 0) {
    vec4 Vproj = projection * vec4(lightPos, 1.0);
    V = Vproj.xy / Vproj.w;
    R = radius;
  } else {
    float ang = (gl_VertexID - 1) * 10;
    ang = ang * PI / 180.0;
    vec4 Vproj = projection * vec4(lightPos.x - (radius * cos(ang)), lightPos.y + (radius * sin(ang)), lightPos.z, 1.0);
    V = Vproj.xy / Vproj.w;
    R = 0.0;
  };
  gl_Position = vec4(V, 0.0, 1.0);
}

Now drawing a circle this way ensure that every pixel that requires our lighting calculation to be applied will be included. For very bright lights this means the entire screen but for small lights the impact is severely minimised.

You can do a few more things if you use a sphere to render the light but there are also some problems with it. We'll revisit this at some other time.

I'm not going to put the entire fragment shader here, its nearly identical to our directional light fragment shader. The main differences are:
- we discard any fragment that doesn't effect our scene
- we ignore the ambient buffer
- we use our boxShadow function to check the correct shadowmap
- we calculate our attenuation and divide our end result with that

Note that if our attenuation is smaller then 1.0 we ignore it. Basically we're within the radius at which our light is at full strength. If we didn't do this we'd see that things close to the light become overly bright. Now that can be a fun thing to play around with. The OpenGL superbible has an interesting example where they write any value where any color component is bigger then 1.0 to a separate buffer. They then blur that buffer and write it back over the end result to create a bloom effect.
But at this stage we keep that bit easy.

Seeing our buffers

Last but not least, I'm now using an array of shaders instead of individual variables and have introduced an enum to manage this.

There are two new shaders both using the same vertex and fragment shader called rect and rectDepth. These two shaders simple draw texture rectangles onto the screen.

At the end of our render loop, if we have our interface turned on (toggle by pressing i) we now see our main buffers.

At the top we see our 5 geobuffer textures.
Then we see our 3 shadow maps for our directional light.
Finally we see our 6 shadow maps of our first point light.

Handy for debugging :)


Here is a shot where we can see the final result of our 3 lights illuminating the scene:


Check out the sourcecode so far here

I'll be implementing some spotlight next time. These are basically easier then our point lights as the shine in a restricted direction and we thus can implement these with a single shadowmap.
But we can also have some fun with the color of these lights.

I'm also going to look into adding support for transparent surfaces.

Last but not least, I want to have a look into volumetric lighting. This is something I haven't played around with before so it is going to take a bit of research on my side.

Tuesday 21 June 2016

Update on lighting

Hey all,

I've been nice and busy in what little spare time I have and have got point lights working nicely in the engine. Point lights are easiest to implement from a light perspective but are a bit of a pain when it comes to shadowmaps as you're basically creating a cube around the light and rendering a shadow map for each side. The plus side is that you can generally use static shadow maps (rendering them once and then just reusing). I'll look into combining static shadow maps for the environment with shadow maps to deal with moving objects at some later time to get a best of both worlds thing going.

I only have 3 point lights at this time but in theory I should be able to render a lot of them before any noticeable framerate drop. I won't however do that until I implement spot lights. Spotlights only illuminate in a particular direction and can use a single shadow map and in most cases suffice where a point light is overkill.

I updated the previous post with some images I generated from the buffers to hopefully make the buffers a bit clearer, I'll find some time to write up info about the point lights at a later stage. For now I will check in my changes as they are right now so you can have a look at the code and leave you with two images.

First an image where I'm rendering small versions of all the buffers ontop of the UI (though I'm only showing the 6 shadow maps for the first point light):

Then here is the end result of having some fun with lighting:



Saturday 4 June 2016

Deferred lighting rendering #2 (part 30)

Sorry for the pause between posts. I've been caught up in life and tinkering away with other things lately. Also as I mentioned in my previous post I'm still not sure of the format I want to go forward with.

So we left off in the last part discussing how deferred lighting works. Lets have a look at the actual implementation (it was checked into GitHub a little while ago, I haven't labeled it yet though).

First off, I'll be changing the shaders to work for deferred lighting, that means they no longer work for our render to texture example that we used for our 3rd LOD of our trees. I could off course easily add a set of shaders to render that texture but I didn't feel that would add to our discussion, just distract from it. For now I've disabled that option but obviously the code for doing so is still in the previous examples and with a little bit of work you could change the engine to support both single stage and deferred rendering.

We've also switched off transparency support for now.

We don't change anything to rendering our shadow maps.

gBuffer


At the heart of our new rendering technique is what is often called rendering to a geometric buffer (because it holds various geometric data for our scene).
I've created a new library for this called gbuffer.h which is implemented in the same single file way we're used to right now.

I have to admit that at this stage I'm very tempted to rejig the engine to a normal header files + source files approach so I can compile the engine into a library to include in here. Anyway, I'm getting distracted :)

Note also at this point that I've added all the logic for the lighting stage into this file as well so you'll find structures and methods for those as well. We'll get there in due time.

An instance of a geographic buffer is contained within a structure called gBuffer which contains all the texture and the FBO we'll be using to render to the frame buffer.

In engine.c we define a global pointer to the gBuffer we'll be using and initialise this by calling newGBuffer in our engineLoad function and freeing our gBuffer in engineUnload.
Note that there is a HMD parameter send to the gBuffer routine which when set applies an experimental barrel distortion for head mounted devices such as a Rift or Vive. I won't be talking about that today as I haven't had a chance to hook it up to an actual HMD but I'll spend a post on it on its own once I've done so and worked out any kinks.

The gBuffer creates several textures that are all mapped as outputs on the FBO. These are hardcoded for now. They are only initialised in newGBuffer, they won't actually be created until you use the gBuffer for the first time and are recreated if the buffer needs to change size.

I have an enum called GBUFFER_TEXTURE_TYPE that is a nice helper to index our textures and then there are a number of arrays defined that configure the textures themselves:
// enumeration to record what types of buffers we need
enum GBUFFER_TEXTURE_TYPE {
  GBUFFER_TEXTURE_TYPE_POSITION,  /* Position */
  GBUFFER_TEXTURE_TYPE_NORMAL,    /* Normal */
  GBUFFER_TEXTURE_TYPE_AMBIENT,   /* Ambient */
  GBUFFER_TEXTURE_TYPE_DIFFUSE,   /* Color */
  GBUFFER_TEXTURE_TYPE_SPEC,      /* Specular */
  GBUFFER_NUM_TEXTURES,           /* Number of textures for our gbuffer */
};

...

// precision and color settings for these buffers
GLint  gBuffer_intFormats[GBUFFER_NUM_TEXTURES] = { GL_RGBA32F, GL_RGBA, GL_RGBA, GL_RGBA, GL_RGBA};
GLenum gBuffer_formats[GBUFFER_NUM_TEXTURES] = { GL_RGBA, GL_RGBA, GL_RGBA, GL_RGBA, GL_RGBA };
GLenum gBuffer_types[GBUFFER_NUM_TEXTURES] = { GL_FLOAT, GL_FLOAT, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE, GL_UNSIGNED_BYTE };
GLenum gBuffer_drawBufs[GBUFFER_NUM_TEXTURES] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4 };
char   gBuffer_uniforms[GBUFFER_NUM_TEXTURES][50] = { "worldPos", "normal", "ambient", "diffuse", "specular" };
When you look at how different people have implemented deferred lighting you'll see they all have a slightly different mix of outputs.
At minimum you'll find an output for position, color and normal. Other outputs depend on what sort of capabilities you want to enable in your lighting. Obviously the more outputs you have, the more overhead you have in clearing those buffers and reading from them in the lighting stage.

There are two other outputs I've added.


One is an ambient color output. Now this one you probably won't see very often. As we saw in our original shaders we simply calculate the ambient color as a fraction of the diffuse color so why store it separately? Well I use it in this case to render our reflection map output to so I know the color is used in full but another use would be for self lighting properties of a material. It is definitely one you probably want to leave out unless you have a specific use for it.

The other is the specular color output. Note that this is the base color that we use for specular output, not the end result. I also 'abuse' the alpha channel to encode the shininess factor. In many engines you'll see that this texture is used only to store the shininess factor because often either the diffuse color is used or the color of the light. If you go down this route you can also use the other 3 channels to encode other properties you want to use in your lighting stage.

Obviously there is a lot of flexibility here in customising what extra information you need. But lets look at the 3 must haves.

Position, this texture stores the position in view space of what we're rendering. We'll need that during our lighting stage so we can calculate our light to object vector for our diffuse and specular lighting especially for things like spotlights. This is by far the largest output buffer using 32bit floats for each color channel. Note that as we're encoding our position in a color, and our color values run from 0.0 to 1.0, we need to scale all our positions to that range.

Diffuse color, this texture stores the color of the objects we're rendering. Basically this texture will look like our end result but without any lighting applied.

Normals, this texture stores the normals of the surfaces we're rendering. We'll need these to calculate the angle at which light hits our surface to determine our intensities.

Changing our shaders


At this stage we're going to seriously dumb down our shaders. First off I've created an include file for our fragment shaders called output.fs:
layout (location = 0) out vec4 WorldPosOut; 
layout (location = 1) out vec4 NormalOut; 
layout (location = 2) out vec4 AmbientOut; 
layout (location = 3) out vec4 DiffuseOut; 
layout (location = 4) out vec4 SpecularOut; 

uniform float posScale = 1000000.0;
Looks pretty similar to our attribute inputs but not we use outputs. Note we've defined an output for each of our textures. The uniform float posScale is simply a configurable factor with which we'll scale our positions to bring them into the aforementioned 0.0 - 1.0 range.

Now I'm only going to look at one fragment shader and a dumbed down version of our standard shader at that but our outputs are used:
#version 330

in vec4           V;                                // position of fragment after modelView matrix was applied
in vec3           Nv;                               // normal vector for our fragment (inc view matrix)
in vec2           T;                                // coordinates for this fragment within our texture map

#include "outputs.fs"

void main() {
  vec4 fragcolor = texture(textureMap, T);
  WorldPosOut = vec4((V.xyz / posScale) + 0.5, 1.0); // our world pos adjusted by view scaled so it fits in 0.0 - 1.0 range
  NormalOut = vec4(Nv, 1.0); // our normal adjusted by view
  AmbientOut = vec4(fragcolor.rgb * ambient, 1.0);
  DiffuseOut = vec4(fragcolor.rgb * (1.0 - ambient), 1.0);
  SpecularOut = clamp(vec4(matSpecColor, shininess / 256.0), 0.0, 1.0);
}
So we can see that we just about copy all the outputs of our vertex shader right into our fragment shader. The only thing we're doing is scaling our position and our shineness factor.

We make similar changes to all our shaders. Note that for some shaders like our skybox shader we abuse our ambient output to ensure no lighting is applied.

Note that I have removed most of the uniform handling on our shaders that relate to lighting from our shader library. I could have left it in place and unused but for now I decided against that. You'll have to put them back in if you want to mix deferred and direct lighting.

Rendering to our gBuffer


Now that we've created our gBuffer and changed our shaders it is time to redirect our output to the gBuffer.

Our changes here are fairly simple. In our engineRender routine we simply add a call to gBufferRenderTo(...) to initialise our textures and make our FBO active. Our output now goes to our gBuffer.

We first clear our output but note though that I no longer clear the color buffer, only the depth buffer. Because of our skybox I know our scene will cover the entire output and thus there really is no need for this overhead.

The rest of the logic is pretty much the same as before as our shaders do most of the work but once our scene is rendered to the gBuffer we do have more work to do.

We're no longer outputting anything to screen, we now need to use our gBuffer to do our lighting pass to get our final output. Before we do so we first need to unset the FBO:
    // set our output to screen
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(wasviewport[0],wasviewport[1],wasviewport[2],wasviewport[3]);  
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

And then we call gBufferDoMainPass to perform our main lighting pass.
After this we should do our additional calls for other lights we want to apply to our scene but that's something we'll come back to.

Our main lighting shaders


Before we can look at our lighting pass we need some more shaders. At this point all we have implemented is our main pass which applies our global lighting over our whole scene. To apply this we're going to draw two triangles making up a rectangle that fills the entire screen. We need to render each pixel on the screen once and this logic is thus applied fully in our fragment shader.

Our vertex shader (geomainpass.vs) for this pass is thus pretty simple:
#version 330

out vec2 V;

void main() {
  // our triangle primitive
  // 2--------1/5
  // |        /|
  // |      /  |
  // |    /    |
  // |  /      |
  // |/        |
  //0/3--------4

  const vec2 coord[] = vec2[](
    vec2(-1.0,  1.0),
    vec2( 1.0, -1.0),
    vec2(-1.0, -1.0),
    vec2(-1.0,  1.0),
    vec2( 1.0,  1.0),
    vec2( 1.0, -1.0)
  );

  V = coord[gl_VertexID];
  gl_Position = vec4(V, 0.0, 1.0);
}
We don't need any buffers for this and we thus render these two triangles with a simple call to glDrawArrays(GL_TRIANGLES, 0, 3 * 2).

The magic happens in our fragment shader (geomainpass.fs, I've left out the barrel distortion in the code below):
#version 330

uniform sampler2D worldPos;
uniform sampler2D normal;
uniform sampler2D ambient;
uniform sampler2D diffuse;
uniform sampler2D specular;

uniform float posScale = 1000000.0;

// info about our light
uniform vec3      lightPos;                         // position of our light after view matrix was applied
uniform vec3      lightCol = vec3(1.0, 1.0, 1.0);   // color of the light of our sun

#include "shadowmap.fs"

in vec2 V;
out vec4 fragcolor;

void main() {
  // get our values...
  vec2 T = (V + 1.0) / 2.0;
  vec4 ambColor = texture(ambient, T);  
  if (ambColor.a < 0.1) {
    // if no alpha is set, there is nothing here!
    fragcolor = vec4(0.0, 0.0, 0.0, 1.0);
  } else {
    vec4 V = vec4((texture(worldPos, T).xyz - 0.5) * posScale, 1.0);
    vec3 difColor = texture(diffuse, T).rgb;
    vec3 N = texture(normal, T).xyz;
    vec4 specColor = texture(specular, T);

    // we'll add shadows back in a minute
    float shadowFactor = shadow(V);

    // Get the normalized directional vector between our surface position and our light position
    vec3  L = normalize(lightPos - V.xyz);
    float NdotL = max(0.0, dot(N, L));
    difColor = difColor * NdotL * lightCol * shadowFactor;

    float shininess = specColor.a * 256.0;
    if ((NdotL != 0.0) && (shininess != 0.0)) {
      // slightly different way to calculate our specular highlight
      vec3  halfVector  = normalize(L - normalize(V.xyz));
      float nxHalf = max(0.0, dot(N, halfVector));
      float specPower = pow(nxHalf, shininess);
      
      specColor = vec4(lightCol * specColor.rgb * specPower * shadowFactor, 1.0);
    } else {
      specColor = vec4(0.0, 0.0, 0.0, 0.0);
    };

    fragcolor = vec4(ambColor.rgb + difColor + specColor.rgb, 1.0);
  }
}
Most of this code you should recognise from our normal shader as we're basically applying our lighting calculations exactly as they were in our old shaders. The difference being that we're getting our intermediate result from our textures first.

Because these shaders are very different from our normal shaders and we simply initialise them when we build our gBuffer I've created a seperate structure for them called lightShader with related functions. They still use much of the base code from our shader library.

Note also that because we're rendering the entire screen and no longer using a z-buffer, I've uncommented the code that clears our buffers in our main loop.

And thats basically it. There is a bit more too it in nitty gritty work but I refer you to the source code on github.

Next up


I really need to add some images of our intermediate textures and I may add those to this writeup in the near future but I think this has covered enough for today.

I'll spend a short post next time about the barrel distortion if I get a chance to borrow a friends Rift or Vive.

The missing bit of this writeup is adding more lights. At this point in time we've not won anything over our earlier implementation, we've actually lost due to the added overhead.
Once we start adding lights to our scene the benefits of this approach will start to show though I've learned that on the new generation hardware a single pass renderer can render multiple lights more then fast enough to make the extra efforts and headaches not worth it.