moire: optional large foreground bokeh drifting past now and then

8f734ab · Gabriel Morell · 2026-09-29 21:17

13 files +64 -1

Files changed

modified README.md
+3 −1
@@ -57,7 +57,9 @@ that draws its patterns live: the moire, a halftone field, both, or
57 57 bokeh (soft out-of-focus discs drifting upward and fading in and out
58 58 as they rise, 4 to 64 of them, each its own shade from the primary
59 59 towards the accent and from muted to vivid). It can also fade back and
60 −forth between any two of those every few minutes.
60 +forth between any two of those every few minutes. Optionally, with any
61 +pattern, a few large defocused discs drift past in front now and then,
62 +like a light passing close to the lens.
61 63 Everything drifts slowly and never visibly repeats.
62 64
63 65 Its colours come from the waxx themes (dark, light, her, afterdark,
modified moire/shaders/field.frag
+44 −0
@@ -31,6 +31,7 @@ layout(std140, binding = 0) uniform buf {
31 31 float wave; // halftone wave phase, radians, kept in [0, 2pi)
32 32 float bokeh; // 0-1, how much of the bokeh to show
33 33 float lights; // how many bokeh discs, up to MAX_DISCS
34 + float flare; // 0-1, how much of the large foreground discs to show
34 35 float drift; // bokeh phase, 0-1 over its whole cycle
35 36
36 37 vec4 base; // the ground
@@ -105,6 +106,43 @@ vec4 bokehLight(vec2 p, float aspect, float px)
105 106 return vec4(total > 0.0 ? sum / total : vec3(0.0), min(total, 1.0));
106 107 }
107 108
109 +// A few large, very defocused discs in front of everything, like a
110 +// light passing close to the lens. Each is visible for about a third
111 +// of its cycle, fading in and out as it slides across, and absent the
112 +// rest of the time, so they come by every so often rather than always.
113 +// As with the bokeh, whole-number laps keep the drift wrap seamless.
114 +const int FLARES = 3;
115 +
116 +vec4 foregroundLight(vec2 p, float aspect)
117 +{
118 + vec3 sum = vec3(0.0);
119 + float total = 0.0;
120 + for (int i = 0; i < FLARES; i++) {
121 + float n = float(i) + 40.0;
122 + float laps = 1.0 + floor(hash(n) * 2.0); // 1-2 visits a cycle
123 + float life = fract(hash(n + 1.0) + laps * drift);
124 + float window = life / 0.35; // visible for the first 35%
125 + if (window >= 1.0)
126 + continue;
127 + float envelope = sin(3.1415927 * window);
128 + envelope *= envelope; // ease in and out
129 + float fromRight = step(0.5, hash(n + 2.0));
130 + float x = mix(-0.2, aspect + 0.2, hash(n + 3.0))
131 + + mix(0.35, -0.35, fromRight) * window;
132 + vec2 c = vec2(x, mix(0.15, 0.85, hash(n + 4.0)) - 0.12 * window);
133 + float radius = 0.22 + 0.2 * hash(n + 5.0);
134 + float d = length(p - c);
135 + // Far out of focus: a wide soft edge and only a hint of a rim.
136 + float disc = 1.0 - smoothstep(radius * 0.72, radius, d);
137 + float rim = smoothstep(radius * 0.6, radius * 0.88, d) * disc;
138 + float strength = (disc * 0.8 + rim * 0.2) * envelope;
139 +
140 + sum += mix(tone.rgb, ink.rgb, 0.6 + 0.4 * hash(n + 6.0)) * strength;
141 + total += strength;
142 + }
143 + return vec4(total > 0.0 ? sum / total : vec3(0.0), min(total, 1.0));
144 +}
145 +
108 146 void main()
109 147 {
110 148 float px = 1.0 / size.y;
@@ -147,5 +185,11 @@ void main()
147 185 colour = mix(colour, ink.rgb, light * 0.6 * fade(p) * moire);
148 186 }
149 187
188 + // In front of everything, so drawn last.
189 + if (flare > 0.0) {
190 + vec4 light = foregroundLight(p, size.x * px);
191 + colour = mix(colour, light.rgb, light.a * 0.11 * flare);
192 + }
193 +
150 194 fragColor = vec4(colour, 1.0) * qt_Opacity;
151 195 }
modified moire/theme/contents/config/main.xml
+4 −0
@@ -31,6 +31,10 @@
31 31 <label>Frames per second while animating</label>
32 32 <default>30</default>
33 33 </entry>
34 + <entry name="ForegroundBokeh" type="Bool">
35 + <label>Now and then, large defocused lights drifting past in front</label>
36 + <default>false</default>
37 + </entry>
34 38 <entry name="Wordmark" type="Bool">
35 39 <label>Write NEIAM in the bottom right corner</label>
36 40 <default>false</default>
modified moire/theme/contents/ui/Field.qml
+4 −0
@@ -17,6 +17,8 @@ Item {
17 17 property string fadeFrom: "moire"
18 18 property string fadeTo: "halftone"
19 19 property int bokehLights: 14
20 + // Now and then, a few large defocused discs drifting past in front.
21 + property bool foreground: false
20 22 // Multiplies how fast everything drifts; 0 holds the picture still.
21 23 property real speed: 1
22 24 property int frameRate: 30
@@ -94,6 +96,8 @@ Item {
94 96 property real halftone: field.amount("halftone")
95 97 property real bokeh: field.amount("bokeh")
96 98 property real lights: field.bokehLights
99 + property real flare: field.foreground ? 1 : 0
100 + Behavior on flare { NumberAnimation { duration: 1500 } }
97 101 Behavior on moire { enabled: field.pattern !== "alternate"; NumberAnimation { duration: 1500 } }
98 102 Behavior on halftone { enabled: field.pattern !== "alternate"; NumberAnimation { duration: 1500 } }
99 103 Behavior on bokeh { enabled: field.pattern !== "alternate"; NumberAnimation { duration: 1500 } }
modified moire/theme/contents/ui/config.qml
+8 −0
@@ -18,6 +18,7 @@ Kirigami.FormLayout {
18 18 property double cfg_Speed
19 19 property int cfg_FrameRate
20 20 property bool cfg_Wordmark
21 + property bool cfg_ForegroundBokeh
21 22 property string cfg_ThemeMode
22 23 property string cfg_Theme
23 24 property string cfg_NightTheme
@@ -127,6 +128,13 @@ Kirigami.FormLayout {
127 128 enabled: root.cfg_Speed > 0
128 129 }
129 130
131 + QQC2.CheckBox {
132 + Kirigami.FormData.label: "Foreground:"
133 + text: "Large lights drifting past now and then"
134 + checked: root.cfg_ForegroundBokeh
135 + onToggled: root.cfg_ForegroundBokeh = checked
136 + }
137 +
130 138 QQC2.CheckBox {
131 139 Kirigami.FormData.label: "Wordmark:"
132 140 text: "Write NEIAM in the corner"
modified ?
+0 −0

Binary file — not rendered.

modified moire/theme/contents/ui/main.qml
+1 −0
@@ -32,6 +32,7 @@ WallpaperItem {
32 32 fadeFrom: root.cfg.FadeFrom
33 33 fadeTo: root.cfg.FadeTo
34 34 bokehLights: root.cfg.BokehLights
35 + foreground: root.cfg.ForegroundBokeh
35 36 speed: root.cfg.Speed
36 37 frameRate: root.cfg.FrameRate
37 38 wordmark: root.cfg.Wordmark

Parents: bd286fb