Showing posts with label Tutorial. Show all posts
Showing posts with label Tutorial. Show all posts

Friday, 8 July 2016

Spotlights in our deferred rendering

Alright, time to add some spotlights :)

But before we do so I wanted to talk about this project itself. I was already musing about it some time ago but I'm going to change things up a bit.
This little project is now pretty far removed from a tutorial series as originally intended and it really doesn't suit the format it's in right now so it's time to change direction.
This post will be the last in the series and it'll leave a number of things open but c'est la vie. The final nail in the coffin so to speak was reading the information behind light indexed rendering and it's made me want to change direction.

I've also felt that the engine I've been building is slowly developing to be a render engine that I can grow to something that I could actually use. But I've been wanting to bring it back to a more traditional library for inclusion in other projects.

After I finish writing this post the first order of business will be to take the core and restructure it, splitting all the header files into header and source files and compiling the source files into a library. After that I'll initially be turning it back into a forward renderer, after which I'm going to look into implementing the light indexing technique. I'll be blogging about each step and making the code available on github but I won't go into the level of detail I've done so far.

Spotlights


However before that, let's add a couple of spotlights.

A spotlight isn't much different from our point light, all that really changes is that it shines in a limited direction. When we look at our deferred shader there are two parts that we need to deal with, the first is the shape we'll be rendering and the second is the changes to the lighting calculation itself.

On that first part I'm going to cheat. Just for getting things to work I'm still using the same code I used for our point light. This means we're rendering way more then we should but at this stage I don't care. The idea is to eventually render a cone but as per my intro above, I've changed direction and won't be doing so at this point in time.

I'm also going to cheat on how we implement our spotlight. Traditionally you would calculate the angle between our light direction vector and the vector from the origin of the light to our fragment. The greater this angle, the less we illuminate our fragment, until eventually we don't illuminate our fragment at all as we pass the edge of our cone. 

In the days that we didn't have oodles of GPU power we cheated using a lightmap:

Now it might seem strange to use this old cheat but there is a really good reason for doing so. You can start doing some really funky things with this because you basically end up projecting this image as our light and it doesn't have to be a boring white circle. It can also be something cool like this:

Yup, we're going to project a bat symbol on our un-expecting little house....

Because our spot light shines in one direction we also only need to create one shadow map which we do using a perspective projection matrix and here's the funky bit, the same calculation we need to do for determining our shadowmap coordinates is the same calculation we need to do to get our light map coordinates.

I've made a few changes to our light structure.
First I've added a type, 0 for directional (which is still handled separately), 1 for a pointlight and 2 for a spotlight. This has allowed me to add code to our shadowmap code to figure out what needs to be created.
I've also added a 'lookat' vector that basically tells us the direction the spotlight is shining to and I've added an extra cached value to track if our lookat has changed and if we need to recalculate our shadowmap.
And there is our light angle value that determines the shape of our light cone.

If you look at the changes to our lsRenderShadowMapsForLight function (used to be our point light function) you'll see that it will calculate only one shadow map for a spotlight and instead of using our 6 lookat vectors uses the vector in our light structure. It also uses our light angle as the FOV value for our projection map. 

Second, I've added our spotlight shader :) I'm not going to show the whole code here but there is one bit in the logic that I do want to highlight:
    // we're going to use our shadow maps projection matrix to limit our light
    vec4 Vs = shadowMat[0] * V;
    vec3 Proj = Vs.xyz / Vs.w;
    if ((abs(Proj.x) < 1.00) && (abs(Proj.y) < 1.00) && (abs(Proj.z) < 1.00)) {
      vec2 coords = vec2(0.5 * Proj.x + 0.5, 0.5 * Proj.y + 0.5);
      // bring it into the range of 0.0 to 1.0 instead of -1.0 to 1.0
      shadowFactor = samplePCF(0.5 * Proj.z + 0.5, coords, 0, 9);

      lColor = lColor * texture(lightMap, 1.0-coords).rgb;
    } else {
      // no point in doing this..
      discard;
    };
This is the bit of code that uses our one shadowmap projection matrix, determines the coordinates in our shadowmap, discards the fragment if we're outside of it, and obtains the lights color from our light map.

And that really is it. The end result is:


And here is our bat symbol :)

Well that's it for today. It'll probably be awhile before my next post as I've got a fair amount of work to do restructuring things :)

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.

Thursday, 5 May 2016

Deferred lighting rendering #1 (part 30)

Alrighty, this one is going to take a bit of time to slowly put together. I'm tinkering with the idea of changing the format of these blog posts. This is really no longer a tutorial but is starting to become a development blog on building my little 3D engine.

The sheer amount of changes I've made to the engine over the past two weeks, many of which are just background things, would just deter from the topic I'm trying to discuss...

So let's just see where this will lead us but I may just start focussing on the core changes and leave it up to you to examine the other stuff by looking at the changes checked in on github.

Speaking of github, I'm still polishing up things so I won't be checking things in just yet. This mostly is more of an introduction into what we'll be doing.

Deferred lighting, I've mentioned it a few times now since I started this series. What is it all about?

Deferred lighting or deferred shading is a technique that implements our rendering into two (or more) separate passes. So far we've been doing all the work in a single pass, every pixel we render to the screen we do all the calculations for to determine its final color in one go.
With deferred lighting we first render our scene to a number of temporary buffers in which we store information about all our objects without doing any lighting calculations, and then we do a pass for each light in our scene to render our final scene.

So why is this a good thing? Well lets start by saying it doesn't have to be :)
When we look at our current example, an outdoor scene with very little detail, well it won't help us at all. In fact after all the work of the last two weeks, the engine is slower then before..

But there is something we can already see in our current implementation of our shaders. Just take a good look at our standard fragment shader. Even with just the calculations of a single light, a very simple directional light, 90% of the fragment shader is all focussed on calculating the amount of light illuminating our fragment.
Now with a simple scene, a bit of sorting of our objects, and good use of our Z-buffer it's a likely assumption we're already discarding all the fragments that would result in all these calculations being done more then once for the same pixel as objects overlap.
But with a complex scene, with many overlapping objects, the likelihood of doing many of these complex calculation for nothing increases.

This is where the first strong point of a deferred shader comes into play. By rendering all the geometry to a set of temporary buffers without doing all our lighting calculations making this step very fast, we can then do a second pass to only do the lighting calculations for those fragments that actually end up being pixels in our end result. The overhead of rendering our scene in two passes can easily be offset by the time we save not doing complex lighting calculations multiple times for the same pixel.

But the second strong point is where this approach comes into its own. Right now we have one light. A light that potentially effects the whole screen, and in that our sun is actually rather unique.
Imagine an indoor scene with a number of lights scattered around the room, maybe a desk lamp somewhere, or the light cast by a small LED clock display.
With a single pass renderer we're checking for the effects of these light for every fragment we attempt to render even though they may only effect a tiny part of our end result.
In a deferred renderer we're using 3D meshes that encompass the area effected by these types of lights. We then render these "lights" onto our end result and only apply the lighting calculations for each light where it counts as part of our 2nd pass.

