// 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); }