Point Light Shadow Mapping – Part 2

As written a few days back, I have implemented point light shadow mapping in Kourjet using single pass cube map rendering. I was not satisfied entirely with the performance and decided to spend some time improving my implementations. I must say that I am quite pleased with the result: on the same test scene, frame rate jumped from 15 frames per second to almost 60 (both for debug builds). While still rather slow, I must say that I have not optimised any routine but rather added some algorithms to help. I guess the next step will be to start looking for the bottlenecks and optimise some code.


Main Render Path Modifications

The main change has been to use a single shadow map for all the point lights instead of one per point light. The main advantage is that instead of N cube maps (N being the number of lights in the scene), we are only having 1. The main drawback is that we need to change the render target more often (2*N + 1 using single pass rendering instead of N + 1). However, this solution is very generic and allows to have as many lights as you want in the scene without requirering allocation of N cube shadow maps.

The main render path now looks like this:

LightSet = Lights visible in the view
MainObjectSet = Objects visible in the view
Foreach L in LightSet
    SetShadowMapRenderTarget( L )
    RenderShadowMap( L )
    SetMainRenderTarget()
    Foreach O in MainObjectSet
        Render( O )
    End
End

Whereas, previously, it was:

LightSet = Lights visible in the view
MainObjectSet = Objects visible in the view

Foreach L in LightSet
    SetShadowMapRenderTarget( L )
    RenderShadowMap( L )
End

SetMainRenderTarget()
Foreach L in LightSet
    Foreach O in MainObjectSet
        Render( O )
    End
End

Multi-Pass VS Single-Pass cube mapping

As of now, I must say that I am still not convinced of the single-pass rendering goodness for cube maps. Currently, I get 45 frames per second with single pass rendering, whereas I get 75 frames per second using single pass rendering on the same scene. I might be missing some optimisations (specially with render state changes) though because I must say the geometry shaders are still very new.

Multi-Pass

Here is the render path for single-pass rendering:

L = current light
BaseObjectSet = Objects within light range
Set cube shadow map as 6 render targets at once
Foreach O in BaseObjectSet
    For each face of the cube map
        Test O with the current face frustum
        If O is visible, set the geometry shader flag for this face
    End

    Render( O )
End

Inside the geometry shader, inside the face loop, we simply test against the flag to know if we have to output a primitive to that face or not (see shader code).

Single-Pass

Here is the render path for multi-pass rendering:

L = current light
For each face of the cube map
    BaseObjectSet = Objects within light range and in current face frustum
    Skip the rest if BaseObjectSet is empty
    Set cube map face as single render target
    Foreach O in BaseObjectSet
        Render( O )
    End
End

Conclusion

All in all I find the implementation is quite elegant and flexible. I must still do a performance analysis to check out where the bottlenecks are and optimise this. Another feature I would like to investigate (to minimise render target changes), is the possibility to have a pool of shadow maps thus allowing to have a solution to update lights in batches and then to render the main scene. The drawback is that you can have only a fixed amount of active lights (depending on the number of shadow maps in your pool) but the advantage is that you’d minimise greatly the number of render target switches.

Next on the list is shadow mapping for directional lights and spot lights (using a technique similar to cascaded shadow maps for example).

Here is some source code for the effects used to render the shadow maps:

For the rest of the source code as well as the sample programs and executables, you can download the Kourjet project from SVN: revision 49.

  1. No comments yet.

  1. November 30th, 2009
  2. January 16th, 2013