When we look at an environment where we want to deal with many lights effecting complex geometry a deferred renderer can be much faster then a single pass renderer.
But it is very important to realise that as with many techniques there is a tradeoff.

Basically there are four main weak points of a deferred renderer that I can name off the top of my head:
- for simpler or more open space environments the added overhead won't be won back as I've explained up above.
- as we'll see in this part of the series, transparency is relatively hard to do. You either need to move rendering transparent objects to your lighting pass or introduce additional buffers
- multi layer materials with different lighting properties are very hard to do because we don't do our lighting until we've already "flattened" our material.
- we can't use some optimisations in lighting normally performed in the vertex shader because the input into our lighting scene is in view space. It's a shame we didn't already implement it before we got to this point so I could show the differences but an example of this is normal mapping. You can avoid doing some costly calculations in the fragment shader by projecting your light based on the orientation of the polygon you're rendering and using your normal map directly. But in a deferred shader your have to project all the normals in your normal map to view space.

When we look at the open world environment we've been working with so far a single pass shader would definitely be the better choice. Let's say we turn this into a little flight simulator, or turn this into a little racing simulator, we're probably want to stick with the single pass shaders we've build so far.

But when I look at the platformer I want to build, I'm suddenly less concerned about the drawbacks and much more enthusiastic about the lighting capabilities of our deferred lighting approach.

At this point in time the changes to the renderer to enable deferred shading have replaced the single pass approach. This is one of the areas where I want to do a bit more spit and polish. Ideally I want to create a situation where I can chose which approach I want to take.

Anyways, that's it for now. In the next post we'll start having a closer look at how the deferred lighting rendered is actually works.


Tuesday, 19 April 2016

A simple shader preprocessor (part 29)

As I started planning out the additions for my deferred lighting renderer I realised I could no longer postpone implementing at least a basic shader preprocessor.

While some parts of the code can be moved more central other parts need to be further duplicated and in doing so the need to fix the same issues in multiple places make things harder and harder to maintain.

For my goals however I don't need the preprocessor to do much so we can keep everything very simple and we'll limit the functionality to the following:

  • support for a #include to insert the text from a file into our shader
  • supplying a number of "defines" which we can trigger logic
  • very basic #ifdef, #ifndef and #else logic that use these defines to include or exclude parts of the shader code

Changes to our system library


I was thinking about putting most of this code in our system.h file but decided against that for now. I may yet change this in the future. For now one support function has been added here:
// gets the portion of the line up to the specified delimiter(s)
// return NULL on failure or if there is no text
// returns string on success, calling function is responsible for freeing the text
char * delimitText(const char *pText, const char *pDelimiters) {
  int    len = 0;
  char * result = NULL;
  bool   found = false;
  int    delimiterCount;

  delimiterCount = strlen(pDelimiters) + 1; // always include our trailing 0 as a delimiter ;)

  while (!found) {
    int pos = 0;
    while ((!found) && (pos < delimiterCount)) {
      if (pText[len] == pDelimiters[pos]) {
        found = true;
      };
      pos++;
    };

    if (!found) {
      len++;
    };
  };

  if (len != 0) {
    result = malloc(len + 1);
    if (result != NULL) {
      memcpy(result, pText, len);
      result[len] = 0;
    };
  };

  return result;
};
This function splits off the first part of the text pointed to by pText from the start until it detects one of the delimiters or the end of the string.
This is fairly similar to the code we wrote before to read out material and object files line by line but without using our varchar implementation.

Changes to our varchar library


We are going to use varchar.h but in combination with a linked list to store our defines in. For this I've added 3 new functions:
// list container for varchars
// llist * strings = newVarcharList()
llist * newVarcharList() {
  llist * varcharList = newLlist((dataRetainFunc) varcharRetain, (dataFreeFunc) varcharRelease);
  return varcharList;
};
This first function simply returns a linked list setup to accept varchar objects.
// list container for varchars created by processing a string
// empty strings will not be added but duplicate strings will be
// llist * strings = newVarcharList()
llist * newVCListFromString(const char * pText, const char * pDelimiters) {
  llist * varcharList = newVarcharList();

  if (varcharList != NULL) {
    int    pos = 0;

    while (pText[pos] != 0) {
      // find our next line
      char * line = delimitText(pText + pos, pDelimiters);
      if (line != NULL) {
        int len = strlen(line);

        varchar * addChar = newVarchar();
        if (addChar != NULL) {
          varcharAppend(addChar, line, len);

          llistAddTo(varcharList, addChar);
        };

        if (pText[pos + len] != 0) {
          // skip our newline character
          pos += len + 1;
        } else {
          // we found our ending
          pos += len;
        };

        free(line);
      } else {
        // skip any empty line...
        pos++;
      };
    };
  };

  return varcharList;
};
This method uses our new delimitText function to pull a given string appart and add each word in the string as an entry into a new linked list.
// check if our list contains a string
bool vclistContains(llist * pVCList, const char * pText) {
  if ((pVCList != NULL) && (pText != NULL)) {
    llistNode * node = pVCList->first;

    while (node != NULL) {
      varchar * text = (varchar *) node->data;

      if (varcharCmp(text, pText) == 0) {
        return true;
      };

      node = node->next;
    };
  };

  // not found
  return false;
};
And finally a function that checks if a given word is present in our linked list.

Changes to our shader library


