Panic filter
One number from 0 to 1 makes a calm night at sea feel wrong.
It tightens the vignette, drains the colour to teal, splits RGB at the edges and makes the edges ripple.
Inspired by DREDGE's panic effect.
The problem
Fear needs a readable signal on screen. Fading in one effect is easy, but four separate effects with four timers drift apart and never feel like one state.
How it works
Gameplay writes a single panic value, 0 to 1. Each frame, one function maps it to every layer:
- Vignette: darkness = 0.62 × panic, offset = 0.35 + 0.4 × panic. The shader is color *= smoothstep(0.8, offset × 0.799, d × (darkness + offset)). Raising both pulls the dark ring in from the corners.
- Teal tint: mix the colour toward its own luminance × (0.55, 1.0, 0.92) by 0.75 × panic, then darken by up to 30%.
- Chromatic aberration: red and blue sampled at uv ± offset, offset = (0.008, 0.004) × panic, scaled with distance from the centre so the middle stays sharp.
- Edge ripple + noise: the UV is pushed along the radius by sin(r × 34 − t × 1.6) × 0.011 × panic, masked to the outer ring with smoothstep(0.25, 0.75, r). Film noise is screen-blended at 0.32 × panic.
At panic 0 every layer is an exact no-op, so the clean frame stays untouched.
What I hit
My first version kept a small base vignette for mood, so the OFF half wasn't clean. Now darkness starts at 0: with offset 0.35, d × 0.35 never reaches the 0.28 edge inside the frame, so the vignette multiplies by exactly 1.
The ripple first shared the tint's uniform, so the layer-by-layer shot couldn't switch them on one at a time. It got its own.
A night scene that is too dark leaves the filter nothing to darken. I raised the moonlight, sky light and fog brightness until the clean frame read well on its own.
My build
three.js 0.170 and pmndrs/postprocessing 6.39.5. The scene look runs first, then three effect passes: my PanicEffect (tint + ripple), ChromaticAberrationEffect with radial modulation, and VignetteEffect + NoiseEffect. Vignette, chroma and noise are the library's effects unchanged. Only the tint, the ripple and the panic mapping are my own shader code.
Boat, lantern, rocks, sea and moon are procedural low-poly. Panic rises 0.3 → 1.0 in the split, holds at 1 for the breakdown, then falls back to 0 when the lantern is turned up.
Trade-offs
- One float drives everything, so design and code talk about the same thing
- Every layer is a full-screen pass of simple maths, no extra scene renders
- Easy to tune per layer without touching gameplay
- Strong chroma and ripple hurt readability; cap them or add an accessibility slider
- Purely screen-space: nothing in the world actually changes
- Ripple and noise near the edges can fight with UI placed there
When to use it
Fear, sanity, low health, poison, being spotted: any state the player should feel on the screen before they read it on a meter.
Source and credits
Repo: github.com/pmndrs/postprocessing (Zlib license), branch main, commit 9cf03cf (npm 6.39.5)
Code: VignetteEffect, ChromaticAberrationEffect (radial modulation) and NoiseEffect are used unchanged from the library, with EffectComposer and EffectPass. The panic mapping, the teal tint and the edge ripple are my own shader code. Scene, boat, sea and lighting are my own procedural build.
Inspired by DREDGE's panic effect (Black Salt Games). No DREDGE assets, UI, colours or music are used or imitated.