Implementing multiple lighting shadows in a pixel shader without relying on shadow maps for shadow effects.
Fake.Shadows.mp4
Demo (Available on Web, Mobile and MetaQuest - Powered by Spatial Creator Toolkit)
https://www.spatial.io/s/Fake-Character-Shadows-65ed3e7bd6afc7e93521bf59
Shadow Mapping, a common technique in realtime-graphics provides an accurate method for implementing shadows, but it comes with clear limiations.
In the art concept above, if there is no strong main light and multiple light sources need to be utilized, the effectiveness of Shadow Mapping diminishes significantly. In the absence of a primary light, the Shadow Mapping techniques becomes less effective and results in unnecessary shadow map computations.
(Images from Polycount.wiki http://wiki.polycount.com/wiki/Decal)
Considering past experiences with Blob Shadows in casual games for web and mobile devices where precise shadows are not always necessary and approximations are acceptable, I started exploring a simple idea applied in the pixel shader of background objects. Starting from this concept, I initiated the shader sketch.
(shader sketch 1)
fakeShadows.mp4
(shader sketch 2)
FakeShadows22.mp4
Below is the shader code that calculates the shadow approximately. As you can see it's very simple and very approximate. Even though they are applied per light, it's still very light.
half CalcFakeShadowPerLight(half4 light, half3 playerPos, half playerRad, half3 posToPlayer, half3 posWS)
{
// Calc dot
half3 playerToLight = normalize(light.xyz - playerPos);
half d = dot(posToPlayer, playerToLight);
float r = 1 - playerRad;
d = saturate((d - r) / (1 - r)); // remap range: r~1 -> 0~1
// Attenuation
half distLightToPos = distance(posWS, light.xyz);
half atten = 1 - saturate(distLightToPos / (light.w + epsilon)); // Apply light radius
atten = atten * atten; // Inverse Square Law
// Adjust attenuation and reverse
return 1 - saturate(d * atten);
}
Needs to fetch lighting information such as light position and radius.
Utilizes Unity Physics.OverlapSphere (considered more optimal than any of C# script approach). FakeShadowsManager.cs#L61 Also uses global shader variables to avoid accessing all meshes and materials of the environment. FakeShadowsManager.cs#L85-L87
Fake.Shadows.mp4
Works reasonably well. This shadow is applied in a game (which will be shared soon), and the game can run on 10-year-old mobile devices without serious heat issues
Very low draw call overhead by not using shadow maps.
Increased pixel computation load. In my experience, the impact on performance from the load of a single pixel complexity is low. On contrary, more load is generated from drawing more pixels with alpha-blending as surfaces overlap.
- Basically it's point shadows so this can be improved by utilizing capsule shadow.
- For a simple process, the environment shader uses ShaderGraph, which is limited to modify lighting. So fake shadow is applied to Albedo and AmbientOcclusion which is not accurate way to calculate shadows. To have more accurate lighting, it will need to be applied to lightmap sample result.
- If you have any improvement ideas, please share them in the Issues section.