The real implementation can be found in our shader library. We've added a new parameter to our newShader function so we can pass it the defines we want to use for that shader:
shaderInfo * newShader(const char *pName, const char * pVertexShader, const char * pTessControlShader, const char * pTessEvalShader, const char * pGeoShader, const char * pFragmentShader, const char *pDefines) {
  shaderInfo * newshader = (shaderInfo *)malloc(sizeof(shaderInfo));
  if (newshader != NULL) {
    llist * defines;
    ...
    // convert our defines
    defines = newVCListFromString(pDefines, " \r\n");

    // attempt to load our shader by name
    if (pVertexShader != NULL) {
      shaders[count] = shaderLoad(GL_VERTEX_SHADER, pVertexShader, defines);
      if (shaders[count] != NO_SHADER) count++;      
    };
    ...
    // no longer need our defines
    if (defines != NULL) {
      llistFree(defines);
    };
    ...
  return newshader;
};
We first convert our new parameter pDefines into a linked list of varchars by calling our new newVCListFromString function.
We then pass our new linked list to each shaderLoad call so it can be used by our preprocessor.
Finally we deallocate our linked list and all the varchars held within.

The only change in shaderLoad is that it no longer called loadFile directly but instead calls shaderLoadAndPreprocess:
varchar * shaderLoadAndPreprocess(const char *pName, llist * pDefines) {
  varchar * shaderText = NULL;

  // create a new varchar object for our shader text
  shaderText = newVarchar();
  if (shaderText != NULL) {
    // load the contents of our file
    char * fileText = loadFile(shaderPath, pName);

    if (fileText != NULL) {
      // now loop through our text line by line (we do this with a copy of our pointer)
      int    pos = 0;
      bool   addLines = true;
      int    ifMode = 0; // 0 is not in if, 1 = true condition not found, 2 = true condition found

      while (fileText[pos] != 0) {
        // find our next line
        char * line = delimitText(fileText + pos, "\n\r");

        // found a non-empty line?
        if (line != NULL) {
          int len = strlen(line);

          // check for any of our preprocessor checks
          if (memcmp(line, "#include \"", 10) == 0) {
            if (addLines) {
              // include this file
              char * includeName = delimitText(line + 10, "\"");
              if (includeName != NULL) {
                varchar * includeText = shaderLoadAndPreprocess(includeName, pDefines);
                if (includeText != NULL) {
                  // and append it....
                  varcharAppend(shaderText, includeText->text, includeText->len);
                  varcharRelease(includeText);
                };
                free(includeName);
              };
            };
          } else if (memcmp(line, "#ifdef ", 7) == 0) {
            if (ifMode == 0) {
              char * ifdefined;

              ifMode = 1; // assume not defined....
              ifdefined = delimitText(line + 7, " ");
              if (ifdefined != NULL) {
                // check if our define is in our list of defines
                if (vclistContains(pDefines, ifdefined)) {
                  ifMode = 2;
                };
                free(ifdefined);
              };
              addLines = (ifMode == 2);              
            } else {
              errorlog(SHADER_ERR_NESTED, "Can't nest defines in shaders");
            };
          } else if (memcmp(line, "#ifndef ", 8) == 0) {
            if (ifMode == 0) {
              char * ifnotdefined;

              ifMode = 1; // assume not defined....
              ifnotdefined = delimitText(line + 7, " ");
              if (ifnotdefined != NULL) {
                // check if our define is not in our list of defines
                if (vclistContains(pDefines, ifnotdefined) == false) {
                  ifMode = 2;
                };
                free(ifnotdefined);
              };
              addLines = (ifMode == 2);              
            } else {
              errorlog(SHADER_ERR_NESTED, "Can't nest defines in shaders");
            };
          } else if (memcmp(line, "#else", 5) == 0) {
            if (ifMode == 1) {
              ifMode = 2;
              addLines = true;
            } else {
              addLines = false;
            };
          } else if (memcmp(line, "#endif", 6) == 0) {
            addLines = true;
            ifMode = 0;
          } else if (addLines) {
            // add our line
            varcharAppend(shaderText, line, len);
            // add our line delimiter
            varcharAppend(shaderText, "\r\n", 1);
          };

          if (fileText[pos + len] != 0) {
            // skip our newline character
            pos += len + 1;
          } else {
            // we found our ending
            pos += len;
          };

          // don't forget to free our line!!!
          free (line);
        } else {
          // skip empty lines...
          pos++;
        };
      };

      // free the text we've loaded, what we need has now been copied into shaderText
      free(fileText);
    };

    if (shaderText->text == NULL) {
      varcharRelease(shaderText);
      shaderText = NULL;
    };
  };

  return shaderText;
};
I'm not going to detail each and every section, I hope the comments do a good enough job for that. In a nutshell however, we start by creating a new varchar variable called shaderText which is what we'll end up returning. This means that our shaderLoad function also has a small change to work with a varchar instead of a char pointer as a result.
After this we load the contents of our shader file into a variable called fileText but instead of using this directly we use delimitText to loop through our shader text one line at a time.
For each line we check if it starts with one of our preprocessor commands and if so handle the special logic associated with it. If not we simply add our line to our shaderText variable.

#include is the first preprocessor command we handle, it simply checks the filename presented and attempts to load that file by calling shaderLoadAndPreprocess recursively.

This is followed by the code that interprets our #ifdef, #ifndef, #else and #endif preprocessor commands. These basically check if the given define is present in our linked list. They toggle the values of ifMode and addLines that control whether we ignore text in our shader file or add the lines to our shaderText.

Changes to our shaders


I've made two changes to our shaders, the first is that I've created a new shader files called "shadowmap.fs" that contains our samplePCF, shadow and shadowTest functions and we use #include in the various fragment shaders where we need these functions.

The second change is that I've combined our flatshader.fs, textured.fs and reflect.fs fragment shaders into a single standard.fs file that looks as follows:
#version 330

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

// info about our material
uniform float     alpha = 1.0;                      // alpha for our material
#ifdef textured
uniform sampler2D textureMap;                       // our texture map
#else
uniform vec3      matColor = vec3(0.8, 0.8, 0.8);   // color of our material
#endif
uniform vec3      matSpecColor = vec3(1.0, 1.0, 1.0); // specular color of our material
uniform float     shininess = 100.0;                // shininess

#ifdef reflect
uniform sampler2D reflectMap;                       // our reflection map
#endif

// these are in world coordinates
in vec3           E;                                // normalized vector pointing from eye to V
in vec3           N;                                // normal vector for our fragment

// these in view
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
in vec4           Vs[3];                            // our shadow map coordinates
out vec4          fragcolor;                        // our output color

#include "shadowmap.fs"

void main() {
#ifdef textured
  // start by getting our color from our texture
  fragcolor = texture(textureMap, T);  
  fragcolor.a = fragcolor.a * alpha;
  if (fragcolor.a < 0.2) {
    discard;
  };
#else
  // Just set our color
  fragcolor = vec4(matColor, alpha);
#endif

  // Get the normalized directional vector between our surface position and our light position
  vec3 L = normalize(lightPos - V.xyz);
  
  // We calculate our ambient color
  vec3  ambientColor = fragcolor.rgb * lightcol * ambient;

  // Check our shadow map
  float shadowFactor = shadow(Vs[0], Vs[1], Vs[2]);
  
  // We calculate our diffuse color, we calculate our dot product between our normal and light
  // direction, note that both were adjusted by our view matrix so they should nicely line up
  float NdotL = max(0.0, dot(Nv, L));
  
  // and calculate our color after lighting is applied
  vec3 diffuseColor = fragcolor.rgb * lightcol * (1.0 - ambient) * NdotL * shadowFactor;

  // now for our specular lighting
 vec3 specColor = vec3(0.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(Nv, halfVector));
    float specPower = pow(nxHalf, shininess);
  
    specColor = lightcol * matSpecColor * specPower * shadowFactor;
  };

#ifdef reflect
  // add in our reflection, this is one of the few places where world coordinates are paramount. 
  vec3  r = reflect(E, N);
  vec2  rc = vec2((r.x + 1.0) / 4.0, (r.y + 1.0) / 2.0);
  if (r.z < 0.0) {
   r.x = 1.0 - r.x;
  };
  vec3  reflColor = texture(reflectMap, rc).rgb;

  // and add them all together
  fragcolor = vec4(clamp(ambientColor+diffuseColor+specColor+reflColor, 0.0, 1.0), fragcolor.a);
#else
  // and add them all together
  fragcolor = vec4(clamp(ambientColor+diffuseColor+specColor, 0.0, 1.0), fragcolor.a);
#endif
}
Note the inclusion of our #ifdef blocks to change between our various bits of logic while reusing code that is the same in all three shaders.
We can now change our shader loading code in engine.h to the following:
  colorShader = newShader("flatcolor", "standard.vs", NULL, NULL, NULL, "standard.fs", "");
  texturedShader = newShader("textured", "standard.vs", NULL, NULL, NULL, "standard.fs", "textured");
  reflectShader = newShader("reflect", "standard.vs", NULL, NULL, NULL, "standard.fs", "reflect");
