feat(lighting): corba additive separada al config

This commit is contained in:
JailDesigner committed 2026-05-13 17:53:32 +02:00
1 parent 771ff1f6cd
commit dacc4a9909
7 files changed
+100 -34

No files matched your search

+18 -2
View File
@@ -12,8 +12,13 @@
# mid_ratio..1.0 → outer_intensity (anell exterior) # mid_ratio..1.0 → outer_intensity (anell exterior)
# fora del radi → light_level de la sala # fora del radi → light_level de la sala
# #
# Les intensitats són factors 0..1 que es multipliquen sobre el píxel # Hi ha dues corbes paral·leles, una per mode, perquè la mateixa escala
# final del canvas. inner=1.0 vol dir "píxel sense alterar". # 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: gradient:
inner_ratio: 0.5 inner_ratio: 0.5
mid_ratio: 0.85 mid_ratio: 0.85
@@ -21,6 +26,17 @@ gradient:
mid_intensity: 0.8 mid_intensity: 0.8
outer_intensity: 0.6 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 # Modulació del radi quan una entitat declara `flicker: true` al seu
# bloc `light:`. El radi efectiu per frame és: # bloc `light:`. El radi efectiu per frame és:
# r_eff = r_base + A1·sin(2π·f1·t + φ1) + A2·sin(2π·f2·t + φ2) # r_eff = r_base + A1·sin(2π·f1·t + φ1) + A2·sin(2π·f2·t + φ2)
+11 -11
View File
@@ -15,20 +15,20 @@ tileset: data/tilesets/pyramid_1.gif
tile_size: 16 tile_size: 16
origin: [-8, 0] origin: [-8, 0]
# Banc de proves del mode `additive` d'il·luminació. La sala queda # Banc de proves del mode `additive` d'il·luminació. La sala es veu
# lleugerament fosca (level 0.9) i els emissors hi sumen color per # "normal" (level 1.0, el GIF intacte fora del cercle de les torxes)
# damunt — antorxes càlides, jugador en blanc-blau fred, mòmies amb # i només les antorxes hi sumen un halo càlid suau. Player i mummies
# halo verd-fantasma. En mode additive el `color` de cada emissor entra # desactivats explícitament perquè aquí no volem cap halo al voltant
# en joc (en subtractive s'ignora). Veure docs al header de # d'actors — el toc cromàtic és només l'antorxa. La corba additiva
# `core/rendering/lighting.hpp` per a la fórmula i el comportament al # (intensitats baixes a propòsit perquè additive satura ràpid) viu a
# solape (suma → tendència al blanc, físicament correcte). # `gradient_additive:` de data/config/lighting.yaml.
lighting: lighting:
mode: additive mode: additive
level: 0.9 level: 1.0
emitters: emitters:
torches: { color: [255, 170, 60] } torches: { radius: 24, color: [255, 170, 60] }
players: { color: [180, 220, 255] } players: false
mummies: { color: [120, 220, 140] } mummies: false
# Índex de tile amb què cada jugador deixa rastre en trepitjar un sòl # Í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 # pintable. Un índex per jugador perquè a multijugador local el recorregut
+40 -14
View File
@@ -26,9 +26,9 @@ namespace Lighting {
return buf_; return buf_;
} }
// Configuració global, inicialitzada amb els defaults compilats. // Corba activa del mode subtractive. loadConfig() la reemplaça
// loadConfig() la reemplaça si troba el fitxer. // si troba el bloc `gradient:` al YAML.
auto activeConfig() -> GradientConfig& { auto activeSubtractiveConfig() -> GradientConfig& {
static GradientConfig cfg_{ static GradientConfig cfg_{
.inner_ratio = Defaults::Lighting::INNER_RATIO, .inner_ratio = Defaults::Lighting::INNER_RATIO,
.mid_ratio = Defaults::Lighting::MID_RATIO, .mid_ratio = Defaults::Lighting::MID_RATIO,
@@ -39,6 +39,22 @@ namespace Lighting {
return cfg_; 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& { auto activeFlicker() -> FlickerConfig& {
static FlickerConfig fc_{ static FlickerConfig fc_{
.wave_amplitude_px = Defaults::Lighting::FLICKER_WAVE_AMPLITUDE_PX, .wave_amplitude_px = Defaults::Lighting::FLICKER_WAVE_AMPLITUDE_PX,
@@ -232,9 +248,22 @@ namespace Lighting {
} // namespace } // namespace
auto config() -> const GradientConfig& { 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) { void loadConfig(const std::string& yaml_path) {
auto bytes = Resource::Helper::loadFile(yaml_path); auto bytes = Resource::Helper::loadFile(yaml_path);
if (bytes.empty()) { if (bytes.empty()) {
@@ -246,13 +275,10 @@ namespace Lighting {
const auto* end = begin + bytes.size(); const auto* end = begin + bytes.size();
auto yaml = fkyaml::node::deserialize(begin, end); auto yaml = fkyaml::node::deserialize(begin, end);
if (yaml.contains("gradient")) { if (yaml.contains("gradient")) {
const auto& g = yaml["gradient"]; readGradient(yaml["gradient"], activeSubtractiveConfig());
auto& cfg = activeConfig(); }
readField(g, "inner_ratio", cfg.inner_ratio); if (yaml.contains("gradient_additive")) {
readField(g, "mid_ratio", cfg.mid_ratio); readGradient(yaml["gradient_additive"], activeAdditiveConfig());
readField(g, "inner_intensity", cfg.inner_intensity);
readField(g, "mid_intensity", cfg.mid_intensity);
readField(g, "outer_intensity", cfg.outer_intensity);
} }
if (yaml.contains("flicker")) { if (yaml.contains("flicker")) {
const auto& f = yaml["flicker"]; const auto& f = yaml["flicker"];
@@ -285,7 +311,7 @@ namespace Lighting {
if (lum.size() != TOTAL) { lum.resize(TOTAL); } if (lum.size() != TOTAL) { lum.resize(TOTAL); }
std::ranges::fill(lum, room); std::ranges::fill(lum, room);
const auto& cfg = activeConfig(); const auto& cfg = activeSubtractiveConfig();
const auto& fc = activeFlicker(); const auto& fc = activeFlicker();
const float T_S = static_cast<float>(SDL_GetTicks()) / 1000.0F; const float T_S = static_cast<float>(SDL_GetTicks()) / 1000.0F;
for (const auto& e : emitters) { for (const auto& e : emitters) {
@@ -303,7 +329,7 @@ namespace Lighting {
// canvas, sense buffer intermig. // canvas, sense buffer intermig.
void applyAdditive(std::uint32_t* canvas, int width, int height, std::uint8_t room, std::span<const Emitter> emitters) { void applyAdditive(std::uint32_t* canvas, int width, int height, std::uint8_t room, std::span<const Emitter> emitters) {
darkenCanvas(canvas, width, height, room); darkenCanvas(canvas, width, height, room);
const auto& cfg = activeConfig(); const auto& cfg = activeAdditiveConfig();
const auto& fc = activeFlicker(); const auto& fc = activeFlicker();
const float T_S = static_cast<float>(SDL_GetTicks()) / 1000.0F; const float T_S = static_cast<float>(SDL_GetTicks()) / 1000.0F;
for (const auto& e : emitters) { for (const auto& e : emitters) {
@@ -327,7 +353,7 @@ namespace Lighting {
const std::uint8_t ROOM = toByte(room_light); const std::uint8_t ROOM = toByte(room_light);
if (ROOM == 255 && emitters.empty()) { return; } if (ROOM == 255 && emitters.empty()) { return; }
if (mode == Mode::Additive) { if (mode == Mode::ADDITIVE) {
applyAdditive(canvas, width, height, ROOM, emitters); applyAdditive(canvas, width, height, ROOM, emitters);
} else { } else {
applySubtractive(canvas, width, height, ROOM, emitters); applySubtractive(canvas, width, height, ROOM, emitters);
+14 -3
View File
@@ -33,6 +33,14 @@
// inner_ratio..mid_ratio → mid_intensity // inner_ratio..mid_ratio → mid_intensity
// mid_ratio..1.0 → outer_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: // Combinació entre emissors:
// SUBTRACTIVE → per a cada píxel es pren el màxim de la intensitat // 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). // 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: // Mode de composició. La sala el selecciona via `lighting.mode:
// additive | subtractive` (default subtractive si manca, retrocompat). // additive | subtractive` (default subtractive si manca, retrocompat).
enum class Mode : std::uint8_t { enum class Mode : std::uint8_t {
Subtractive, SUBTRACTIVE,
Additive, ADDITIVE,
}; };
// Color emès. Default blanc (sense modificació quan s'usa en mode // Color emès. Default blanc (sense modificació quan s'usa en mode
@@ -104,7 +112,10 @@ namespace Lighting {
// simplement reemplaça la configuració activa. // simplement reemplaça la configuració activa.
void loadConfig(const std::string& yaml_path); 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. // Defaults::Lighting fins que loadConfig() es crida.
[[nodiscard]] auto config() -> const GradientConfig&; [[nodiscard]] auto config() -> const GradientConfig&;
+3 -3
View File
@@ -73,7 +73,7 @@ namespace Map {
// d'una sala anterior. // d'una sala anterior.
void loadLightingBlock(const fkyaml::node& yaml) { void loadLightingBlock(const fkyaml::node& yaml) {
g_room_emitters.clear(); g_room_emitters.clear();
lighting_mode = Lighting::Mode::Subtractive; lighting_mode = Lighting::Mode::SUBTRACTIVE;
if (!yaml.contains("lighting")) { return; } if (!yaml.contains("lighting")) { return; }
const auto& lighting = yaml["lighting"]; const auto& lighting = yaml["lighting"];
if (lighting.contains("level")) { if (lighting.contains("level")) {
@@ -82,9 +82,9 @@ namespace Map {
if (lighting.contains("mode")) { if (lighting.contains("mode")) {
const auto MODE_STR = lighting["mode"].get_value<std::string>(); const auto MODE_STR = lighting["mode"].get_value<std::string>();
if (MODE_STR == "additive") { if (MODE_STR == "additive") {
lighting_mode = Lighting::Mode::Additive; lighting_mode = Lighting::Mode::ADDITIVE;
} else if (MODE_STR == "subtractive") { } else if (MODE_STR == "subtractive") {
lighting_mode = Lighting::Mode::Subtractive; lighting_mode = Lighting::Mode::SUBTRACTIVE;
} else { } else {
Log::warn("Map", "lighting.mode '{}' desconegut — s'usa subtractive", MODE_STR); Log::warn("Map", "lighting.mode '{}' desconegut — s'usa subtractive", MODE_STR);
} }
+1 -1
View File
@@ -69,7 +69,7 @@ namespace Map {
// Veure el comentari de `core/rendering/lighting.hpp` per a la // Veure el comentari de `core/rendering/lighting.hpp` per a la
// semàntica de cada mode. En subtractive, els colors dels emissors // semàntica de cada mode. En subtractive, els colors dels emissors
// s'ignoren — el camí és el de sempre. // 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.<tipus>` // Override d'emissor declarat al bloc opcional `lighting.emitters.<tipus>`
// de la sala. Tres formes acceptades al YAML: // de la sala. Tres formes acceptades al YAML:
+13
View File
@@ -111,11 +111,24 @@ namespace Defaults {
// fitxer falta o li manca alguna clau. La forma final (3 anells) es // fitxer falta o li manca alguna clau. La forma final (3 anells) es
// descriu al YAML; veure `Lighting::GradientConfig` per a la semàntica. // descriu al YAML; veure `Lighting::GradientConfig` per a la semàntica.
namespace Lighting { 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 INNER_RATIO = 0.8F;
constexpr float MID_RATIO = 0.9F; constexpr float MID_RATIO = 0.9F;
constexpr float INNER_INTENSITY = 1.0F; constexpr float INNER_INTENSITY = 1.0F;
constexpr float MID_INTENSITY = 0.9F; constexpr float MID_INTENSITY = 0.9F;
constexpr float OUTER_INTENSITY = 0.8F; 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 // Nivell d'il·luminació de sala per defecte. Una sala sense la
// clau `light_level` queda totalment il·luminada (com hui). // clau `light_level` queda totalment il·luminada (com hui).
constexpr float ROOM_LIGHT_DEFAULT = 1.0F; constexpr float ROOM_LIGHT_DEFAULT = 1.0F;