From e8e218744a258a5a4e8943c8ec14c519db9c578e Mon Sep 17 00:00:00 2001 From: EtienneLescot Date: Sat, 26 Sep 2026 23:44:36 +0200 Subject: [PATCH] fix(background): make the gradient motions visible at a glance Measured on a port of the shader, the three motions strayed 3 to 9 % from the still gradient and changed by less than 1/255 per frame, so they read as a still background. Motion is now twice as fast and two to ten times wider, depending on the gradient. Aurora blobs and wave crests also lighten or darken a little: most offered gradients pair two neighbouring hues, and moving their own colours alone stayed invisible on them. Drift slides along its axis on top of swinging it, since a rotation alone leaves the centre still. --- crates/compositor/src/frame_geometry.rs | 6 +-- crates/compositor/src/shaders.hlsl | 55 +++++++++++++-------- crates/compositor/src/shaders.metal | 42 ++++++++++------ crates/compositor/src/vk_shaders/layer.wgsl | 41 +++++++++------ 4 files changed, 88 insertions(+), 56 deletions(-) diff --git a/crates/compositor/src/frame_geometry.rs b/crates/compositor/src/frame_geometry.rs index 2976299bf..3f270805e 100644 --- a/crates/compositor/src/frame_geometry.rs +++ b/crates/compositor/src/frame_geometry.rs @@ -1565,8 +1565,8 @@ fn same_source_path(a: &str, b: &str) -> bool { a.eq_ignore_ascii_case(b) } -/// Période commune des mouvements de fond (s). Chaque période du shader (20, 24, 30, 40, 12, -/// 120 s) la divise, donc replier le temps dessus ne crée aucun raccord visible — et garde au +/// Période commune des mouvements de fond (s). Chaque période du shader (6, 10, 12, 15, 20, +/// 30 s) la divise, donc replier le temps dessus ne crée aucun raccord visible — et garde au /// shader un temps borné, là où un `f32` de plusieurs heures perdrait la finesse du bruit. pub const GRADIENT_MOTION_PERIOD_S: f32 = 120.0; @@ -6722,7 +6722,7 @@ mod tests { assert_eq!(gradient_motion_slots(M::Aurora, 0.0, 1.0).0[1], 2.0); assert_eq!(gradient_motion_slots(M::Waves, 240.0, 1.0).0, [0.0, 3.0]); // Chaque période du shader divise la période de repli : pas de raccord au bouclage. - for p in [20.0, 24.0, 30.0, 40.0, 12.0, 120.0] { + for p in [6.0, 10.0, 12.0, 15.0, 20.0, 30.0] { assert_eq!(GRADIENT_MOTION_PERIOD_S % p, 0.0, "période {p}"); } } diff --git a/crates/compositor/src/shaders.hlsl b/crates/compositor/src/shaders.hlsl index 9c32e2886..eae92d60e 100644 --- a/crates/compositor/src/shaders.hlsl +++ b/crates/compositor/src/shaders.hlsl @@ -349,10 +349,18 @@ float3 ramp4(float t, float4 k0, float4 k1, float4 k2, float4 k3) return c; } +// Éclaircit vers le blanc (k > 0) ou assombrit vers le noir (k < 0) d'une fraction |k|. +float3 lighten(float3 c, float k) +{ + return k > 0.0 ? c + (1.0 - c) * k : c * (1.0 + k); +} + // Mouvements 2 (aurore) et 3 (vagues) du mode 5, sur la rampe de ses stops. // `gp` 0..1 sur le quad, `dir`/`denom` ceux du dégradé, `time` = temps programme replié sur -// 120 s (toutes les périodes ci-dessous le divisent), `aspect` = w/h de la sortie. Périodes -// longues et contraste bas : le fond ne doit jamais prendre l'attention. +// 120 s (toutes les périodes ci-dessous le divisent), `aspect` = w/h de la sortie. Amples et +// rapides assez pour se voir d'un coup d'œil : réglés plus bas, ils ne se voyaient qu'en +// comparant deux images, et la plupart des dégradés proposés n'ont que deux teintes voisines, +// d'où la lumière que chacun ajoute en plus de déplacer la rampe. float3 gradient_motion(float2 gp, float2 dir, float denom, float4 k0, float4 k1, float4 k2, float4 k3, float time, float motion, float aspect) { @@ -360,26 +368,28 @@ float3 gradient_motion(float2 gp, float2 dir, float denom, float4 k0, float4 k1, float u = dot(gp - 0.5, dir) / denom; // position le long de l'axe, -0.5..0.5 if (motion < 2.5) { - // Aurore : rampe perturbée par un bruit lent (dont l'origine tourne en 120 s), puis - // trois nappes gaussiennes sur des Lissajous de 20 à 40 s. Coordonnées corrigées de - // l'aspect pour que les nappes restent rondes. + // Aurore : rampe perturbée par un bruit (dont l'origine tourne en 30 s), puis trois + // larges nappes gaussiennes sur des Lissajous de 10 à 20 s. Deux portent la couleur de + // fin éclaircie, une celle du début assombrie. Coordonnées corrigées de l'aspect pour + // que les nappes restent rondes. float2 p = float2((gp.x - 0.5) * aspect, gp.y - 0.5); - float ph = TAU * time / 120.0; - float n = value_noise(p * 2.5 + 1.5 * float2(cos(ph), sin(ph))); - float3 g = ramp4(saturate(0.5 + u + 0.3 * (n - 0.5)), k0, k1, k2, k3); - float2 b0 = float2(0.35 * aspect * sin(TAU * time / 20.0), 0.25 * sin(TAU * time / 30.0 + 1.0)); - float2 b1 = float2(0.30 * aspect * sin(TAU * time / 24.0 + 2.0), 0.22 * cos(TAU * time / 40.0)); - float2 b2 = float2(0.25 * aspect * cos(TAU * time / 30.0 + 4.0), 0.28 * sin(TAU * time / 24.0 + 3.0)); - g = lerp(g, k3.rgb, 0.45 * exp(-dot(p - b0, p - b0) / 0.08)); - g = lerp(g, k0.rgb, 0.45 * exp(-dot(p - b1, p - b1) / 0.06)); - g = lerp(g, k3.rgb, 0.35 * exp(-dot(p - b2, p - b2) / 0.05)); + float ph = TAU * time / 30.0; + float n = value_noise(p * 1.8 + 1.5 * float2(cos(ph), sin(ph))); + float3 g = ramp4(saturate(0.5 + u + 0.6 * (n - 0.5)), k0, k1, k2, k3); + float2 b0 = float2(0.35 * aspect * sin(TAU * time / 10.0), 0.25 * sin(TAU * time / 15.0 + 1.0)); + float2 b1 = float2(0.30 * aspect * sin(TAU * time / 12.0 + 2.0), 0.22 * cos(TAU * time / 20.0)); + float2 b2 = float2(0.25 * aspect * cos(TAU * time / 15.0 + 4.0), 0.28 * sin(TAU * time / 12.0 + 3.0)); + float3 light = lighten(k3.rgb, 0.15); + g = lerp(g, light, 0.7 * exp(-dot(p - b0, p - b0) / 0.176)); + g = lerp(g, lighten(k0.rgb, -0.15), 0.7 * exp(-dot(p - b1, p - b1) / 0.132)); + g = lerp(g, light, 0.6 * exp(-dot(p - b2, p - b2) / 0.11)); return g; } - // Vagues : trois bandes sinus perpendiculaires à l'axe, qui avancent d'une bande en 12 s, - // légèrement ondulées le long des bandes (20 s). Elles décalent la rampe, rien d'autre. + // Vagues : trois bandes sinus perpendiculaires à l'axe, qui avancent d'une bande en 6 s, + // ondulées le long des bandes (10 s). Elles décalent la rampe et éclairent leurs crêtes. float v = dot(gp - 0.5, float2(-dir.y, dir.x)) / denom; - float w = sin(TAU * (3.0 * u + 0.04 * sin(TAU * (1.5 * v + time / 20.0)) - time / 12.0)); - return ramp4(saturate(0.5 + u + 0.07 * w), k0, k1, k2, k3); + float w = sin(TAU * (3.0 * u + 0.04 * sin(TAU * (1.5 * v + time / 10.0)) - time / 6.0)); + return lighten(ramp4(saturate(0.5 + u + 0.18 * w), k0, k1, k2, k3), 0.07 * w); } // Couverture d'une pastille (disque) adoucie sur ~1.5 px, pour la barre de titre du mode 14. @@ -2493,13 +2503,16 @@ float4 ps_main(VSOut i) : SV_Target if (mode > 4.5) { float2 dir = fx.xy; + float slide = 0.0; if (fx.w > 0.5 && fx.w < 1.5) { - // Dérive : l'axe respire de ±15° (0.2617994 rad) en 20 s. - float da = 0.2617994 * sin(6.2831853 * fx.z / 20.0); + // Dérive : l'axe balance de ±30° (0.5235988 rad) en 20 s, et le dégradé glisse le + // long de lui de ±20 % en 15 s. Une rotation seule laisse le centre immobile. + float da = 0.5235988 * sin(6.2831853 * fx.z / 20.0); float sa = sin(da); float ca = cos(da); dir = float2(dir.x * ca - dir.y * sa, dir.x * sa + dir.y * ca); + slide = 0.2 * sin(6.2831853 * fx.z / 15.0); } float denom = max(abs(dir.x) + abs(dir.y), 1e-4); // Paramétré sur le QUAD dès qu'il en a un (la bulle webcam), sinon sur la sortie. Pour le @@ -2507,7 +2520,7 @@ float4 ps_main(VSOut i) : SV_Target // que la tranche du dégradé plein cadre qui passe dessous, jamais la rampe complète que // le sélecteur affiche. float2 gp = (quad_px.x > 0.0 && quad_px.y > 0.0) ? (i.local / quad_px) : i.pout; - float t = saturate(0.5 + dot(gp - 0.5, dir) / denom); + float t = saturate(0.5 + dot(gp - 0.5, dir) / denom + slide); float3 g = ramp4(t, color, src_prev, dst_prev, src); if (fx.w > 1.5) { diff --git a/crates/compositor/src/shaders.metal b/crates/compositor/src/shaders.metal index 07d86bd51..907eb91b5 100644 --- a/crates/compositor/src/shaders.metal +++ b/crates/compositor/src/shaders.metal @@ -463,6 +463,12 @@ inline float3 ramp4(float t, float4 k0, float4 k1, float4 k2, float4 k3) return c; } +// Éclaircit vers le blanc (k > 0) ou assombrit vers le noir (k < 0) d'une fraction |k|. +inline float3 lighten(float3 c, float k) +{ + return k > 0.0 ? c + (1.0 - c) * k : c * (1.0 + k); +} + // Mouvements 2 (aurore) et 3 (vagues) du mode 5. Miroir ligne pour ligne de // `gradient_motion` côté HLSL (commentaires complets là-bas). inline float3 gradient_motion(float2 gp, float2 dir, float denom, float4 k0, float4 k1, @@ -472,23 +478,24 @@ inline float3 gradient_motion(float2 gp, float2 dir, float denom, float4 k0, flo float u = dot(gp - 0.5, dir) / denom; // position le long de l'axe, -0.5..0.5 if (motion < 2.5) { - // Aurore : rampe perturbée par un bruit lent, puis trois nappes gaussiennes. + // Aurore : rampe perturbée par un bruit, puis trois larges nappes gaussiennes. float2 p = float2((gp.x - 0.5) * aspect, gp.y - 0.5); - float ph = TAU * time / 120.0; - float n = value_noise(p * 2.5 + 1.5 * float2(cos(ph), sin(ph))); - float3 g = ramp4(clamp(0.5 + u + 0.3 * (n - 0.5), 0.0, 1.0), k0, k1, k2, k3); - float2 b0 = float2(0.35 * aspect * sin(TAU * time / 20.0), 0.25 * sin(TAU * time / 30.0 + 1.0)); - float2 b1 = float2(0.30 * aspect * sin(TAU * time / 24.0 + 2.0), 0.22 * cos(TAU * time / 40.0)); - float2 b2 = float2(0.25 * aspect * cos(TAU * time / 30.0 + 4.0), 0.28 * sin(TAU * time / 24.0 + 3.0)); - g = mix(g, k3.rgb, 0.45 * exp(-dot(p - b0, p - b0) / 0.08)); - g = mix(g, k0.rgb, 0.45 * exp(-dot(p - b1, p - b1) / 0.06)); - g = mix(g, k3.rgb, 0.35 * exp(-dot(p - b2, p - b2) / 0.05)); + float ph = TAU * time / 30.0; + float n = value_noise(p * 1.8 + 1.5 * float2(cos(ph), sin(ph))); + float3 g = ramp4(clamp(0.5 + u + 0.6 * (n - 0.5), 0.0, 1.0), k0, k1, k2, k3); + float2 b0 = float2(0.35 * aspect * sin(TAU * time / 10.0), 0.25 * sin(TAU * time / 15.0 + 1.0)); + float2 b1 = float2(0.30 * aspect * sin(TAU * time / 12.0 + 2.0), 0.22 * cos(TAU * time / 20.0)); + float2 b2 = float2(0.25 * aspect * cos(TAU * time / 15.0 + 4.0), 0.28 * sin(TAU * time / 12.0 + 3.0)); + float3 light = lighten(k3.rgb, 0.15); + g = mix(g, light, 0.7 * exp(-dot(p - b0, p - b0) / 0.176)); + g = mix(g, lighten(k0.rgb, -0.15), 0.7 * exp(-dot(p - b1, p - b1) / 0.132)); + g = mix(g, light, 0.6 * exp(-dot(p - b2, p - b2) / 0.11)); return g; } - // Vagues : trois bandes sinus perpendiculaires à l'axe (12 s), ondulées (20 s). + // Vagues : trois bandes sinus perpendiculaires à l'axe (6 s), ondulées (10 s), crêtes éclairées. float v = dot(gp - 0.5, float2(-dir.y, dir.x)) / denom; - float w = sin(TAU * (3.0 * u + 0.04 * sin(TAU * (1.5 * v + time / 20.0)) - time / 12.0)); - return ramp4(clamp(0.5 + u + 0.07 * w, 0.0, 1.0), k0, k1, k2, k3); + float w = sin(TAU * (3.0 * u + 0.04 * sin(TAU * (1.5 * v + time / 10.0)) - time / 6.0)); + return lighten(ramp4(clamp(0.5 + u + 0.18 * w, 0.0, 1.0), k0, k1, k2, k3), 0.07 * w); } // ============ Curseur MODÉLISÉ (mode 15) ============ @@ -2274,13 +2281,16 @@ fragment float4 ps_main(VSOut i [[stage_in]], if (layer.mode > 4.5 && layer.mode < 5.5) { float2 dir = layer.fx.xy; + float slide = 0.0; if (layer.fx.w > 0.5 && layer.fx.w < 1.5) { - // Dérive : l'axe respire de ±15° (0.2617994 rad) en 20 s. - float da = 0.2617994 * sin(6.2831853 * layer.fx.z / 20.0); + // Dérive : l'axe balance de ±30° (0.5235988 rad) en 20 s, le dégradé glisse le long + // de lui de ±20 % en 15 s. + float da = 0.5235988 * sin(6.2831853 * layer.fx.z / 20.0); float sa = sin(da); float ca = cos(da); dir = float2(dir.x * ca - dir.y * sa, dir.x * sa + dir.y * ca); + slide = 0.2 * sin(6.2831853 * layer.fx.z / 15.0); } float denom = max(abs(dir.x) + abs(dir.y), 1e-4); // Paramétré sur le QUAD dès qu'il en a un (la bulle webcam), sinon sur la sortie. Pour le @@ -2290,7 +2300,7 @@ fragment float4 ps_main(VSOut i [[stage_in]], float2 gp = (layer.quad_px.x > 0.0 && layer.quad_px.y > 0.0) ? (i.local / layer.quad_px) : i.pout; - float t = clamp(0.5 + dot(gp - 0.5, dir) / denom, 0.0, 1.0); + float t = clamp(0.5 + dot(gp - 0.5, dir) / denom + slide, 0.0, 1.0); float3 g = ramp4(t, layer.color, layer.src_prev, layer.dst_prev, layer.src); if (layer.fx.w > 1.5) { diff --git a/crates/compositor/src/vk_shaders/layer.wgsl b/crates/compositor/src/vk_shaders/layer.wgsl index c7131acde..69f1026d6 100644 --- a/crates/compositor/src/vk_shaders/layer.wgsl +++ b/crates/compositor/src/vk_shaders/layer.wgsl @@ -424,6 +424,11 @@ fn ramp4(t: f32, k0: vec4, k1: vec4, k2: vec4, k3: vec4) -> return c; } +// Eclaircit vers le blanc (k > 0) ou assombrit vers le noir (k < 0) d'une fraction |k|. +fn lighten(c: vec3, k: f32) -> vec3 { + return select(c * (1.0 + k), c + (vec3(1.0) - c) * k, k > 0.0); +} + // Mouvements 2 (aurore) et 3 (vagues) du mode 5. Miroir ligne pour ligne de // `gradient_motion` cote HLSL (commentaires complets la-bas). fn gradient_motion(gp: vec2, dir: vec2, denom: f32, k0: vec4, k1: vec4, @@ -431,23 +436,24 @@ fn gradient_motion(gp: vec2, dir: vec2, denom: f32, k0: vec4, k1: let TAU = 6.2831853; let u = dot(gp - vec2(0.5), dir) / denom; // position le long de l'axe, -0.5..0.5 if motion < 2.5 { - // Aurore : rampe perturbee par un bruit lent, puis trois nappes gaussiennes. + // Aurore : rampe perturbee par un bruit, puis trois larges nappes gaussiennes. let p = vec2((gp.x - 0.5) * aspect, gp.y - 0.5); - let ph = TAU * time / 120.0; - let n = value_noise(p * 2.5 + 1.5 * vec2(cos(ph), sin(ph))); - var g = ramp4(clamp(0.5 + u + 0.3 * (n - 0.5), 0.0, 1.0), k0, k1, k2, k3); - let b0 = vec2(0.35 * aspect * sin(TAU * time / 20.0), 0.25 * sin(TAU * time / 30.0 + 1.0)); - let b1 = vec2(0.30 * aspect * sin(TAU * time / 24.0 + 2.0), 0.22 * cos(TAU * time / 40.0)); - let b2 = vec2(0.25 * aspect * cos(TAU * time / 30.0 + 4.0), 0.28 * sin(TAU * time / 24.0 + 3.0)); - g = mix(g, k3.rgb, 0.45 * exp(-dot(p - b0, p - b0) / 0.08)); - g = mix(g, k0.rgb, 0.45 * exp(-dot(p - b1, p - b1) / 0.06)); - g = mix(g, k3.rgb, 0.35 * exp(-dot(p - b2, p - b2) / 0.05)); + let ph = TAU * time / 30.0; + let n = value_noise(p * 1.8 + 1.5 * vec2(cos(ph), sin(ph))); + var g = ramp4(clamp(0.5 + u + 0.6 * (n - 0.5), 0.0, 1.0), k0, k1, k2, k3); + let b0 = vec2(0.35 * aspect * sin(TAU * time / 10.0), 0.25 * sin(TAU * time / 15.0 + 1.0)); + let b1 = vec2(0.30 * aspect * sin(TAU * time / 12.0 + 2.0), 0.22 * cos(TAU * time / 20.0)); + let b2 = vec2(0.25 * aspect * cos(TAU * time / 15.0 + 4.0), 0.28 * sin(TAU * time / 12.0 + 3.0)); + let light = lighten(k3.rgb, 0.15); + g = mix(g, light, 0.7 * exp(-dot(p - b0, p - b0) / 0.176)); + g = mix(g, lighten(k0.rgb, -0.15), 0.7 * exp(-dot(p - b1, p - b1) / 0.132)); + g = mix(g, light, 0.6 * exp(-dot(p - b2, p - b2) / 0.11)); return g; } - // Vagues : trois bandes sinus perpendiculaires a l'axe (12 s), ondulees (20 s). + // Vagues : trois bandes sinus perpendiculaires a l'axe (6 s), ondulees (10 s), cretes eclairees. let v = dot(gp - vec2(0.5), vec2(-dir.y, dir.x)) / denom; - let w = sin(TAU * (3.0 * u + 0.04 * sin(TAU * (1.5 * v + time / 20.0)) - time / 12.0)); - return ramp4(clamp(0.5 + u + 0.07 * w, 0.0, 1.0), k0, k1, k2, k3); + let w = sin(TAU * (3.0 * u + 0.04 * sin(TAU * (1.5 * v + time / 10.0)) - time / 6.0)); + return lighten(ramp4(clamp(0.5 + u + 0.18 * w, 0.0, 1.0), k0, k1, k2, k3), 0.07 * w); } // Couverture d'une pastille (disque) adoucie sur ~1.5 px, pour la barre de titre du mode 14. @@ -1869,12 +1875,15 @@ fn fs_main(i: VsOut) -> @location(0) vec4 { // Fond anime : fx.z = temps programme (s, replie sur 120), fx.w = mouvement (0 immobile, // 1 derive, 2 aurore, 3 vagues), mb.x = aspect w/h. 0 rend le degrade d'avant a l'octet. var dir = layer.fx.xy; + var slide = 0.0; if layer.fx.w > 0.5 && layer.fx.w < 1.5 { - // Derive : l'axe respire de +-15 deg (0.2617994 rad) en 20 s. - let da = 0.2617994 * sin(6.2831853 * layer.fx.z / 20.0); + // Derive : l'axe balance de +-30 deg (0.5235988 rad) en 20 s, le degrade glisse le + // long de lui de +-20 % en 15 s. + let da = 0.5235988 * sin(6.2831853 * layer.fx.z / 20.0); let sa = sin(da); let ca = cos(da); dir = vec2(dir.x * ca - dir.y * sa, dir.x * sa + dir.y * ca); + slide = 0.2 * sin(6.2831853 * layer.fx.z / 15.0); } let denom = max(abs(dir.x) + abs(dir.y), 1e-4); // Parametre sur le QUAD des qu'il en a un (la bulle webcam), sinon sur la sortie. Pour @@ -1884,7 +1893,7 @@ fn fs_main(i: VsOut) -> @location(0) vec4 { if layer.quad_px.x > 0.0 && layer.quad_px.y > 0.0 { gp = i.local / layer.quad_px; } - let t = clamp(0.5 + dot(gp - vec2(0.5), dir) / denom, 0.0, 1.0); + let t = clamp(0.5 + dot(gp - vec2(0.5), dir) / denom + slide, 0.0, 1.0); rgb = ramp4(t, layer.color, layer.src_prev, layer.dst_prev, layer.src); if layer.fx.w > 1.5 { rgb = gradient_motion(gp, dir, denom, layer.color, layer.src_prev, layer.dst_prev,