If we need it we could very quickly add a fourth shader that combines texture mapping and reflection mapping by simply passing "textured reflect" as our defines.

In the same way I've combined our shadow shaders into a single shader file.

Obviously there is a lot of room for improvement here, but it is a start and enough to keep us going for a little bit longer.

Download the source here

What's next?


Now we're ready to start working on our deferred shader.



Saturday, 9 April 2016

Shadow maps #3 (part 28)

Ok, time for the last part of implementing basic shadow maps. The technique we're going to look at in this post is called cascading shadow maps. This is a technique mostly used to improve the quality for lights that effect large areas such as our sunlight. The problem we've had so far is that a high quality shadow map will only produce shadows in a small area while a large area shadow maps will be of such low quality things get very blocky.

While using a smoothing technique like we did in our last post does improve this somewhat up close shadows do not look very good. The lower quality shadow maps are fine for things that are further away.

Now there are several techniques that can improve this each with their own strong points and weakpoints. One alternative I'd like to mention is altering the projection matrix so the projection is skewed based on the distance to the camera ensuring we have higher detail shadow maps closer to the camera in a single map.
I'd also like to point to a completely different technique called shadow volumes, I've not implemented those myself but reading about them I'm interested to try some day. They seem to give incredible results but they may be more difficult to implement if you have loads of moving objects. I'm no expert in them yet so I'll refrain from commenting too much.

The technique we'll be using is where we simply render multiple shadow maps and pick the one best suited to what we're rendering. So we have a high quality shadow map for shadows cast close to the camera, we have a medium quality shadow map for things further away, and we have a low quality shadow map for things even further out. The screenshot below shows the three maps where I've changed the color of the shadow cast to highlight the transitions between the shadow maps (I left the code that produces this in our terrain fragment shader but disabled it, if you want to play around with it):


Now I'm keeping things simple here and as a result we're adding more overhead then we need.
First off, where there is overlap in the shadow maps we're rendering a bunch of detail into the lower quality shadow maps that will never be used. We could use a stencil buffer to cut that out but I'm not sure how much that would improve things as we're really not doing anything in the fragment shader anyway. Another improvement I've thought about is using our bounding box checking logic to exclude anything that falls fully within the overlap space, that might make a noticeable difference.
Second, depending on our camera position and the angle of our sun we may not need the other shadow maps at all.
Third, I already mentioned this in my previous posts and this ties into the second point, we're centering our shadow maps on the camera position so in worse case half our our shadow maps will never be used. Adjusting our lookat point for our shadows may allow us to cover a greater area with our higher detail shadow map.

These are all issues for later to deal with. It's worth noting though that with the changes we're making today on my little MacBook Pro the frame rate has suffered and while we were rendering at a comfortable 60fps unless we move to high in our scene, it's dropped to 30 to 40fps at the moment.
I have added one small enhancement and that is that I only re-render our shadow maps if our lighting direction has changed (which usually is a static) or if our lookat point has moved more then a set distance (we do this by rounding our look at position).

Last but not least, I've added a small bit of code to react to the - and = (+) keys and move the position of the sun. There is no protection for "night time" so we actually end up lighting the scene from below.

Going from 1 to 3 shadow maps


Obviously we need to add support for our 3 levels of shadow maps first. This starts with adjusting our lightsource structure:
// and a structure to hold information about a light (temporarily moved here)
typedef struct lightSource {
  float             ambient;          // ambient factor for our light
  vec3              position;         // position of our light
  vec3              adjPosition;      // position of our light with view matrix applied
  bool              shadowRebuild[3]; // do we need to rebuild our shadow map?
  vec3              shadowLA[3];      // remembering our lookat point for our shadow map
  texturemap *      shadowMap[3];     // shadowmaps for this light
  mat4              shadowMat[3];     // view-projection matrices for this light
} lightSource;
Note that as we're not yet dealing with anything but our sun as a lightsource I'm not putting any code in yet to support a flexible number of shadow maps.

So we now have 3 shadow maps and three shadow matrices to go with them. There is also a set of flags that determine if shadow maps need to be rebuild and a set of look at coordinates that we can use to check if we've moved our camera enough in order to need to rebuild our shadow maps.

It is important to realise at this point that this won't be enough once we start moving objects around. The easiest is to update our rebuild flags but we may as well remove this all together once things start moving around. A better solution would be to render our shadow maps with all the static objects only, and either overlay or add in objects that move around as we render our scenes. Thats something for much later however.

