Noise-driven fire light rig with bounce, core, embers and glow

This commit is contained in:
enne2
2026-10-11 02:26:08 +02:00
parent e15deef84f
commit 70c82df450
11 changed files with 440 additions and 22 deletions
+98
View File
@@ -0,0 +1,98 @@
# Dynamic fire light
`scripts/fire_light.gd` replaces the old single light with sinusoidal flicker. One
noise signal now drives the light rig **and** the flame, so brightness, colour, shadow
sway and flame height pulse together.
## Research
Perplexity answered this time (it had been unavailable in earlier sessions), and the
official documentation was checked in parallel:
- Godot renderer comparison table — Compatibility is the only method available for the
web target. It supports 8 OmniLights per mesh by default, glow, depth/height fog and
SSAO, and it does **not** support volumetric fog, light projector textures, PCSS,
SSIL, SDFGI, decals or CompositorEffects post-processing. Lights **with** shadows are
drawn in extra passes and blended in sRGB, so they differ in appearance and dominate
the cost: https://docs.godotengine.org/en/stable/3d/overview_of_renderers.html
- Faking global illumination — for positional lights, add a second OmniLight3D as a
child of the main one with +25/50% range, shadows off, energy 10-40% and specular 0:
https://docs.godotengine.org/en/stable/tutorials/3d/global_illumination/faking_global_illumination.html
- FastNoiseLite (`get_noise_1d`, Perlin, seed):
https://docs.godotengine.org/en/stable/classes/class_fastnoiselite.html
- Key advice from Perplexity: continuous noise instead of per-frame randomness, fixed
`omni_range`, frame-rate independent smoothing, amplitude 15-30%, colour variation
smaller than brightness, `omni_attenuation` ~2 for a physical falloff, one shadowed
light only, and profile illumination and shadows separately.
- Community: hard on/off flicker makes the lit area visibly "expand" after each switch,
so the light is never toggled. Particles are lit inconsistently by a light placed
inside the same particle system, which is why the flame stays unshaded and luminous on
its own (the light only lights the room).
## Implementation
| Element | Value | Reason |
| --- | --- | --- |
| Main light | energy 1.9-2.8, range **fixed** 10, attenuation 1.6, shadows **on** (cube) | the only shadow caster; animating the range makes the boundary breathe |
| Noise | FastNoiseLite Perlin, seed 20261011, 0.8 Hz (weight 0.75) + 7 Hz (0.25), gain 1.6, response 15 | smooth, deterministic, frame-rate independent, no strobing |
| Colour drift | green 0.36-0.42, blue 0.07-0.12 | deliberately smaller than the brightness swing |
| Sway | ±4 cm at ~1.2 Hz, main light only | makes the shadows move; kept subtle |
| Bounce light | child, range 14 (=+40%), energy 20%, shadows off, specular 0, attenuation 1.5 | documented fake-GI recipe; no indirect lighting exists in Compatibility |
| Core light | range 3.5, energy 45%, shadows off, 45 cm below the main light | hot glow at the flame |
| Embers | 8 unshaded additive particles, 1.8 s | cheapest large realism gain |
| Flame | quad height ±8%, material brightness ±25% | visibly breathes with the light |
| Glow | enabled, intensity 0.45, blend additive, `hdr_threshold` **1.3**, levels 2-4 | stands in for the missing volumetric fog; a high threshold keeps the amber HUD from blooming |
Total: 6 omni lights in the room (2 desk lamps, 3 fire, 1 muzzle flash), only one with
shadows, against the default limit of 8 per mesh.
## Measured cost (desktop Radeon 780M, GL Compatibility, 1280x720, vsync off)
| State | FPS | Frame time |
| --- | --- | --- |
| Fire rig active | 262.6 | 3.8 ms |
| Fire lights hidden | 250.7 | 4.0 ms |
| Shadows off | 588.4 | 1.7 ms |
The three fire lights are inside the measurement noise; the single shadow-casting light
costs about 2.1 ms, i.e. more than half the frame. That matches the documentation and is
the intended trade: the shadow is what makes the fire feel present. On a phone the same
ratio is expected to be heavier, and this workstation cannot measure it. To shed the cost
on weak devices, in this order: `main_light.shadow_enabled = false`, glow off, then
`fire_rig.main_light.visible = false`.
## Pitfalls found while implementing
1. **Dual-paraboloid shadows do not exist in Compatibility.** Setting
`OmniLight3D.SHADOW_DUAL_PARABOLOID` logs `Dual paraboloid shadow mode not supported in
the Compatibility renderer` and the light loses its shadows. Only `SHADOW_CUBE`
works, even though the class reference describes the other mode as faster. Advice
found online ("try dual paraboloid first") is wrong for this renderer.
2. Keep `omni_range` constant; animating it makes the light edge visibly expand.
3. Never switch the light off to flicker: the lit area visibly pops afterwards.
4. Keep fire particles unshaded and additive; the light must only light the room.
5. Raise the glow threshold before trusting it: at the default 1.0 the amber HUD panels
bloom.
6. When taking verification screenshots, stop wave spawning first: a zombie that reached
the camera looks like a rendering bug but is just a silhouette in front of the lens.
## Verification
```sh
"$G" --headless --path . --script res://tools/test_fire_light.gd
"$G" --display-driver x11 --rendering-method gl_compatibility --path . --script res://tools/shot_fire.gd
```
`test_fire_light.gd` simulates 60 s at 60 Hz on the pure noise signal and checks: energy
stays inside 1.9-2.8 (measured 1.96-2.76), deviation above 0.05 (0.135, so the fire is
alive), maximum per-frame step below 0.05 (0.036, so no strobing), the three lights move
together, the range stays fixed, bounce and core have no shadows and no specular, the
bounce is a child at +40% range, only the main light casts shadows, the sway stays inside
±4 cm (measured 3.5 cm) while actually moving, the flame height varies, embers exist,
and two rigs with the same seed produce identical signals. `test_office_atmosphere.gd`
was updated to the new light budget. All eleven suites pass.
Images `/tmp/zombie-fire-{basso,alto,alto-scarto,insieme}.png` compare the dim and bright
phases, the same brightness with a different sway phase, and a wide view: the wall and
ceiling illumination, the floor shadow and the flame halo all change together, and the
HUD stays crisp.