2017년 9월 30일 토요일

Directional Light Shadow Caching with UE4 Deferred Shadow



Multiple shadow map for one light

Shadow map technique is widely used for realtime rendering. Some methods like cascaded shadow map uses multiple shadow map to cover wide area effectively. Forward renderer bind multiple shadowmaps or use shadowmap atlas to properly project world space position to light space. Sometimes this multiple texture binding is not good for various reason. For example, binding multiple shadowmaps in aUE4 basepass is bad idea because it will reduce limit texture usage of material editor. Like landscape material this could be severe problem. Atlsing could be solution but this is not topic this time.

Deferred shadow

UE4 uses special buffer which uses to draw shadow result, called LightAttenuationBuffer. UE4 project world position to light space in basepass or lighitng pass. Instead projection is being done special ShadowProjection which only calculate shadow value of specific light.
This seems to be a little inefficient because of multiple pass. But it is very useful for various reason. The best benefit of this method is easy combining multiple shadow map like cascaded shadow maps. We dont have to concern which shadow map should be sampled for some world position. We just can MIN blend to shadow result buffer(LightAttenuationBuffer).

Shadow Caching

Dynamic shadow could be very expensive in various situations. It require additional shadow depth pass. If geometry complexity is high or masked material is used, the cost could be even higher. I see many next-gen console titles uses similar shadow caching technique to reduce the cost of their shadow depth pass. Caching or Pre-baking of shadow depth map could reduce the cost of shadow depth pass to almost zero in specific condition. Even worst case, shadow depth cost could be distributed to several frames. But this technique only could be applied when shadow casters and shadow receiver is specified. For example, spot/point light  has specific sets of shadow casters and receivers. Shadow for these light type could be easily cached or baked to limted size texture. But shadows for global light(ex : directional light) cannot be cached to one texture, and usually updated view dependantly every frame. UE4 support shadow caching only for point, spotlights.

Directionallight Caching Volume

With deferred shadow(LightAttenuationBuffer), We can cache portion of shadow of directionallight. We still need view dependant cascaded shadow for large object and moving characters(including vertex animated foliage and trees). The idea is place a volume at the area with dense geomtry and objects in this volume is excluded from cascaded shadow map rendering and a new cached shadow setup will be created with frutums created by that volume and directional light.



Like above drawing, we can set up shadow for group of objects for sun light(Green box is shadow caching volume, red box is shadow frustum) and we can easily cache this shadow using existing caching system. And this can be merged to cascaded shadow result(LightAttenuationBuffer). We can barely distinguish which pixel is from cascaded shadow or caching volume shadow.

UE4 Shadow Caching

After implemented this feature. I pretty satisfied that UE4 gracefully is adjusting shadow map size according to projected screen size of shadow so that total memory usage is being kept below 30 MB in typical setup. And because all cached shadow is  automatically recached if object or light move, artistic control is minimized.
file

ESM or VSM

ESM or VSM enables pre-filtering shadow map and enables soft shadow(despite light leak). When shadow map is being cached, pre-filtering cost could almost ignored. So I am considering  ESM filtering for environment shadowing.