Similarly our shader library is enhanced to support the 3 shadow maps as well:
...
// structure for encapsulating a shader, note that not all ids need to be present (would be logical to call this struct shader but it's already used in some of the support libraries...)
typedef struct shaderInfo {
  ...
  GLint   shadowMapId[3];           // ID of our shadow maps
  GLint   shadowMatId[3];           // ID for our shadow matrices
  ...
} shaderInfo;
...
void shaderSetProgram(shaderInfo * pShader, GLuint pProgram) {
  ...
  for (i = 0; i < 3; i++) {
    sprintf(uName, "shadowMap[%d]", i);
    pShader->shadowMapId[i] = glGetUniformLocation(pShader->program, uName);
    if (pShader->shadowMapId[i] < 0) {
      errorlog(pShader->shadowMapId[i], "Unknown uniform %s:%s", pShader->name, uName);
    };
    sprintf(uName, "shadowMat[%d]", i);
    pShader->shadowMatId[i] = glGetUniformLocation(pShader->program, uName);
    if (pShader->shadowMatId[i] < 0) {
      errorlog(pShader->shadowMatId[i], "Unknown uniform %s:%s", pShader->name, uName);
    };
  };
  ...

And we need a similar change to our materials library to inform our shaders of the 3 shadow maps:
...
bool matSelectProgram(material * pMat, shaderMatrices * pMatrices, lightSource * pLight) {
  ...
  for (i = 0; i < 3; i++) {
    if (pMat->matShader->shadowMapId[i] >= 0) {
      glActiveTexture(GL_TEXTURE0 + texture);
      if (pLight->shadowMap[i] == NULL) {
        glBindTexture(GL_TEXTURE_2D, 0);      
      } else {
        glBindTexture(GL_TEXTURE_2D, pLight->shadowMap[i]->textureId);
      }
      glUniform1i(pMat->matShader->shadowMapId[i], texture); 
      texture++;   
    };
    if (pMat->matShader->shadowMatId[i] >= 0) {
      glUniformMatrix4fv(pMat->matShader->shadowMatId[i], 1, false, (const GLfloat *) pLight->shadowMat[i].m);
    };
  };
  ...
These changes should all be pretty straight forward so far.

Rendering our 3 shadow maps


Rendering 3 maps instead of 1 is simply a matter of calling our shadow map code 3 times. For this to work I've changed our renderShadowMapForSun function so I can parse it parameters to let it know which shadow map we're rendering and at what level of detail we want it. I'm just adding the start of the code here as most of the function has stayed the same from our first part. Have a look at the full source on github to see that other changes needed:
...
// render our shadow map
// we'll place this in our engine.h for now but we'll soon make this part of our lighting library
void renderShadowMapForSun(bool * pRebuild, texturemap * pShadowMap, vec3 * pLookat, mat4 * pShadowMat, int pResolution, float pSize) {
  vec3 newLookat;

  // prevent rebuilds if we only move a tiny bit....
  newLookat.x = camera_eye.x - fmod(camera_eye.x, pSize/100.0);
  newLookat.y = camera_eye.y - fmod(camera_eye.x, pSize/100.0);
  newLookat.z = camera_eye.z - fmod(camera_eye.x, pSize/100.0);

  if ((pLookat->x != newLookat.x) || (pLookat->y != newLookat.y) || (pLookat->z != newLookat.z)) {
    vec3Copy(pLookat, &newLookat);
    *pRebuild = true;
  };

  // we'll initialize a shadow map for our sun
  if (*pRebuild == false) {
    // reuse it as is...
  } else if (tmapRenderToShadowMap(pShadowMap, pResolution, pResolution)) {
  ...
I'm highlighting this part of the code because it shows the changes we made to limit the number of times we rebuild our shadow maps. We round our lookat position based on the level of detail we want in our shadow map. For our closest shadow map we may only move our camera 15 units before we need to rebuild our shadow maps while for our higher detail map it will be 100 units. Obviously if our light position changes we set our rebuild flags to true and we rebuild all shadow maps.

Finally we need to call this method 3 times which we do in our engineRender method:
  ...
  if (pMode != 2) {
    renderShadowMapForSun(&sun.shadowRebuild[0], sun.shadowMap[0], &sun.shadowLA[0], &sun.shadowMat[0], 4096, 1500);
    renderShadowMapForSun(&sun.shadowRebuild[1], sun.shadowMap[1], &sun.shadowLA[1], &sun.shadowMat[1], 4096, 3000);
    renderShadowMapForSun(&sun.shadowRebuild[2], sun.shadowMap[2], &sun.shadowLA[2], &sun.shadowMat[2], 2048, 10000);
  };
  ...
So our highest quality shadow map is a 4096x4096 map that covers an area of 3000x3000 units (2*1500).
Our lowest quality shadow map is a 2048x2048 map that covers an area of 20000x20000 units.

Note that this is where the color coded rendering of the shadow maps does come in handy for tweaking what works well as the size of our maps depend a lot on the sizes of your objects and what you consider to be close or far.

Changing our shaders

The final ingredient is changing our shaders. Again at this stage we need to update all our shaders but I'm only going to look at the changes once.

In our vertex shader (and in our tessellation evaluation shader for our terrain) we now need to calculate 3 vertices projected for our shadow maps. In this case I'm fully writing them out as its faster then looping:
...
uniform mat4      shadowMat[3];   // our shadows view-projection matrix
...
// shadow map
out vec4          Vs[3];          // our shadow map coordinates

void main(void) {
  ...
  // our shadow map coordinates
  Vs[0] = shadowMat[0] * model * V;
  Vs[1] = shadowMat[1] * model * V;
  Vs[2] = shadowMat[2] * model * V;

  ...
Our fragment shaders need to be adjusted as well. First off we need to change our samplePCF function so it checks a specific shadow map:
...
uniform sampler2D shadowMap[3];                     // our shadow map
in vec4           Vs[3];                            // our shadow map coordinates
...
float samplePCF(float pZ, vec2 pCoords, int pMap, int pSamples) {
  float bias = 0.0000005; // our bias
  float result = 1.0; // our result
  float deduct = 0.8 / float(pSamples); // deduct if we're in shadow

  for (int i = 0; i < pSamples; i++) {
    float Depth = texture(shadowMap[pMap], pCoords + offsets[i]).x;
    if (pZ - bias > Depth) {
      result -= deduct;
    };  
  };
    
  return result;
}
...
And finally we need to change our shadow function to figure out which shadow map to use.

We simply start with our highest quality shadow map and if our projection coordinates are within bounds we use it, else we check a level up:
...
// check if we're in shadow..
float shadow(vec4 pVs0, vec4 pVs1, vec4 pVs2) {
  float factor;
  
  vec3 Proj = pVs0.xyz / pVs0.w;
  if ((abs(Proj.x) < 0.99) && (abs(Proj.y) < 0.99) && (abs(Proj.z) < 0.99)) {
    // bring it into the range of 0.0 to 1.0 instead of -1.0 to 1.0
    factor = samplePCF(0.5 * Proj.z + 0.5, vec2(0.5 * Proj.x + 0.5, 0.5 * Proj.y + 0.5), 0, 9);
  } else {
    vec3 Proj = pVs1.xyz / pVs1.w;
    if ((abs(Proj.x) < 0.99) && (abs(Proj.y) < 0.99) && (abs(Proj.z) < 0.99)) {
      // bring it into the range of 0.0 to 1.0 instead of -1.0 to 1.0
      factor = samplePCF(0.5 * Proj.z + 0.5, vec2(0.5 * Proj.x + 0.5, 0.5 * Proj.y + 0.5), 1, 4);
    } else {
      vec3 Proj = pVs2.xyz / pVs2.w;
      if ((abs(Proj.x) < 0.99) && (abs(Proj.y) < 0.99) && (abs(Proj.z) < 0.99)) {
        // bring it into the range of 0.0 to 1.0 instead of -1.0 to 1.0
        factor = samplePCF(0.5 * Proj.z + 0.5, vec2(0.5 * Proj.x + 0.5, 0.5 * Proj.y + 0.5), 2, 1);
      } else {
        factor = 1.0;
      };
    };
  };

  return factor;
}

void main() {
  ...
  // Check our shadow map
  float shadowFactor = shadow(Vs[0], Vs[1], Vs[2]);
  ...
And that's it.

For this part I've created a Tag in Github instead of a branch. We'll see which works better.
Download the source code

And a quick video showing the end result:


What's next


I think I've gone as far as I want with shadows for now. The next part may take while before I get it done as there is a lot involved rewriting our code so far to a deferred lighting model but that's what we'll be doing next.

After that we'll start looking at adding additional lights and looking at other shading techniques.
Somewhere in the middle we'll also start looking at adding a simple pre-processor to our shaders so we can start reusing some code and make our shaders easier to put together.


Sunday, 3 April 2016

Shadow maps #2 (part 27)

Okay, just a small one today.

When we look at texture mapping our GPU nicely interpolates the colors between pixels to not make our textures look very blocky when we come too close.
Now it does do the same when we query our shadow map however we're just interpolating our Z, when we then apply that to our rendering we still end up with a very blocky result:

Obviously in this case it is clear our shadow map simply doesn't contain the resolution we require to get nice looking shadows for our trees and we'll be looking at resolving that at least somewhat in our next post but we'll never get rid of this completely unless we're willing to waste GPU and memory on really large shadow maps.

Instead we'll take a page our of our texture mapping book and smooth our shadows and the algorithm we're going to use is commonly known as Percentage Closer Soft Shadows.

Now note that this technique isn't just for smoothing out shadows to get rid of our blockiness. Another use for it is to soften the shadows more as the distance between the surface and the shadow casting object grows as more ambient light is able to illuminate the surface. We won't be going into that today though.

The algorithm itself requires us to obtain values for surrounding pixels in our shadow map and obtain an average for our shadow. The more of those pixels are in shadow, the darker we render our surface.

To enable doing this we add a table of offsets to our shader:
// Precision ring
//      9 9 9
//      9 1 2
//      9 4 4
const vec2 offsets[] = vec2[](
  vec2( 0.0000,  0.0000),
  vec2( 0.0005,  0.0000),
  vec2( 0.0000,  0.0005),
  vec2( 0.0005,  0.0005),
  vec2(-0.0005,  0.0005),
  vec2(-0.0005,  0.0000),
  vec2(-0.0005, -0.0005),
  vec2( 0.0000, -0.0005),
  vec2(-0.0005, -0.0005)
);
Note that this table gives us the option to use 1, 2, 4 and 9 samples. We could add more rings if we wish to go further.

Now we replace our sample function with one that applies the PCF algorithm:
float samplePCF(float pZ, vec2 pCoords, int pSamples) {
  float bias = 0.0000005; // our bias
  float result = 1.0; // our result
  float deduct = 0.8 / float(pSamples); // deduct if we're in shadow

  for (int i = 0; i < pSamples; i++) {
    float Depth = texture(shadowMap, pCoords + offsets[i]).x;
    if (pZ - bias > Depth) {
      result -= deduct;
    };  
  };
    
  return result;
}
And now we call this new function from our shadow factor function:
// check if we're in shadow..
float shadow(vec4 pVs) {
  float factor;
  
  vec3 Proj = pVs.xyz / pVs.w;
  if ((abs(Proj.x) < 0.99) && (abs(Proj.y) < 0.99) && (abs(Proj.z) < 0.99)) {
    // bring it into the range of 0.0 to 1.0 instead of -1.0 to 1.0
    factor = samplePCF(0.5 * Proj.z + 0.5, vec2(0.5 * Proj.x + 0.5, 0.5 * Proj.y + 0.5), 4);
  } else {
    factor = 1.0;
  };

  return factor;
}
Note that for now I've duplicated this code in each shader and they are all using a sample size of 4 for now.

Here is our result with this sample size:


And with a sample size of 9:


Download the source here

What's next?

Okay, that was a short one. I've left the shadow map projection matrix calculation alone for now, I may come back to that at a later time but I haven't found a really good adjustment yet.

The next tutorial we'll have a look at cascaded shadow maps to get some sharper shadows up close.




Saturday, 2 April 2016

Shadow maps #1 (part 26)

So yes, I've decided to swap doing shadows first and then change the engine over to using a deferred lighting model.

Part of me wishes I hadn't. It isn't that shadows are difficult but in the state our engine currently is in, we're duplicating a few things. I really need to find time to add a pre-processor into our shader loading code. But we'll make do. Note that for our write up I'll only do things once so where code currently needs to be duplicated, have a look at the finished source code.

Rendering shadows requires knowing whether there is anything between the surface you are rendering and the lightsource that illuminates it.
It gets increasingly complex when more light sources are involved though that is something I won't get into now.

Shadowmaps are a bit of a cheat to allow us to quickly find out if light is being blocked out by another object. With a shadow map we render our scene from the perspective of the light source. As we render our scene the Z-buffer builds up and will eventually paint a picture of what are the closest objects that block out our light.

When we render our real scene besides projecting our vertices to screen space, we also project them using the same mvp we used when rendering it to the shadow map. That allows us to check the Z value for each fragment against our shadow map. If it's larger, we're behind something and we're thus in shadow.

This does require us to render our scene twice (or more if we have more light sources). This adds overhead but we've got a few things going for us:
  • we're only interested in our depth buffer, so we can create very simple and quick shaders that do as little calculations as possible
  • we can be more conservative with what we render, for spotlights we often need to only render a fragment of our scene, only for our sunlight we include a lot
  • we may not need to render everything, for instance it makes little sense to render our terrain into our shadow map, nothings beneath our terrain so there is nothing for it to cast its shadow on.
  • when stereo rendering we can reuse our shadow maps for both eyes, we don't need to render them twice
Also once we go beyond these initial stages there are other optimisations. For instance most things in your scene are static, so you could render your shadow maps once with all the static objects, then in your render loop make a copy and render just the objects that move around. In our example below that requires a bit more thought as our light "moves" to deal with the large area a sunlight covers but there are still ways to use this optimisation.

We're just going to render a shadow map for our sunlight. Because the sun is very very far away and light rays hit our surfaces pretty much parallel we're going to use an orthographical projection for this.
When we'll eventually add spotlights we'll use a perspective projection to create proper shadows.

Creating our shadow map

Now here one of our previous posts comes in very handy. For our shadow map we're going to render to texture, it's just that our texture is a depth buffer :)

So we start by adding a handy function for this to our texture map library that is a simplified version of our render to texture function we added in our LOD tutorial:
// Prepare our texture as a shadow map (if needed) and makes our
// shadow map frame buffer active
bool tmapRenderToShadowMap(texturemap * pTMap, int pWidth, int pHeight) {
  if (pTMap == NULL) {
    return false;
  };

  // check if we can reuse what we have...
  if ((pTMap->width != pWidth) || (pTMap->height != pHeight)) {
    // chuck our current frame buffer JIC.
    tmapFreeFrameBuffers(pTMap);
  };
Note that we'll decide to rebuild our shadow map if its size changes. Not something we'll use today but it can be handy sometimes. We just need to be conservative as rebuilding our buffers will introduce a fair amount of overhead.
  // create our frame buffer if we haven't already
  if (pTMap->frameBufferId == 0) {
    GLenum status;

    pTMap->filter = GL_LINEAR;
    pTMap->wrap = GL_CLAMP;
    pTMap->width = pWidth;
    pTMap->height = pHeight;
Obviously we're assuming no texture is loaded so we set our values as we need them to be.
    glGenFramebuffers(1, &pTMap->frameBufferId);
    glBindFramebuffer(GL_FRAMEBUFFER, pTMap->frameBufferId);

    // init our depth buffer
    glBindTexture(GL_TEXTURE_2D, pTMap->textureId);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, pTMap->width, pTMap->height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, pTMap->filter);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, pTMap->filter);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, pTMap->wrap);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, pTMap->wrap);    

    // bind our depth texture to our frame buffer
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, pTMap->textureId, 0);
So this bit is nearly identical to how we created our frame buffer and added our Z-buffer in our render to texture example. The difference is that we're not adding any color buffers. I'm also using our textureId as we've already generated a texture object when we construct our object and it seems wasteful to create a second one just to use our depth buffer.
    // and make sure our framebuffer knows we draw nothing else...
    glDrawBuffer(GL_NONE);
    glReadBuffer(GL_NONE);
Now here is a bit of magic, these two commands ensure our frame buffer knows there are no color buffers to write to or read from. We're just writing to our Z-buffer.
    // and check if all went well
    status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE) {
      errorlog(status, "Couldn't init framebuffer (errno = %i)", status);
      tmapFreeFrameBuffers(pTMap);
      return false;
    } else {
      errorlog(0, "Created shadow map %i,%i", pWidth, pHeight);
    };
  } else {
    // reactivate our framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, pTMap->frameBufferId);
  };

  return true;
};
And the last bit again is the same as our render to texture function, we check if we've successfully created our frame buffer and reuse our frame buffer next time we call our function.

