The DarknessFinished the darkness. There's a lamp the player has to carry through levels, and this lamp provides light to buffet the darkness. Taking steps and making moves takes time, and the oil in the lamp burns out slowly reducing its radius of effect. Collecting more oil expands the lamp's light radius, granting more time to solve the level. For now I'm just showing the lamp's effect on the player by attaching the lamp's light component to the player.
When the light radius is full, only a single circle of light is applied. By using the stencil buffer it's quite easy to implement this kind of effect. Clear the stencil buffer to 1's, then render in circles over the lights while setting the stencil operations to set all rendered portions to 0. Afterwards, render the darkness over the screen by a full-screen quad with a darkened color.
The light radius can go from 0-1, 0 meaning completely dark, and 1 meaning the maximum radius. This 0-1 value is then cut into different sections with a very useful function I called
remap.
float remap(float t, float lo, float hi) { return (hi - lo) != 0 ? (t - lo) / (hi - lo) : 0; }
This function takes an interpolant and scales it to a new range. For example, if we want all values from 0.5 to 0.6 to be rescaled to a range of 0-1, it would look like so.
t = remap(t, 0.5f, 0.6f);
Usually the input value can be outside the given remap range, and so I do also quite often use remap in conjunction with a clamp. For example, remapping t from 0.5f 0.6f to 0-1, and clamping within the 0-1 range.
t = clamp01(remap(t, 0.5f, 0.6f));
This remap function is used to map a large range of the light radius to go from bright to fully black. There's a small section near the large end of the radius remapped to fade in the gradient (the inner ring of slightly-less-dark pixels).
Another small section of the radius is remapped to fade in the creepy things at the edge of the light radius.
However, I also had to be careful to also fade out smaller lights that are not coming from the player's lamp itself. These smaller lights, if not handled carefully, would illuminate the creepy things when the player approaches, completely ruining the scary effect.
To solve this I ended up rendering all the creepy things along with the radius of all lights except for the lamp. These are all rendered with the same color. As the creepy things fade in, the other lights in the game fade out with the exact same color. To achieve this effect I utilized the stencil buffer again. First clear the screen to 0's, then render in the creepy things and non-lamp lights to set the buffer to 1. Then render a full-screen quad with the color of the creepy things.
The rest is some hand-tuned RNG to procedureally place all of the geometry for the dark creepy things at the edge of the light. There's some code to generate a crescent triangle mesh, and some code to generate the circle triangle meshes.
And finally a gif the full effect!
Despite being a fairly simple looking effect, designing how multiple different lights interact with subtle behaviors to make everything feel completely seemless takes quite a bit of design work. It would have been trivial to simply use the stencil buffer a couple times to render circles, but also rendering in creepy geometry at the border of *one* light (the player's lamp), but *not* on any other light without any kind of visual artifacts was an interesting challenge!
Oh wow - looks pretty spooky. Especially when those arms/worms reach into the light circle.
Thanks buto