To be able to implement volumetric lights I had to start with shadows for the sun light. As it wasn’t my primary focus I went for a straightforward shadow map implementation on a 2k texture. It’s easy and quick to code, but yeah, the results are ugly.
I didn’t planned to implement a more advanced shadow map technique anytime soon, but I also didn’t want to stay with those ultra pixaleted shadows, so I tried to apply my two current favorite techniques: dithering and temporal supersampling :).
Let see what it can do !
First step, offset the shadow map bias by a value given by a Bayer matrix :
float shadow = 1.0f; float ditherValue = ditherPattern[screenSpacePosition.x % 4][screenSpacePosition.y % 4]; if (shadowMapValue < worldByShadowCamera.z - bias * ditherValue) shadow = 0.0f;
And then a bilateral blur. In my engine I already have a bilateral blur pass for the SSAO, with one free channel in the texture. So I just added the shadow value in the alpha channel of my SSAO texture and got the blur for free. It’s also convenient for the lighting stage as the tiled deferred shader can read both AO and sun light shadow in one tap.
Here is what it looks like now:
It look way better, and it was really a few lines of codes ! Plus by merging it with the ambient occlusion it’s almost free !
And in movement there is a lot of flickering.
It’s now time for some temporal supersampling !
Again in my case it was really easy as I already implemented this for the ambient occlusion. I just had to store a “previous frame shadows” texture and give it to my AO shader that already take care of rejecting bad previous pixels based on depth.
The flickering is almost totally gone ! But as you notice there is a lot of ghosting, especially when the sun light is moving. This is because wrong pixels rejection is only based on their depth value, it’s good enough for AO, but not for shadows. When the sun is moving depth won’t change and yet the previous value shouldn’t be used.
So I added a pixel rejection based on the difference between the current and previous values. It means that if the value changed too much between two frames then something must be wrong. The hard part is to find the right function and the right values to be able to remove the obviously wrong datas but still keep enought informations to be efficient. It’s just my first experiments with this and I need to read some papers/presentations and do more tests to improve the result, and maybe write more about it. Here is my results so far.
It’s better ! A little of the flickering is back, but it’s still a great improvement compared to what I had at first.
But the issue with the dithering pattern being noticeable in some places isn’t resolved. To remove this I used two tricks. First I changed the dither offset used by a given pixel each frame. It would normally induce a lot of flickering, but the temporal supersampling smooth it and it become unnoticeable.
Then I randomly change the sun direction by a small amount. In not yet perfectly happy with this as it’s currently really noticeable even if it remove some artifact and had a soft shadow effect. Maybe I should use a predefined pattern instead of a completly random offset, in order to have a better temporal stability.
That’s it for now ! There is still a lot of room for improvement, but just using some quick tricks and existing resources in my engine I was able to improve the looks of my shadows, at a very limited cost and in a few hours. I really like the possibilities offered by the temporal supersampling, I will definitely use it more !
To conclude here is two screenshots of the shadows only, before and after.