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 :)

No comments:

Post a Comment