moire: measure the pattern in units of the screen's shorter side

56cf58f · Gabriel Morell · 2026-09-29 23:17

8 files +23 -19
Message
{commit_body(@commit)}

Files changed

modified moire/shaders/field.frag
+18 −16
@@ -18,8 +18,9 @@ layout(std140, binding = 0) uniform buf {
18 18 float qt_Opacity;
19 19
20 20 vec2 size; // pixels
21 − // Positions and lengths below are in units of the height, so every
22 − // aspect ratio shows the same pattern at the same apparent scale.
21 + // Positions and lengths below are in units of the shorter side, so
22 + // every aspect ratio, landscape or portrait, shows the same pattern
23 + // at the same apparent scale. The screen spans 0 to size / shorter.
23 24 vec2 ringA; // centres of the two ring families
24 25 vec2 ringB;
25 26 vec2 hub; // where the pattern is strongest
@@ -110,13 +111,13 @@ float starLight(vec2 p, float px)
110 111 // A band of light sweeping diagonally across the screen during the
111 112 // first 30% of each glint cycle. It lights only the rings and dots, as
112 113 // light catches fine grooves, and is zero between sweeps.
113 −float glintBand(vec2 p, float aspect)
114 +float glintBand(vec2 p, vec2 extent)
114 115 {
115 116 if (glintPhase >= 0.3)
116 117 return 0.0;
117 118 float s = glintPhase / 0.3;
118 119 vec2 dir = vec2(0.8, -0.6);
119 − float c0 = 0.0, c1 = aspect * dir.x, c2 = dir.y, c3 = aspect * dir.x + dir.y;
120 + float c0 = 0.0, c1 = extent.x * dir.x, c2 = extent.y * dir.y, c3 = c1 + c2;
120 121 float lo = min(min(c0, c1), min(c2, c3));
121 122 float hi = max(max(c0, c1), max(c2, c3));
122 123 const float width = 0.08;
@@ -134,7 +135,7 @@ float glintBand(vec2 p, float aspect)
134 135 // vivid. Returns the discs' blended colour and their combined coverage.
135 136 const int MAX_DISCS = 64;
136 137
137 −vec4 bokehLight(vec2 p, float aspect, float px)
138 +vec4 bokehLight(vec2 p, vec2 extent, float px)
138 139 {
139 140 // More discs means smaller ones, so a crowd stays a scatter of
140 141 // lights rather than a haze.
@@ -149,9 +150,9 @@ vec4 bokehLight(vec2 p, float aspect, float px)
149 150 float climb = 1.0 + floor(hash(n + 1.0) * 3.0); // 1-3 laps a cycle
150 151 float sway = 1.0 + floor(hash(n + 2.0) * 2.0);
151 152 float life = fract(hash(n + 3.0) + climb * drift); // 0 at bottom, 1 at top
152 − vec2 c = vec2(hash(n + 4.0) * aspect
153 + vec2 c = vec2(hash(n + 4.0) * extent.x
153 154 + 0.04 * sin(6.2831853 * (sway * drift + hash(n + 5.0))),
154 − 1.15 - 1.3 * life);
155 + extent.y + 0.15 - (extent.y + 0.3) * life);
155 156 float radius = (0.035 + 0.075 * hash(n + 6.0)) * scale;
156 157 float d = length(p - c);
157 158 float disc = clamp((radius - d) / (2.5 * px) + 0.5, 0.0, 1.0);
@@ -177,7 +178,7 @@ vec4 bokehLight(vec2 p, float aspect, float px)
177 178 // As with the bokeh, whole-number laps keep the drift wrap seamless.
178 179 const int FLARES = 3;
179 180
180 −vec4 foregroundLight(vec2 p, float aspect)
181 +vec4 foregroundLight(vec2 p, vec2 extent)
181 182 {
182 183 vec3 sum = vec3(0.0);
183 184 float total = 0.0;
@@ -191,9 +192,9 @@ vec4 foregroundLight(vec2 p, float aspect)
191 192 float envelope = sin(3.1415927 * window);
192 193 envelope *= envelope; // ease in and out
193 194 float fromRight = step(0.5, hash(n + 2.0));
194 − float x = mix(-0.2, aspect + 0.2, hash(n + 3.0))
195 + float x = mix(-0.2, extent.x + 0.2, hash(n + 3.0))
195 196 + mix(0.35, -0.35, fromRight) * window;
196 − vec2 c = vec2(x, mix(0.15, 0.85, hash(n + 4.0)) - 0.12 * window);
197 + vec2 c = vec2(x, mix(0.15, 0.85, hash(n + 4.0)) * extent.y - 0.12 * window);
197 198 float radius = 0.22 + 0.2 * hash(n + 5.0);
198 199 float d = length(p - c);
199 200 // Far out of focus: a wide soft edge and only a hint of a rim.
@@ -209,9 +210,10 @@ vec4 foregroundLight(vec2 p, float aspect)
209 210
210 211 void main()
211 212 {
212 − float px = 1.0 / size.y;
213 − vec2 p = qt_TexCoord0 * size * px;
214 − vec2 centre = vec2(0.5 * size.x * px, 0.5);
213 + float px = 1.0 / min(size.x, size.y);
214 + vec2 extent = size * px;
215 + vec2 p = qt_TexCoord0 * extent;
216 + vec2 centre = 0.5 * extent;
215 217
216 218 // Ground: base, darkening towards deep at the corners.
217 219 float v = smoothstep(0.35, 1.25, length(p - centre));
@@ -220,10 +222,10 @@ void main()
220 222 if (stars > 0.0)
221 223 colour = mix(colour, ink.rgb, starLight(p, px) * 0.85 * stars);
222 224
223 − float glint = glints > 0.0 ? glintBand(p, size.x * px) * glints : 0.0;
225 + float glint = glints > 0.0 ? glintBand(p, extent) * glints : 0.0;
224 226
225 227 if (bokeh > 0.0) {
226 − vec4 light = bokehLight(p, size.x * px, px);
228 + vec4 light = bokehLight(p, extent, px);
227 229 colour = mix(colour, light.rgb, light.a * 0.3 * bokeh);
228 230 }
229 231
@@ -261,7 +263,7 @@ void main()
261 263
262 264 // In front of everything, so drawn last.
263 265 if (flare > 0.0) {
264 − vec4 light = foregroundLight(p, size.x * px);
266 + vec4 light = foregroundLight(p, extent);
265 267 colour = mix(colour, light.rgb, light.a * 0.11 * flare);
266 268 }
267 269
modified moire/theme/contents/ui/Field.qml
+5 −3
@@ -52,7 +52,9 @@ Item {
52 52 // random point in the motion so two screens don't mirror each other.
53 53 property real clock: Math.random() * 10000
54 54
55 − readonly property real aspect: height > 0 ? width / height : 16 / 9
55 + // The screen's size in the shader's units, the shorter side's length.
56 + readonly property real extentX: height > 0 ? width / Math.min(width, height) : 16 / 9
57 + readonly property real extentY: width > 0 ? height / Math.min(width, height) : 1
56 58
57 59 // How much of each layer a pattern shows.
58 60 function layers(name) {
@@ -90,8 +92,8 @@ Item {
90 92 // The hub, where the pattern is strongest, wanders around the
91 93 // right of centre; the ring centres circle it at a breathing
92 94 // distance, which walks the moire's beat around the rings.
93 − property point hub: Qt.point(0.62 * field.aspect + 0.05 * field.sweep(97, 0),
94 − 0.46 + 0.04 * field.sweep(131, 0.3))
95 + property point hub: Qt.point(0.62 * field.extentX + 0.05 * field.sweep(97, 0),
96 + 0.46 * field.extentY + 0.04 * field.sweep(131, 0.3))
95 97 readonly property real spread: 0.03 * (1 + 0.35 * field.sweep(71, 0.1))
96 98 readonly property real turn: 2 * Math.PI * field.clock / 53
97 99 property point ringA: Qt.point(hub.x - spread * Math.cos(turn) / 2,
modified ?
+0 −0

Binary file — not rendered.

Parents: cd4c494