From dacc4a990912137e1c1743bfaa2f734be3affc24 Mon Sep 17 00:00:00 2001 From: Sergio Valor Date: Wed, 13 May 2026 17:53:32 +0200 Subject: [PATCH] feat(lighting): corba additive separada al config --- data/config/lighting.yaml | 20 +++++++++-- data/maps/p1/r2.yaml | 22 ++++++------ source/core/rendering/lighting.cpp | 54 ++++++++++++++++++++++-------- source/core/rendering/lighting.hpp | 17 ++++++++-- source/game/map/map.cpp | 6 ++-- source/game/map/map.hpp | 2 +- source/utils/defaults.hpp | 13 +++++++ 7 files changed, 100 insertions(+), 34 deletions(-) diff --git a/data/config/lighting.yaml b/data/config/lighting.yaml index 9b8c3dd..77f0193 100644 --- a/data/config/lighting.yaml +++ b/data/config/lighting.yaml @@ -12,8 +12,13 @@ # mid_ratio..1.0 → outer_intensity (anell exterior) # fora del radi → light_level de la sala # -# Les intensitats són factors 0..1 que es multipliquen sobre el píxel -# final del canvas. inner=1.0 vol dir "píxel sense alterar". +# Hi ha dues corbes paral·leles, una per mode, perquè la mateixa escala +# 0..1 vol dir coses molt diferents: +# `gradient` → subtractive: 1.0 = "no fosqueix" el píxel +# (l'emissor "trenca" una sala fosca). +# `gradient_additive` → additive: 1.0 = "afegeix el color sencer" +# (satura). Valors molt més baixos donen un +# halo cromàtic subtil sobre una sala normal. gradient: inner_ratio: 0.5 mid_ratio: 0.85 @@ -21,6 +26,17 @@ gradient: mid_intensity: 0.8 outer_intensity: 0.6 +# Corba del mode additive. Valors petits a propòsit: només volem un +# toc de tint cromàtic prop de l'emissor — fora del nucli, la sala +# original ha de quedar pràcticament intacta. Si pugeu massa l'inner, +# els colors de l'emissor "inunden" la sala (additiu satura ràpid). +gradient_additive: + inner_ratio: 0.5 + mid_ratio: 0.85 + inner_intensity: 0.15 + mid_intensity: 0.10 + outer_intensity: 0.05 + # Modulació del radi quan una entitat declara `flicker: true` al seu # bloc `light:`. El radi efectiu per frame és: # r_eff = r_base + A1·sin(2π·f1·t + φ1) + A2·sin(2π·f2·t + φ2) diff --git a/data/maps/p1/r2.yaml b/data/maps/p1/r2.yaml index 2f5690d..ada2223 100644 --- a/data/maps/p1/r2.yaml +++ b/data/maps/p1/r2.yaml @@ -15,20 +15,20 @@ tileset: data/tilesets/pyramid_1.gif tile_size: 16 origin: [-8, 0] -# Banc de proves del mode `additive` d'il·luminació. La sala queda -# lleugerament fosca (level 0.9) i els emissors hi sumen color per -# damunt — antorxes càlides, jugador en blanc-blau fred, mòmies amb -# halo verd-fantasma. En mode additive el `color` de cada emissor entra -# en joc (en subtractive s'ignora). Veure docs al header de -# `core/rendering/lighting.hpp` per a la fórmula i el comportament al -# solape (suma → tendència al blanc, físicament correcte). +# Banc de proves del mode `additive` d'il·luminació. La sala es veu +# "normal" (level 1.0, el GIF intacte fora del cercle de les torxes) +# i només les antorxes hi sumen un halo càlid suau. Player i mummies +# desactivats explícitament perquè aquí no volem cap halo al voltant +# d'actors — el toc cromàtic és només l'antorxa. La corba additiva +# (intensitats baixes a propòsit perquè additive satura ràpid) viu a +# `gradient_additive:` de data/config/lighting.yaml. lighting: mode: additive - level: 0.9 + level: 1.0 emitters: - torches: { color: [255, 170, 60] } - players: { color: [180, 220, 255] } - mummies: { color: [120, 220, 140] } + torches: { radius: 24, color: [255, 170, 60] } + players: false + mummies: false # Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl # pintable. Un índex per jugador perquè a multijugador local el recorregut diff --git a/source/core/rendering/lighting.cpp b/source/core/rendering/lighting.cpp index c64a15c..1d2fa6c 100644 --- a/source/core/rendering/lighting.cpp +++ b/source/core/rendering/lighting.cpp @@ -26,9 +26,9 @@ namespace Lighting { return buf_; } - // Configuració global, inicialitzada amb els defaults compilats. - // loadConfig() la reemplaça si troba el fitxer. - auto activeConfig() -> GradientConfig& { + // Corba activa del mode subtractive. loadConfig() la reemplaça + // si troba el bloc `gradient:` al YAML. + auto activeSubtractiveConfig() -> GradientConfig& { static GradientConfig cfg_{ .inner_ratio = Defaults::Lighting::INNER_RATIO, .mid_ratio = Defaults::Lighting::MID_RATIO, @@ -39,6 +39,22 @@ namespace Lighting { return cfg_; } + // Corba activa del mode additive. Defaults baixos a propòsit: + // en additive la intensitat és l'alfa del color afegit, així + // que valors petits (0.05..0.15) donen el "halo subtil" que + // sol voler-se. loadConfig() la reemplaça si troba el bloc + // `gradient_additive:` al YAML. + auto activeAdditiveConfig() -> GradientConfig& { + static GradientConfig cfg_{ + .inner_ratio = Defaults::Lighting::INNER_RATIO_ADDITIVE, + .mid_ratio = Defaults::Lighting::MID_RATIO_ADDITIVE, + .inner_intensity = Defaults::Lighting::INNER_INTENSITY_ADDITIVE, + .mid_intensity = Defaults::Lighting::MID_INTENSITY_ADDITIVE, + .outer_intensity = Defaults::Lighting::OUTER_INTENSITY_ADDITIVE, + }; + return cfg_; + } + auto activeFlicker() -> FlickerConfig& { static FlickerConfig fc_{ .wave_amplitude_px = Defaults::Lighting::FLICKER_WAVE_AMPLITUDE_PX, @@ -232,9 +248,22 @@ namespace Lighting { } // namespace auto config() -> const GradientConfig& { - return activeConfig(); + return activeSubtractiveConfig(); } + namespace { + // Llegeix els 5 camps de `GradientConfig` d'un node YAML — qualsevol + // camp absent conserva el valor compilat dels defaults. Compartit + // entre els blocs `gradient:` i `gradient_additive:`. + void readGradient(const fkyaml::node& g, GradientConfig& cfg) { + readField(g, "inner_ratio", cfg.inner_ratio); + readField(g, "mid_ratio", cfg.mid_ratio); + readField(g, "inner_intensity", cfg.inner_intensity); + readField(g, "mid_intensity", cfg.mid_intensity); + readField(g, "outer_intensity", cfg.outer_intensity); + } + } // namespace + void loadConfig(const std::string& yaml_path) { auto bytes = Resource::Helper::loadFile(yaml_path); if (bytes.empty()) { @@ -246,13 +275,10 @@ namespace Lighting { const auto* end = begin + bytes.size(); auto yaml = fkyaml::node::deserialize(begin, end); if (yaml.contains("gradient")) { - const auto& g = yaml["gradient"]; - auto& cfg = activeConfig(); - readField(g, "inner_ratio", cfg.inner_ratio); - readField(g, "mid_ratio", cfg.mid_ratio); - readField(g, "inner_intensity", cfg.inner_intensity); - readField(g, "mid_intensity", cfg.mid_intensity); - readField(g, "outer_intensity", cfg.outer_intensity); + readGradient(yaml["gradient"], activeSubtractiveConfig()); + } + if (yaml.contains("gradient_additive")) { + readGradient(yaml["gradient_additive"], activeAdditiveConfig()); } if (yaml.contains("flicker")) { const auto& f = yaml["flicker"]; @@ -285,7 +311,7 @@ namespace Lighting { if (lum.size() != TOTAL) { lum.resize(TOTAL); } std::ranges::fill(lum, room); - const auto& cfg = activeConfig(); + const auto& cfg = activeSubtractiveConfig(); const auto& fc = activeFlicker(); const float T_S = static_cast(SDL_GetTicks()) / 1000.0F; for (const auto& e : emitters) { @@ -303,7 +329,7 @@ namespace Lighting { // canvas, sense buffer intermig. void applyAdditive(std::uint32_t* canvas, int width, int height, std::uint8_t room, std::span emitters) { darkenCanvas(canvas, width, height, room); - const auto& cfg = activeConfig(); + const auto& cfg = activeAdditiveConfig(); const auto& fc = activeFlicker(); const float T_S = static_cast(SDL_GetTicks()) / 1000.0F; for (const auto& e : emitters) { @@ -327,7 +353,7 @@ namespace Lighting { const std::uint8_t ROOM = toByte(room_light); if (ROOM == 255 && emitters.empty()) { return; } - if (mode == Mode::Additive) { + if (mode == Mode::ADDITIVE) { applyAdditive(canvas, width, height, ROOM, emitters); } else { applySubtractive(canvas, width, height, ROOM, emitters); diff --git a/source/core/rendering/lighting.hpp b/source/core/rendering/lighting.hpp index 57b092e..c1e16aa 100644 --- a/source/core/rendering/lighting.hpp +++ b/source/core/rendering/lighting.hpp @@ -33,6 +33,14 @@ // inner_ratio..mid_ratio → mid_intensity // mid_ratio..1.0 → outer_intensity // +// La corba viu en dos blocs separats al YAML perquè la mateixa escala +// 0..1 té significats molt diferents per mode: +// `gradient:` → corba del mode subtractive (1.0 = "no fosqueix") +// `gradient_additive:` → corba del mode additive (1.0 = "afegeix +// el color sencer i satura"). Es vol molt més +// baixa per a no inundar la sala de color. +// Cada mode llegeix la seva pròpia corba — no es comparteixen valors. +// // 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). @@ -45,8 +53,8 @@ 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, + SUBTRACTIVE, + ADDITIVE, }; // Color emès. Default blanc (sense modificació quan s'usa en mode @@ -104,7 +112,10 @@ namespace Lighting { // simplement reemplaça la configuració activa. void loadConfig(const std::string& yaml_path); - // Configuració activa (per a debug/inspectors). Inicialitzada amb els + // Configuració activa (per a debug/inspectors). Retorna sempre la + // corba subtractive — la corba additive viu en paral·lel al cpp i, + // si algun dia es necessita inspeccionar des de fora, caldrà afegir + // una variant `configAdditive()`. Inicialitzada amb els // Defaults::Lighting fins que loadConfig() es crida. [[nodiscard]] auto config() -> const GradientConfig&; diff --git a/source/game/map/map.cpp b/source/game/map/map.cpp index 66974dd..281fcb2 100644 --- a/source/game/map/map.cpp +++ b/source/game/map/map.cpp @@ -73,7 +73,7 @@ namespace Map { // d'una sala anterior. void loadLightingBlock(const fkyaml::node& yaml) { g_room_emitters.clear(); - lighting_mode = Lighting::Mode::Subtractive; + lighting_mode = Lighting::Mode::SUBTRACTIVE; if (!yaml.contains("lighting")) { return; } const auto& lighting = yaml["lighting"]; if (lighting.contains("level")) { @@ -82,9 +82,9 @@ namespace Map { if (lighting.contains("mode")) { const auto MODE_STR = lighting["mode"].get_value(); if (MODE_STR == "additive") { - lighting_mode = Lighting::Mode::Additive; + lighting_mode = Lighting::Mode::ADDITIVE; } else if (MODE_STR == "subtractive") { - lighting_mode = Lighting::Mode::Subtractive; + lighting_mode = Lighting::Mode::SUBTRACTIVE; } else { Log::warn("Map", "lighting.mode '{}' desconegut — s'usa subtractive", MODE_STR); } diff --git a/source/game/map/map.hpp b/source/game/map/map.hpp index 68c1b0c..39c5732 100644 --- a/source/game/map/map.hpp +++ b/source/game/map/map.hpp @@ -69,7 +69,7 @@ namespace Map { // Veure el comentari de `core/rendering/lighting.hpp` per a la // semàntica de cada mode. En subtractive, els colors dels emissors // s'ignoren — el camí és el de sempre. - inline Lighting::Mode lighting_mode = Lighting::Mode::Subtractive; + inline Lighting::Mode lighting_mode = Lighting::Mode::SUBTRACTIVE; // Override d'emissor declarat al bloc opcional `lighting.emitters.` // de la sala. Tres formes acceptades al YAML: diff --git a/source/utils/defaults.hpp b/source/utils/defaults.hpp index 7f11e7f..8081191 100644 --- a/source/utils/defaults.hpp +++ b/source/utils/defaults.hpp @@ -111,11 +111,24 @@ namespace Defaults { // fitxer falta o li manca alguna clau. La forma final (3 anells) es // descriu al YAML; veure `Lighting::GradientConfig` per a la semàntica. namespace Lighting { + // Defaults del gradient subtractive (mode històric). Les + // intensitats són multiplicadors 0..1 que es multipliquen sobre + // el píxel: 1.0 = no toca, 0.0 = negre. constexpr float INNER_RATIO = 0.8F; constexpr float MID_RATIO = 0.9F; constexpr float INNER_INTENSITY = 1.0F; constexpr float MID_INTENSITY = 0.9F; constexpr float OUTER_INTENSITY = 0.8F; + // Defaults del gradient additive. Les intensitats són "alfa" + // del color afegit (0..1): 1.0 = afegeix el color sencer i + // satura, 0.0 = no afegeix res. Valors baixos a propòsit perquè + // l'additive es nota fort molt fàcilment — només un toc de tint + // cromàtic al voltant de l'emissor. + constexpr float INNER_RATIO_ADDITIVE = 0.5F; + constexpr float MID_RATIO_ADDITIVE = 0.85F; + constexpr float INNER_INTENSITY_ADDITIVE = 0.15F; + constexpr float MID_INTENSITY_ADDITIVE = 0.10F; + constexpr float OUTER_INTENSITY_ADDITIVE = 0.05F; // Nivell d'il·luminació de sala per defecte. Una sala sense la // clau `light_level` queda totalment il·luminada (com hui). constexpr float ROOM_LIGHT_DEFAULT = 1.0F;