summaryrefslogtreecommitdiff
path: root/shaders/post/grain.glsl
blob: 61c514927922883023d25a47150e5cd823c54dd4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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);
}