Colored Shadow Penumbra
Source: Hacker News
Colored Shadow Penumbra
Some time ago I saw this effect implemented by Romain Durand on a social media post where the penumbra regions of a light’s shadows would get additional color, known as Colored Penumbra or Colored Shadow Terminator. I really liked the effect and took a stab at it, and now I’m making it available.
If you’re curious, this Medium article by Shahriar Shahrabi goes through some of the theory.
There are different ways to implement it, and I’ve decided to edit the Engine Shaders for this, which (like everything) has pros and cons:
- Easy to integrate; no need to guess which values correspond to light or shadow (as you would with a post‑process solution), which can be painful with Lumen or day/night cycles.
- Works with all light types.
- The implementation is extremely cheap.
- The color saturation is only configurable globally (not per light or per scene).
- It needs wide penumbras for the effect to be visible.
- The implementation works only for Dynamic Lights, not Baked.
We’re only editing the engine shaders, not the engine itself, so we can stick to the Launcher version. I’ve covered the basics in my previous post about Editing the Engine Shaders; make sure you read it to understand what that entails.
Implementation
If you’re using Substrate
Open the file Engine\Shaders\Private\Substrate\SubstrateDeferredLighting.ush (line numbers refer to UE 5.7).
At line 190 find the following code:
float3 SpecularLuminance = BSDFEvaluate.IntegratedSpecularValue * LightData.SpecularScale;
Add the code below right after it:
// Colored shadow penumbra - Start
const float PenumbraSaturation = 4.0f; // configure this to your liking (1.0 means no change)
float3 LuminanceFactors = float3(0.3f, 0.59f, 0.11f); // Luminance factor for desaturation
float3 PenumbraColor = dot(DiffuseLuminance, LuminanceFactors); // Desaturate
PenumbraColor = lerp(PenumbraColor, DiffuseLuminance, PenumbraSaturation); // Apply saturation (inverse desaturation)
DiffuseLuminance = lerp(DiffuseLuminance, PenumbraColor, 1.0f - BSDFShadowTerms.SurfaceShadow); // Blend
// Colored shadow penumbra - End
If you’re not using Substrate
Open the file Engine\Shaders\Private\DeferredLightPixelShaders.usf (line numbers refer to UE 5.7).
At line 397 find the following code:
OutColor += Radiance;
Add the code below right after it:
// Colored shadow penumbra - Start
const float PenumbraSaturation = 4.0f; // configure this to your liking (1.0 means no change)
float3 LuminanceFactors = float3(0.3f, 0.59f, 0.11f); // Luminance factor for desaturation
float3 PenumbraColor = dot(OutColor.xyz, LuminanceFactors); // Desaturate
PenumbraColor = lerp(PenumbraColor, OutColor.xyz, PenumbraSaturation); // Apply saturation (inverse desaturation)
OutColor.xyz = lerp(OutColor.xyz, PenumbraColor, 1.0f - SurfaceShadow); // Blend
// Colored shadow penumbra - End
Save the shader file, return to Unreal, and press Ctrl + Shift + .. Then make some coffee while the shaders recompile.
That’s it—easy as that!
The effect is quite strong in comparison shots, so you’ll probably want to lower the PenumbraSaturation value for a subtler look. Note that this implementation alters the surface color to be more saturated; gray or already fully‑saturated surfaces will not show any effect.
Comments?
If you have any comments or questions, feel free to reply to the relevant post on: