DEV Community

Cover image for Unreal Shadows: The Hidden Cascade Trap
Ankit
Ankit

Posted on • Edited on

Unreal Shadows: The Hidden Cascade Trap

For someone who has been working with Unreal Engine for just about a year, the learning curve can feel pretty steep. The engine exposes a huge number of properties and console variables, and when you first encounter them, it can feel like opening a control panel with way too many buttons and absolutely no labels.

In this blog, I'll talk about a fairly simple concept (although it felt quite complex to me when I first ran into it) and try to explain it in simpler terms. Think of this as me documenting the things I painfully figured out so that future-me doesn't have to go down the same rabbit hole again. And if this ends up helping someone else who is also staring at the same mysterious settings panel… well, that's a nice bonus.

The Problem

We have a Directional Light set up in a large level that is responsible for casting shadows on a few selected actors. Interestingly, when these shadows were cast on a vehicle, they appeared sharp at close range but started getting noticeably blurrier just around 2–3 meters away from the camera.

Naturally, the expectation was for the shadow to remain crisp and seamless, without this abrupt drop in quality.

problem description

Is the Quick Fix the Best Fix?

After a quick look into the famous BaseScalability.ini (which sits in the Config folder of the Engine), I found a scalability group setting for shadows. Scalability Groups are divided into Low, Medium, High, Epic, and Cinematic — corresponding to identifiers 0, 1, 2, 3, and Cine in the ini file.

Since I was debugging this on a high-end device, I looked at the CVars under [ShadowQuality@2] and tweaked r.Shadow.DistanceScale to get a seamless, crisp shadow across the vehicle.

But I wasn't in the mood to just make a quick change and walk away from the problem — especially since I'm usually the one breaking my head trying to squeeze out a few extra milliseconds of frame time so our devices don't start doubling as hand warmers while running the game.

So instead, I decided to dig a little deeper and try to understand what these shadow-related settings actually mean. I'm definitely not going to cover all of them here (mostly because I don't know all of them), but I'll walk through the ones I managed to study and experiment with.

Shadow Settings

r.Shadow.CSM.MaxCascades

Cascades are essentially distance ranges — the camera view is split into these ranges. Each cascade gets its own shadow map.

More cascades generally improve shadow quality at farther distances, but they also increase GPU cost.

r.Shadow.MaxResolution

Sets the maximum resolution allowed for dynamic shadow maps generated by lights. This directly affects how sharp the shadows appear.

Higher resolution = sharper shadows, but also higher GPU memory usage and rendering cost.

r.Shadow.MaxCSMResolution

Sets the maximum resolution specifically for Cascaded Shadow Maps generated by Directional Lights (for example, the Sun).

This is separate from r.Shadow.MaxResolution and applies only to cascaded shadows.

r.Shadow.RadiusThreshold

Controls when objects stop casting dynamic shadows based on their screen size. If an object appears smaller than this threshold on screen, it will not cast a shadow.

This helps reduce shadow rendering cost for tiny distant objects. Increasing this value can yield some performance gains, at the cost of small objects no longer casting shadows.

Example: If the value is 0.04, any object occupying less than 4% of the screen height will not cast a shadow.

r.Shadow.DistanceScale

Scales the maximum distance from the camera where dynamic shadows are rendered.

A lower value reduces the shadow rendering distance, while a higher value extends shadows further into the scene.

This directly impacts how far the shadowed region extends and proportionally affects GPU cost as well.

So yes — the quick "just increase the quality" fix wasn't really the best solution after all, was it?

Stop Looking at the Shadow — Maybe Check the Light?

Now, let's look at a few properties of the Directional Light in our level.

DirectionalLight Properties

The Cascaded Shadow Maps section of the Directional Light has the following:

DynamicShadowDistanceStationaryLight

How far from the camera dynamic shadows from the directional light are rendered. Within this distance, shadows are calculated in real time using CSMs. Beyond this distance, shadows switch to baked/static shadows (or disappear if none exist).

DynamicShadowCascades

How many cascades are used for the directional light's CSM shadows. Instead of one large shadow map, the camera view is divided into multiple distance slices, and each cascade gets its own shadow map.

CascadeDistributionExponent

Controls how the cascades are distributed across the shadow distance. The range is from 1.0 to 4.0.

This is the interesting one — it took me a while to wrap my head around the concept:

Low Exponent (1.0): Cascades are spread more evenly across the distance.

High Exponent (4.0): More cascades are concentrated close to the camera, with lower detail farther away.

Wrapping Up

Consider the following Directional Light setup:

  • Dynamic Shadow Distance = 20,000 units
  • Num Cascades = 4
  • Distribution Exponent = 3.0

The cascade boundaries are computed using this formula:

Boundary(i) = DynamicShadowDistance × (i / NumCascades) ^ DistributionExponent
Enter fullscreen mode Exit fullscreen mode

With Exponent = 3.0, the cascade boundaries work out to:

Cascade Formula Boundary (Unreal Units) Boundary (Meters)
1 20000 × (1/4)³ = 20000 × 0.0156 312.5 3.12 m
2 20000 × (2/4)³ = 20000 × 0.125 2,500.0 25.00 m
3 20000 × (3/4)³ = 20000 × 0.4219 8,437.5 84.38 m
4 20000 × (4/4)³ = 20000 × 1.0 20,000.0 200.00 m

And this would explain why at a distance of roughly 3 meters (312.5 Unreal units) from the camera, we saw a drop in shadow quality — that's exactly where the first cascade ends and the next one begins.

If we change the Distribution Exponent to 2.0, the cascades shift like this:

Cascade Formula Boundary (Unreal Units) Boundary (Meters)
1 20000 × (1/4)² = 20000 × 0.0625 1,250.0 12.50 m
2 20000 × (2/4)² = 20000 × 0.25 5,000.0 50.00 m
3 20000 × (3/4)² = 20000 × 0.5625 11,250.0 112.50 m
4 20000 × (4/4)² = 20000 × 1.0 20,000.0 200.00 m

This means the quality drop is pushed out to 12.5 meters instead of 3.12 meters — roughly 4× farther from the camera.

Trade-off?

Of course there is a trade-off!

The max CSM resolution of 2048 was originally being used for a relatively small coverage distance of 3.12 meters. After changing the cascade distribution, the same resolution now covers a much larger range of 12.5 meters.

What this means in practice is that the shadow texel density (texels per world unit) decreases. In simpler terms, the same number of texels now has to cover a larger area of the world, which naturally makes the shadow appear softer.

Texel density calculation

trade-off

Important Insight

This does not mean the entire scene suddenly becomes 4× blurrier.

In reality, only Cascade 1 changes dramatically because its coverage area increases significantly. Meanwhile, Cascades 2–4 actually get sharper when using a distribution exponent of 2, since more shadow resolution is now distributed across a wider near-field range, and the far cascades cover proportionally less area than before.


That's pretty much the rabbit hole I went down while trying to understand why a shadow decided to become blurry a few meters away from the camera.

The takeaway for me was simple: many of these Unreal settings don't exist in isolation. Changing one value often shifts the balance somewhere else — sometimes improving one cascade while quietly hurting another. Understanding that trade-off is far more useful than blindly pushing everything to higher values.

More importantly, the next time I see a shadow doing something strange, at least I'll know where to start looking.

And hopefully, future-me will thank present-me for writing this down.

Top comments (0)