Our shadow shaders

As I mentioned we need simplified shaders to render our objects to our shadow maps. We have one vertex shader and two fragment shaders. We need an extra fragment shader to deal with texture maps that have an alpha such as those that we use for our leaves or our leaves would cast square shadows. We don't want that overhead if we don't need it.

Here is our vertex shader:
#version 330

layout (location=0) in vec3 positions;
layout (location=2) in vec2 texcoords;

uniform mat4      mvp;            // our model-view-projection matrix
out vec2          T;              // coordinates for this fragment within our texture map

void main(void) {
  // load up our values
  vec4 V = vec4(positions, 1.0);
  T = texcoords;
  
  // our on screen position by applying our model-view-projection matrix
  gl_Position = mvp * V;
}
And our normal fragment shader:
#version 330

out vec4          fragcolor;

void main() {
  // this does nothing, we're only interested in our Z
  fragcolor = vec4(1.0, 1.0, 1.0, 1.0);
}
And our texture shadow shader:
#version 330

uniform sampler2D textureMap;                       // our texture map

in vec2           T;                                // coordinates for this fragment within our texture map
out vec4          fragcolor;

void main() {
  fragcolor = texture(textureMap, T);
  if (fragcolor.a < 0.2) {
    discard;
  };
}
By now these should be pretty self explanatory. Even though we do output a fragment color that output is ignored

