summaryrefslogtreecommitdiff
path: root/shaders/post/grain.glsl
diff options
context:
space:
mode:
Diffstat (limited to 'shaders/post/grain.glsl')
-rw-r--r--shaders/post/grain.glsl32
1 files changed, 32 insertions, 0 deletions
diff --git a/shaders/post/grain.glsl b/shaders/post/grain.glsl
new file mode 100644
index 00000000..61c51492
--- /dev/null
+++ b/shaders/post/grain.glsl
@@ -0,0 +1,32 @@
+// Grain: the page's own ground takes a fine, still grain, like paper. Only
+// pixels of the page's colour (iBackgroundColor) are grained: no glyph, no
+// selection, no tag or rule, and nothing in pardesSpare. The grain averages
+// to the page, is a logical pixel big (pardesScale), and never moves, so it
+// costs no frames. At most 1.5, 3 and 5 of 255 at levels 1 to 3, and never
+// more than pardesCap, which keeps the text on it at its own contrast or 4.5.
+// It is a Shadertoy file: pardes puts its prefix before it.
+
+float amplitude() { return min(pardesByLevel(1.5, 3.0, 5.0) / 255.0, pardesCap); }
+
+// An integer hash of the grain cell: white, with no pattern to see.
+float hash(uvec2 p) {
+ uint h = p.x * 1664525u + p.y * 1013904223u;
+ h ^= h >> 16;
+ h *= 2246822519u;
+ h ^= h >> 13;
+ h *= 3266489917u;
+ h ^= h >> 16;
+ return float(h) / 4294967295.0;
+}
+
+void mainImage(out vec4 fragColor, in vec2 fragCoord) {
+ vec2 uv = fragCoord / iResolution.xy;
+ vec4 frame = texture(iChannel0, uv);
+ fragColor = frame;
+ vec3 page = iBackgroundColor.rgb;
+ if (any(greaterThan(abs(frame.rgb - page), vec3(0.75 / 255.0))) || pardesSpared(fragCoord)) return;
+ uvec2 cell = uvec2(fragCoord / max(1.0, pardesScale));
+ // Two hashes summed: a triangular spread, softer than a flat one.
+ float n = hash(cell) + hash(cell + uvec2(7919u, 104729u)) - 1.0;
+ fragColor = vec4(clamp(page + n * amplitude(), 0.0, 1.0), frame.a);
+}