1
$\begingroup$

I have a simulation software where the user can set the camera parameters such as the field of view. If the field of view is about 90°, the cascaded shadow images fit perfectly, as in Figure 1.

enter image description here

Figure 1: Three shadow cascades on a 90° FoV.

However, if the field of view is very small, most of the pixels of the shadow map are outside the frustum. See figure 2.

enter image description here

Figure 2: Three shadow cascades on a very small FoV.

I played around a bit and figured that this problem could be easily solved by changing the projections of the shadow map to fit better into the frustum (see Figure 3).

enter image description here

Figure 3: The three shadow cascades are scaled in order to better adapt them to the frustum.

Then I noticed that the shadow flickers when you move / rotate the camera. So the cascades have to be aligned in a voxel grid, and when you move / rotate the camera, the cascade jumps to the neighboring grid cell. From now on, the cascades are no longer rotated. This works very well for square cascades and large fields of views. However, this is not possible for scaled cascades.

So I came up with the idea of using many more cascades with a lower shadow map resolution (see Figure 4)

enter image description here

Figure 4: Many square shadow cascades on a very small FoV.

I haven't tested it now because there are so many things to change in the code, but I think the downside is that if the sun and the camera are aligned the same (directly behind the camera), the geometry that is close to the camera will be projected onto all the shadow maps. Due to the high number of shadow maps, this would slow down my renderings.

At the moment I have a texture2D_Array assigned to my framebuffer, and within the geometry shader the triangles are duplicated via the shadow cascades.

What is the best way to cascade shadows when the field of view is small? How to deal with telephoto lenses? (FoV < 1°). Or is there a better algorithm than cascaded shadow mapping? (should work without raytracing)

$\endgroup$
5
  • $\begingroup$ What do you mean by " I noticed that the shadow flickers when you move / rotate the camera"? The shadow edges or the entire shadow? If it is the shadow edges is the code doing anything to stabilized the shadow cameras? $\endgroup$
    – pmw1234
    Commented Nov 13 at 22:59
  • $\begingroup$ @pmw1234 If you attach the shadow map perfectly to the camera frustum and move or turn the camera slowly, shadows are projected from one pixel of the shadow map to the other. This flickers a little. This can be remedied by using a voxel grid, where the size of an individual voxel is as large as a pixel of the shadow map. The shadow map can only be on the voxels, not between them (modulo) $\endgroup$
    – Thomas
    Commented Nov 14 at 10:50
  • $\begingroup$ The reason I ask is I do something very similar with cascades and when I narrow the fov down to something like 12 degree's the shadow quality actually improves considerably. As I widen the angle out to 90 degree's those "wedges" on either side of the camera frustum that are not in view start consuming a lot of my shadow image and shadow quality goes down. In both case's I use "camera stabilization" to snap the shadow camera's to a single world space aligned 3d position. (which sounds similar to your voxel technique). How the cascades are rendered also has an effect on the result. $\endgroup$
    – pmw1234
    Commented Nov 14 at 15:13
  • $\begingroup$ The end result is the shadows are very stable with almost no perceptible swimming or flickering. Even when the shadow resolution is low and the shadows are highly aliased. $\endgroup$
    – pmw1234
    Commented Nov 14 at 15:17
  • $\begingroup$ @pmw1234 so you are saying, with the technique you are using, small FoV have better shadow quality than larger FoV? Can you share a paper or some pseudo code? That would help me a lot! =) $\endgroup$
    – Thomas
    Commented Nov 14 at 17:53

1 Answer 1

2
$\begingroup$

I'll attempt to do a clear writeup of this, however, there is a lengthy explanation in the book fged2 that is much better then what I can do.


The first piece is making sure the shadow projection matrix has a constant size in light space for both the x and the y directions. This step keeps the physical size of each Texel constant in each cascade. If we just compute them using pure floating point math then there is a fractional part that can cause the Texel positions of the final calculation to move fractionally between different positions depending on the camera position and results in shadows that pop from one place to another as the camera moves.

To get rid of the fractional part the book uses the ceil function, but I think the code could probably get away with floor. At any rate use $d_c =ceil(d)$ on the x and y sizes of the parallel projection. $d_c$ should be the same for both, ie $2/d_c$.

The second piece is to figure out the physical size of each Texel of the shadow map in light space and use that to guarantee the shadow camera's x and y position is an integral multiple of the Texel size. The physical size of each Texel after computing $d_c$ is just $P=d_c/s_d$ where $s_d$ is the shadow map size. This gives $P$ as the physical size of each Texel. Now use that to snap the camera to the grid. To do this we compute the cameras x and y grid position, then multiply that by the grid size to snap the camera into place for both x and y positions. Where the grid size is the physical size of each Texel $P$. Here is the calc for $x$ but $y$ is basically the same $x_p=floor(Xsize/(2P))*P$ Where $Xsize$ is the projections x dimension (the distance across it) in light space. Also the floor function is doing the same function as it did in part 1 above. Do the same thing for y and use the usual value for z. This gives us the camera position.

Finally we need a stabilized cascade matrix. Normally you would compute the matrix then takes its inverse but that process will put a fractional piece back into the matrix equation and cause popping again so instead the book recommends just computing the inverse directly using the negative of the camera position computed above for the last column of the matrix. (really you can get away with just computing the inverse and then just put the negative of the camera position in the last column)

One last detail is the shadow map size must be a power of 2. Power of 2 fractions can be represented exactly on the GPU. ie $1/2^n$

This sounds pretty involved when I write it up like this, but all it is really doing is snapping the camera and the projection to a grid that guarantees Texel's always map to the same locations. If you implement these one at a time. Part one only gives a minor improvement, part 2 gives a significant improvement, and part 3 makes shadows more stable then what I have seen in a lot of games. In fact I think it has made me into a shadow snob.


Cascade shadow layout and overlap

enter image description here

$\endgroup$
5
  • $\begingroup$ You are right, it makes sense that the viewport resolution and the shadowmap resolution fit better if the FoV angle is small. But how many cascades are you using? Are you rendering landscapes? $\endgroup$
    – Thomas
    Commented Nov 18 at 20:17
  • $\begingroup$ I am using 4 cascades currently but the code can do 1 to 6. 1024x1024 is my low quality map resolution target. It does do landscapes out to 350 meters on low resolution with 4 cascades. $\endgroup$
    – pmw1234
    Commented Nov 18 at 22:40
  • $\begingroup$ When you say “shadow map size”, do you mean the resolution or the size of the cascades? Can you add a sketch of the cascades, like my figures? $\endgroup$
    – Thomas
    Commented Nov 19 at 6:47
  • $\begingroup$ I was referring to the shadow map resolution for map size. I'll add a link to the figures from the book that shows the cascades. (my code is pretty much switched over to using the method for the book) Note that the cascades overlap on purpose so the transition between cascades can be blended which helps avoid that sudden change in aliasing between cascade levels. I'll also see if I can do a short video of some exaggerated aliasing to show the stability. (but no promises =) $\endgroup$
    – pmw1234
    Commented Nov 19 at 10:09
  • $\begingroup$ There is no good way to add a video that isn't first put on some external website, I tried a gif and it ended up the size of a postage stamp, and it was terribly degraded to the point of being useless. It doesn't add much to the answer anyway. $\endgroup$
    – pmw1234
    Commented Nov 19 at 11:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.