Finally in our load_shaders we actually load these shaders:
  solidShadow = newShader("solidshadow", "shadow.vs", NULL, NULL, NULL, "solidshadow.fs");
  textureShadow = newShader("textureshadow", "shadow.vs", NULL, NULL, NULL, "textureshadow.fs");
It starts to get interesting once we start using our shaders. I've modified our material library to record both the normal shader and the shadow shader for each material. If no shadow shaders is set the material doesn't cast a shadow. For this to work we've added two functions to our material library:
  • matSetShadowShader assigns a shadow shader to our material
  • matSelectShadow selects that shader and set it up
Note that I've also moved our lightSource struct into this library temporally and added both a shadowMap texture and shadowMat view-projection matrix variable to this structure. This will soon get it's own place.

Finally we assign our shadow shaders to our materials in our load_objects function. Note that I have moved a few things around where I didn't want materials to get a shadow shader:)
  ...
  
  // assign shaders to our materials
  lnode = materials->first;
  while (lnode != NULL) {
    mat = (material * ) lnode->data;

    // assign both solid and shadow shaders, note that our shadow shader will be ignored for transparent shadows
    if (mat->reflectMap != NULL) {  
      matSetShader(mat, reflectShader);
      matSetShadowShader(mat, solidShadow);
    } else if (mat->diffuseMap != NULL) {          
      matSetShader(mat, texturedShader);
      matSetShadowShader(mat, textureShadow);
    } else {
      matSetShader(mat, colorShader);
      matSetShadowShader(mat, solidShadow);
    };
    
    lnode = lnode->next;
  };

  ...

Rendering our shadow map

Now it's time to render our shadow map. First I've enhanced our meshnode library and added a meshNodeShadowMap function to it that renders our node using the shadow shaders. It's a dumbed down version of our meshNodeRender function that only renders non transparent objects for which a shadow shader is available.
// render suitable objects to a shadow map
void meshNodeShadowMap(meshNode *pNode, shaderMatrices * pMatrices) {
  dynarray *      meshesWithoutAlpha  = newDynArray(sizeof(renderMesh));
  mat4            model;
  int             i;

  // prepare our array with things to render, we ignore meshes with alpha....
  mat4Identity(&model);
  meshNodeBuildRenderList(pNode, &model, pMatrices, meshesWithoutAlpha, NULL);

  // we should sort our meshesWithoutAlpha list by material here and then only select our material 
  // if we're switching material  
  for (i = 0; i < meshesWithoutAlpha->numEntries; i++) {
    bool selected = true;
    renderMesh * render = dynArrayDataAtIndex(meshesWithoutAlpha, i);
  
    shdMatSetModel(pMatrices, &render->model);
    if (render->mesh->material != NULL) {
      selected = matSelectShadow(render->mesh->material, pMatrices);
      if (selected) {
        meshRender(render->mesh);
      };
    };
  };

  dynArrayFree(meshesWithoutAlpha);
};
There are a few small tweaks to meshNodeBuildRenderList that allow for a NULL pointer to be used for the dynarrays and prevent rendering our bounding boxes to our depth buffer.

Now it's time to enhance our rendering loop. At the start of engineRender I've added this snippit of code:
  // only render our shadow maps once per frame, we can reuse them if we're doing our right eye as well
  if (pMode != 2) {
    renderShadowMapForSun();
  };
It calls the renderShadowMapForSun function unless we're rendering our right eye (as we're reusing our left eyes map).

