feat(lighting): mode additive amb color per emissor

This commit is contained in:
JailDesigner committed 2026-05-13 14:11:51 +02:00
1 parent 24609d6c5c
commit 771ff1f6cd
10 files changed
+352 -71

No files matched your search

+59 -18
View File
@@ -7,36 +7,73 @@
// Sistema d'il·luminació CPU. Treballa sobre el mateix canvas ARGB del
// joc (320×240) que tot el pipeline de render: després que GameScene
// haja pintat tilemap, paint layer, sombres i actors, una pasada final
// multiplica cada píxel del canvas per un factor de luminància 0..1
// derivat de la combinació de "llum de la sala" + cercles emissors al
// voltant de certes entitats.
// modifica cada píxel del canvas en funció de la "llum de la sala" i
// dels cercles emissors al voltant de certes entitats.
//
// Forma de cada emissor (radial, 3 anells configurables al fitxer
// data/config/lighting.yaml; veure `GradientConfig`):
// Dos modes seleccionables per la sala (`lighting.mode` al YAML):
//
// SUBTRACTIVE (mode històric, default si no es declara mode):
// out = src × max(room_light, max_emitter_intensity_at_pixel)
// Els emissors "trenquen" una capa fosca multiplicativa. Tot el
// trip cromàtic dels emissors s'ignora — només la intensitat
// compta. Pensat per a sales fosques (cripta, pirámide).
//
// ADDITIVE (mode nou):
// out = saturate( (src × room_light) + Σ (emitter_color × intensity) )
// El canvas s'enfosqueix opcionalment per `room_light` (atenuació
// ambient) i els emissors hi sumen color cap a sobre. Les intensitats
// dels anells es llegeixen com a "alfa" del color afegit (0..1).
// Pensat per a sales clares on les antorxes aporten halo cromàtic
// sense restar contrast — o per a sales lleument fosques (0.85, p.ex.)
// on el halo de l'antorxa es veu càlid en lloc de "blanquinós".
//
// Forma del cercle de cada emissor (radial, 3 anells configurables al
// fitxer data/config/lighting.yaml; veure `GradientConfig`):
// 0..inner_ratio del radi → inner_intensity
// inner_ratio..mid_ratio → mid_intensity
// mid_ratio..1.0 → outer_intensity
// fora del radi → light_level de la sala
//
// Combinació entre emissors: per a cada píxel es pren el màxim del
// llum de sala i de la intensitat aportada per cada emissor que el
// solapa — no se "sumen" intensitats. Coherent amb la idea de "centre
// 1.0": el píxel que cau dins de qualsevol nucli no s'enfosqueix.
// Combinació entre emissors:
// SUBTRACTIVE → per a cada píxel es pren el màxim de la intensitat
// aportada per cada emissor que el solapa (no se sumen intensitats).
// ADDITIVE → per a cada píxel se sumen totes les contribucions
// (`color × intensity`), saturant a 255 per canal. Dues llums de
// colors diferents que solapen produeixen la suma natural (orange +
// blue → near-white) — comportament físic.
namespace Lighting {
// Mode de composició. La sala el selecciona via `lighting.mode:
// additive | subtractive` (default subtractive si manca, retrocompat).
enum class Mode : std::uint8_t {
Subtractive,
Additive,
};
// Color emès. Default blanc (sense modificació quan s'usa en mode
// additive). En mode subtractive s'ignora — la pasada multiplicativa
// només té un canal (intensitat). El YAML accepta `color: [r, g, b]`
// a qualsevol nivell de la cadena de resolució (tipus, sala, spawn).
struct Color {
std::uint8_t r{255};
std::uint8_t g{255};
std::uint8_t b{255};
};
// Posició al canvas (en píxels, mateixa unitat que la resta del
// motor) i radi efectiu de l'emissor. El centre és el del sprite
// visible: és l'entitat qui sap on cau (veure `Entity::getLightCenter`).
// `flicker` activa la modulació del radi per frame; `seed` és un
// identificador estable per emissor (no canvia amb la posició) del
// qual es deriva la fase del flicker — així els emissors mòbils
// mantenen un patró propi que no "deriva" pel moviment.
// mantenen un patró propi que no "deriva" pel moviment. `color`
// només té efecte en mode additive.
struct Emitter {
float cx{0.0F};
float cy{0.0F};
int radius{0};
int seed{0};
bool flicker{false};
Color color{};
};
// Forma del gradient. Compartida per tots els emissors per evitar
@@ -71,12 +108,16 @@ namespace Lighting {
// Defaults::Lighting fins que loadConfig() es crida.
[[nodiscard]] auto config() -> const GradientConfig&;
// Aplica la pasada d'il·luminació al canvas in-place. `room_light`
// és l'atenuació base de la sala (0..1, 1 = sense atenuació). El
// span d'emissors pot ser buit — en aquest cas tota la sala queda
// multiplicada per `room_light`. Aquesta funció no fa cap allocació
// si es crida amb la mateixa mida de canvas múltiples cops: manté
// un buffer intern reutilitzable.
void apply(std::uint32_t* canvas, int width, int height, float room_light, std::span<const Emitter> emitters);
// Aplica la pasada d'il·luminació al canvas in-place. `mode`
// selecciona la composició (vegeu el comentari de la capçalera).
// `room_light` és l'atenuació ambient de la sala (0..1, 1 = sense
// atenuació): en subtractive és el "terra" de la intensitat fora
// dels emissors; en additive és la multiplicació prèvia del canvas
// abans de sumar els emissors (es pot deixar a 1.0 per "només tint",
// o baixar per combinar oscuriment ambient + halo cromàtic). El
// span d'emissors pot ser buit. La funció no fa cap allocació si
// es crida amb la mateixa mida de canvas múltiples cops: manté un
// buffer intern reutilitzable (només l'usa el mode subtractive).
void apply(std::uint32_t* canvas, int width, int height, Mode mode, float room_light, std::span<const Emitter> emitters);
} // namespace Lighting