// The moire wallpaper's whole picture: ground, halftone dots and moire
// rings, each fading out into the ground away from a drifting hub.
//
// Everything that moves is computed in Field.qml, in double precision,
// and arrives here as positions and phases; the shader holds no clock,
// so it never loses precision however long the session runs.
//
// Compiled into ../theme/contents/ui/field.frag.qsb by tools/shaders.sh.
#version 440
layout(location = 0) in vec2 qt_TexCoord0;
layout(location = 0) out vec4 fragColor;
layout(std140, binding = 0) uniform buf {
mat4 qt_Matrix;
float qt_Opacity;
vec2 size; // pixels
// Positions and lengths below are in units of the height, so every
// aspect ratio shows the same pattern at the same apparent scale.
vec2 ringA; // centres of the two ring families
vec2 ringB;
vec2 hub; // where the pattern is strongest
float reach; // distance at which it has faded out entirely
float period; // ring spacing
float moire; // 0-1, how much of the rings to show
float halftone; // 0-1, how much of the dots to show
float cell; // halftone grid spacing
float angle; // halftone grid rotation, radians
float wave; // halftone wave phase, radians, kept in [0, 2pi)
vec4 base; // the ground
vec4 deep; // the ground at the edges
vec4 ink; // the moire
vec4 tone; // the halftone
};
float fade(vec2 p)
{
float t = clamp(1.0 - length(p - hub) / reach, 0.0, 1.0);
return t * t * (3.0 - 2.0 * t);
}
float rings(vec2 p, vec2 centre, float px)
{
float r = length(p - centre);
float d = abs(fract(r / period + 0.5) - 0.5) * period;
float halfWidth = max(0.9 * px, period * 0.09);
return clamp(1.0 - (d - halfWidth) / px, 0.0, 1.0);
}
void main()
{
float px = 1.0 / size.y;
vec2 p = qt_TexCoord0 * size * px;
vec2 centre = vec2(0.5 * size.x * px, 0.5);
// Ground: base, darkening towards deep at the corners.
float v = smoothstep(0.35, 1.25, length(p - centre));
vec3 colour = mix(base.rgb, deep.rgb, v);
// Halftone: dots on a rotated grid, each sized by the fade and a
// travelling wave sampled at its cell's centre, so a dot is round
// and uniform rather than cut by the field. Area, not radius, is
// proportional to the field, as in print.
if (halftone > 0.0) {
mat2 rot = mat2(cos(angle), sin(angle), -sin(angle), cos(angle));
vec2 q = rot * p;
vec2 index = floor(q / cell);
vec2 local = (q / cell - index - 0.5) * cell;
vec2 cellCentre = transpose(rot) * ((index + 0.5) * cell);
float field = fade(cellCentre)
* (0.55 + 0.45 * sin(dot(cellCentre, vec2(0.8, 0.6)) * 9.0 - wave));
float radius = 0.5 * cell * sqrt(field);
float dotCover = clamp((radius - length(local)) / px + 0.5, 0.0, 1.0);
colour = mix(colour, tone.rgb, dotCover * halftone * 0.55);
}
// Moire: two ring families slightly apart. Where their rings
// interleave the light doubles up, where they coincide it thins
// out, and that beat is the moire.
if (moire > 0.0) {
float a = rings(p, ringA, px);
float b = rings(p, ringB, px);
float light = 1.0 - (1.0 - 0.85 * a) * (1.0 - 0.85 * b);
colour = mix(colour, ink.rgb, light * 0.6 * fade(p) * moire);
}
fragColor = vec4(colour, 1.0) * qt_Opacity;
}
neiam
/neiam-devices
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
3.4 KiB · text
History
f629a67