The renderShadowMapForSun function is where the magic happens, lets look at it in detail:
// render our shadow map
// we'll place this in our engine.h for now but we'll soon make this part of our lighting library
void renderShadowMapForSun() {
  // we'll initialize a 4096x4096 shadow map for our sun
  if (tmapRenderToShadowMap(sun.shadowMap, 4096, 4096)) {
    mat4            tmpmatrix;
    vec3            tmpvector;
    shaderMatrices  matrices;
    GLint           wasviewport[4];

    // remember our current viewport
    glGetIntegerv(GL_VIEWPORT, &wasviewport[0]);

    // set our viewport
    glViewport(0, 0, 4096, 4096);
So above we've called tmapRenderToShadowMap to create our shadowMap (first time round) and select our frame buffer. We then set our viewport to match. Now here I was a little surprised to find out the viewport is not bound to the framebuffer so this overwrites the viewport configuration we had set in our main.c code. We thus store this before hand.
We've created a 4096x4096 map which should provide us with enough detail to get started.
    // clear our depth buffer
    glClear(GL_DEPTH_BUFFER_BIT);      

    // 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);
    // disable alpha blending  
    glDisable(GL_BLEND);
    // solid polygons
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
This should look pretty familiar, we clear our depth buffer, enable what we need to but there is one strange little tidbit here. We're culling our front faces instead of our back faces.
Assuming our objects are all solid this prevents objects to throw shadows onto themselves.
    // need to create our projection matrix first
    // for our sun we need an orthographic projection as rays of sunlight pretty much are parallel to each other.
    // if this was a spotlight a perspective projection gives the best result
    mat4Identity(&tmpmatrix);
    mat4Ortho(&tmpmatrix, -10000.0, 10000.0, -10000.0, 10000.0, -50000.0, 50000.0);
    shdMatSetProjection(&matrices, &tmpmatrix);
As mentioned, we use an orthographic projection for our sun. Note that our near place is -50000. Our orthographic projection maps our Z buffer to -1.0 => 1.0, if we set our near plane to 0 any objects between our "eye" and halfway through our scene would fall behind the clipping point. Oops.
The our map spans is important. The larger our area the further down in the scene we'll be able to render shadows but at the cost of precision. Our map spans an area of 20000x20000 which is enough for our scene to get shadows far away enough without sacrificing too much precision but you'll see it isn't perfect. We'll be looking at way to improve this in the next two posts.
    // We are going to adjust our sun's position based on our camera position.
    // We position the sun such that our camera location would be at Z = 0.
    // Our near plane is actually behind our 'sun' which gives us some wiggleroom.
    vec3Copy(&sun.adjPosition, &sun.position);
    vec3Normalise(&sun.adjPosition);  // normalize our sun position vector
    vec3Mult(&sun.adjPosition, 10000.0); // move the sun far enough away
    vec3Add(&sun.adjPosition, &camera_eye); // position in relation to our camera
We readjust our position of our sun so it's not too far away as it becomes the position of our camera.
    // Now we can create our view matrix, here we use a lookat matrix from our sun looking towards our camera position.
    // There is an argument to use our lookat point instead as in worst case scenarios half our of shadowmap could
    // relate to what is behind our camera but using our lookat point risks not covering enough with our shadowmap.
    //
    // Note that for our 'up-vector' we're using an Z-axis aligned vector. This is because our sun will be straight
    // up at noon and we'd get an unusable view matrix. An Z-axis aligned vector assumes that our sun goes from east
    // to west along the X/Y axis and the Z of our sun will be 0. Our 'up-vector' thus points due north (or south
    // depending on your definition).
    // If you do not align your coordinate system to a compass you'll have to calculate an up-vector that points to your
    // north or south 
    mat4Identity(&tmpmatrix);
    mat4LookAt(&tmpmatrix, &sun.adjPosition, &camera_eye, vec3Set(&tmpvector, 0.0, 0.0, 1.0));
    shdMatSetView(&matrices, &tmpmatrix);
And we use our good old mat4LookAt function to set our view matrix. I won't repeat what I mention about the up-vector in the comments in the code, just read them:)
    // now we override our eye position to be at our camera position, this is important for our LOD calculations
    shdMatSetEyePos(&matrices, &camera_eye);
This is an important small change we added to our matrices object. We can override our eye position which is important here because our LOD calculations would otherwise be incorrect.
    // now remember our view-projection matrix, we need it later on when rendering our scene
    mat4Copy(&sun.shadowMat, shdMatGetViewProjection(&matrices));
We also need to remember our view-projection matrix because we need it later on when rendering our scene.
    // and now render our scene for shadow maps (note that we only render materials that have a shadow shader and we ignore transparent objects)
    if (scene != NULL) {
      meshNodeShadowMap(scene, &matrices);    
    };
Last but not least, we call our meshNodeShadowMap render function
    // and output back to screen
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glViewport(wasviewport[0],wasviewport[1],wasviewport[2],wasviewport[3]);
  };
};
And as part of our cleanup we reset our viewport back to what it was before. At the end of this we have our shadow map, but we're not using it yet.

Applying our shadows


Now we're ready to actually cast some shadows in our end result. This has become fairly simple at this point in time. First off we need to make sure our shaders know what shadowmap to use and what our shadow view-projection matrix is. Luckily these are both stored in our lightSource structure so we simply need to add a small code fragment to matSelectProgram:
  ...

  if (pMat->matShader->shadowMapId >= 0) {
    glActiveTexture(GL_TEXTURE0 + texture);
    if (pLight->shadowMap == NULL) {
      glBindTexture(GL_TEXTURE_2D, 0);      
    } else {
      glBindTexture(GL_TEXTURE_2D, pLight->shadowMap->textureId);
    }
    glUniform1i(pMat->matShader->shadowMapId, texture); 
    texture++;   
  };
  if (pMat->matShader->shadowMatId >= 0) {
    glUniformMatrix4fv(pMat->matShader->shadowMatId, 1, false, (const GLfloat *) pLight->shadowMat.m);
  };

  ...

So now we need to update our shaders. Now these changes at this point in time need to be applied to multiple shaders. I've added them to our terrain shader, our flatshader, our textured shader and our reflection shader. We'll only discuss the changes to our textured shader here.

First we start with our standard.vs vertex shader, I'll just highlight the changes:
...
uniform mat4      shadowMat;      // our shadows view-projection matrix
...
// shadow map
out vec4          Vs;             // our shadow map coordinates

void main(void) {
  ...
  // our shadow map coordinates
  Vs = shadowMat * model * V;
  ...
}
So we've added our shadow view-projection matrix as a uniform and added an output called Vs. Then we calculate Vs by projecting our vertex position.

In our fragment shaders we add a new uniform for our shadowMap and an input for Vs at the start:
uniform sampler2D shadowMap;                        // our shadow map
in vec4           Vs;                               // our shadow map coordinates
After that we add two helper functions that use our Vs input to perform our lookup in our shadowMap and return a factor between 0.0 (fully in shadow) and 1.0 (not in shadow). That seems a bit like overkill right now but in part two of this write up we'll expand on this:
// sample our shadow map
float sampleShadowMap(float pZ, vec2 pCoords) {
  float bias = 0.00005;
  float depth = texture(shadowMap, pCoords).x;
  
  if (pZ - bias > depth) {
    return 0.0;
  } else {
    return 1.0;
  };  
}

// check if we're in shadow..
float shadow(vec4 pVs) {
  float factor;
  
  vec3 Proj = pVs.xyz / pVs.w;
  if ((abs(Proj.x) < 0.99) && (abs(Proj.y) < 0.99) && (abs(Proj.z) < 0.99)) {
    // bring it into the range of 0.0 to 1.0 instead of -1.0 to 1.0
    factor = sampleShadowMap(0.5 * Proj.z + 0.5, vec2(0.5 * Proj.x + 0.5, 0.5 * Proj.y + 0.5));
  } else {
    factor = 1.0;
  };

  return factor;
}
And in our main function we'll call shadow to obtain our shadow factor and apply it:
void main() {
  ...
  // Check our shadow map
  float shadowFactor = shadow(Vs);
  ...
  // and calculate our color after lighting is applied
  vec3 diffuseColor = fragcolor.rgb * lightcol * (1.0 - ambient) * NdotL * shadowFactor; 
  ...
    specColor = lightcol * matSpecColor * specPower * shadowFactor;
  ...
}
Note how we simply add our shadow factor into our diffuse and specular colour calculation.

And we have shadows:

Download the source here

What's next?


This is only a start. When you move the camera around you'll see we've got plenty of things that need to improve. Very simply put, we don't have enough detail in our shadow map. We're also sacrificing half our shadow map as part of the shadow maps relates to shadows that are behind our camera.

In the next part we'll start looking at "Percentage Close Filtering" which will be a small post on smoothing out our shadow maps. We'll also look at ways to improve our projection matrix so we sacrifice less detail.

After that we'll look at cascaded shadow maps, we basically render more then 1 shadow map for our light source so we can use a higher detail one for shadows that are close to our camera.