diff options
Diffstat (limited to 'shaders/post/grain.glsl')
| -rw-r--r-- | shaders/post/grain.glsl | 32 |
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); +} |
