Spotlight Shadow Flickering (+solution)

A potential game breaker, especially for large open-worlds: spotlight shadows start to bug out once we get further away from 0,0,0 . Here you can see what happens at around 5000 units from origin:

SpotlightShadowFlicker

A workaround is to use point lights with a cookie instead (to mimic a spotlight) BUT, if you need shadows, then brace yourselves for a hard landing because point light shadows are very very expensive. In my case each car has a headlight with 150m range and converting those to a point light means calculating shadows in 6 directions instead of 1, for each headlight.

So I began testing and the flickering only happens when the spotlight is rotated on the x and y axes. Transform.position is fine but any rotation and the flickering is there. So I tried other ways to rotate the spotlight, without a clue I tried some matrix4x4 stuff, and hooray! it seems to have fixed the problem:

SpotlightShadowFix

Spotlight shadow fix script - attach to your spotlight:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpotShadowFix : MonoBehaviour
{
    public Light sLight;
    public Matrix4x4 spotlightMatrix;

    void Start()
    {
        sLight = sLight.GetComponent<Light>();
    }
    void Update()
    {
        sLight.useShadowMatrixOverride = true;
        sLight.shadowMatrixOverride = spotlightMatrix;

    }

}

For now, tested successfully in editor and in build. Maybe @chap-unity and/or possibly @bgolus could actually explain why this fixes the issue :smiley:

P.S. This is for built-in, not sure it’s applicable to HDRP

1 Like

What is stored in the spotlightMatrix field? Is it set through Inspector?

@MUGIK To be honest I have no idea. It possibly assigns a blank matrix and that solves it somehow. I just tried moving the code from Update to Start and that works too. In the inspector, nothing is modified, this is how it looks:

As you can see above, the performance difference is quite dramatic;

  • top screen: 3 spotlights, 150 range, 95 angle.
    -bottom screen: 3 point lights with cookie, 150 range.
1 Like

For anyone who is still having issues, this does indeed fix the shadows generated by the spotlight, but not the flickering of the spotlight itself. You may not notice the flickering of the light if the spotlight range is high. For example, in my case, the flickering is noticeable with a range of 50, while with a range of 150 everything seems to be fine, but if the game allows it and you can see clearly into the distance (no fog and blur), then the flickering at the end of the spotlight is visible. It seems that there is a solution, but this requires the latest version (currently Unity 6000.0.30f1). I have only tested it in a URP project, so I don’t know how it works in others. Go to Assets - Settings and select the renderer used (by default it is URP-HighFidelity-Renderer). When you select it, under Rendering settings, change the Rendering Path to Forward+. This will ensure that the spotlight does not flicker, but it will basically provide the best graphical appearance as you move further away from the origin (it will not solve the jitter itself, of course). Deferred mode also seems to solve the spotlight flicker problem, but at greater distances you will experience basic rendering glitches. So Forward+ seems to provide the least graphical glitch at long distances, and it also solves the spotlight issue, but it also requires the SpotShadowFix script (of course you don’t have to think about crazy long distances, but I tested XYZ with 30000 and I didn’t experience any flickering, just jittering). The latest version is needed because in one of the previous versions, Forward+ and spotlight did not work well together (issuetracker: Spot light with high Outer Spot Angle produces artefacts/clipping when using the Forward+ Rendering Path).

I made a demonstration video with the different rendering paths:

1 Like

@Pixi91 Thank you for the confirmation & testing! I haven’t encountered the flickering of the light itself, as can be seen in your video - but I’m on built-in + deferred (tested in 2019.4 and 2021.3). Tried light ranges from 10 to 200 at positions 7000, 70000 and 700000 from origin, all good, and as you pointed out, the geometry jitter kicks in way before any shadow instability.

Correction: there is indeed some light flickering. Exposure has been increased 4x to better discern - as you can see on the steering wheel specular, the spotlight behind is flickering in intensity.

SpotlightFlickerB

This is essentially very similar to when I used Deferred rendering as a test. In my video, after 1:52, you can see 2 flickerings. What are you going to do in the end? Do you plan to revert to point light based lighting? If you decide to do so, I recommend both options. Have both spot light and point light as objects on the cars, but the player can decide at any time which lighting solution (light objects) is active. You can make a switch in the menu that controls the lighting on the cars, and the default would be point light, and the performance would be spotlight. I think some flickering is acceptable for better performance, it is definitely worth it on a weaker GPU, since it seems that you can’t fix this 100% in old Unity, where Forward+ is not available.

Hey, I got an idea. In your last comment you said you had to increase the exposure value for better recognition. Does that mean you didn’t experience flickering with the previous value? If I understand correctly, you had to change the spotlight of the enemy cars so that the player car could be better detected from their perspective, i.e. on their own little AI camera. How about the following solution?

Create two different Spotlights on enemy cars:

  • A normal spotlight with a lower exposure value for the player.
  • A spotlight with a higher exposure value exclusively for the enemy cars own AI camera.

Culling Mask setting:

  • The two spotlights should be placed on separate Layers (for example PlayerLightLayer and AIViewLightLayer).
  • Set the culling mask for the players main camera to only render the PlayerLightLayer.
  • Set the culling mask for the enemy cars AI camera to only render the AIViewLightLayer.