kojdj0811 / unity-fake-character-shadows

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fake Character Shadows

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

Problem of Shadow Mapping

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.

Alternative Approach


(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

Implementation

Shader

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);
}

Breakdown

  1. First dot playerToLight and PositionToPlayer.

  2. Adjust it with player radius.

  3. Calculate attenuation using distance.

  4. dot * attenuation

  5. One minus the result

Runtime Script

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

Results

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

Advantages

Very low draw call overhead by not using shadow maps.

Disadvantages

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.

Improvement Ideas

  • 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.

About

License:MIT License


Languages

Language:C# 66.9%Language:ShaderLab 22.0%Language:HLSL 11.2%