summaryrefslogtreecommitdiff
path: root/shaders/crt.frag.glsl
diff options
context:
space:
mode:
Diffstat (limited to 'shaders/crt.frag.glsl')
-rw-r--r--shaders/crt.frag.glsl175
1 files changed, 0 insertions, 175 deletions
diff --git a/shaders/crt.frag.glsl b/shaders/crt.frag.glsl
deleted file mode 100644
index b0b0b93b..00000000
--- a/shaders/crt.frag.glsl
+++ /dev/null
@@ -1,175 +0,0 @@
-#version 450
-
-// The Crt builtin's post-process. Screen point cc shows the scene at
-// uv' = cc*(1 + BARREL*r2), and src/gui/crt.zig maps mouse input through that
-// same transform — keep them equal or clicks drift near the edges.
-
-layout(set = 2, binding = 0) uniform sampler2D u_scene;
-layout(std140, set = 3, binding = 0) uniform CrtUniforms {
- float time_seconds;
- float frame;
- vec2 viewport;
- // x = CRT, y = ripple, z = glitch. One scene pass can combine them.
- vec4 effects;
-} u_crt;
-
-layout(location = 0) out vec4 o_col;
-
-const float TAU = 6.28318530718;
-const float BARREL = 0.016; // MUST match src/gui/crt.zig barrel
-const float EDGE_SOFTNESS_PX = 3.5;
-const float CHROMA_PX = 0.28; // radial R/B separation in physical pixels at r2=1
-const float BLOOM_RADIUS_PX = 1.75;
-const float BLOOM_THRESHOLD = 0.60; // linear-light channel peak
-const float BLOOM_SOFTNESS = 0.25;
-const float BLOOM_STRENGTH = 0.045;
-const float SCAN_PERIOD_PX = 3.0;
-const float SCAN_STRENGTH = 0.055;
-const float MASK_PERIOD_PX = 3.0;
-const float MASK_STRENGTH = 0.015;
-const float VIGNETTE_STRENGTH = 0.11; // 11% only at the extreme corners
-const float HUM_STRENGTH = 0.0025;
-const float NOISE_STRENGTH = 0.0020;
-const float EXPOSURE = 1.012;
-
-vec3 srgbToLinear(vec3 c) {
- vec3 low = c / 12.92;
- vec3 high = pow((c + 0.055) / 1.055, vec3(2.4));
- return mix(high, low, lessThanEqual(c, vec3(0.04045)));
-}
-
-vec3 linearToSrgb(vec3 c) {
- c = max(c, vec3(0.0));
- vec3 low = c * 12.92;
- vec3 high = 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055;
- return mix(high, low, lessThanEqual(c, vec3(0.0031308)));
-}
-
-vec3 sceneLinear(vec2 uv) {
- return srgbToLinear(texture(u_scene, uv).rgb);
-}
-
-vec3 brightPass(vec2 uv) {
- vec3 c = sceneLinear(uv);
- float peak = max(c.r, max(c.g, c.b));
- return c * smoothstep(BLOOM_THRESHOLD, BLOOM_THRESHOLD + BLOOM_SOFTNESS, peak);
-}
-
-float noiseAt(vec2 p) {
- vec3 q = fract(vec3(p.xyx) * 0.1031);
- q += dot(q, q.yzx + 33.33);
- return fract((q.x + q.y) * q.z);
-}
-
-// The non-CRT scene effects deliberately distort the source coordinates,
-// rather than remapping the final colors. Themes retain their palette and the
-// effects remain composable with the linear-light CRT treatment below.
-vec2 sceneUv(vec2 uv) {
- if (u_crt.effects.y > 0.5) {
- vec2 centered = uv - 0.5;
- float radius = length(centered);
- float envelope = 1.0 - smoothstep(0.04, 0.72, radius);
- float wave = sin(radius * 42.0 - u_crt.time_seconds * 5.2);
- uv += centered / max(radius, 0.00001) * wave * envelope * 0.0032;
- }
- if (u_crt.effects.z > 0.5) {
- float band = floor(uv.y * 96.0);
- float seed = noiseAt(vec2(band, floor(u_crt.time_seconds * 13.0)));
- float tear = smoothstep(0.955, 1.0, seed);
- uv.x += (noiseAt(vec2(seed, band)) - 0.5) * tear * 0.020;
- }
- return uv;
-}
-
-// The scene, ALPHA INCLUDED: with a see-through ground the grid arrives
-// premultiplied and partly empty, and a pass that dropped .a would turn the
-// desktop behind a `*_transparent` theme into opaque black the moment Crt,
-// Ripple or Glitch came on.
-vec4 sceneSrgb(vec2 uv) {
- vec2 warped = sceneUv(uv);
- vec4 center = texture(u_scene, warped);
- if (u_crt.effects.z <= 0.5) return center;
-
- float band = floor(warped.y * 96.0);
- float seed = noiseAt(vec2(band, floor(u_crt.time_seconds * 13.0)));
- float tear = smoothstep(0.955, 1.0, seed);
- vec2 chroma = vec2((0.35 + 1.25 * tear) / u_crt.viewport.x, 0.0);
- return vec4(
- texture(u_scene, warped + chroma).r,
- center.g,
- texture(u_scene, warped - chroma).b,
- center.a
- );
-}
-
-vec3 effectSceneLinear(vec2 uv) {
- return srgbToLinear(sceneSrgb(uv).rgb);
-}
-
-void main() {
- vec2 size = u_crt.viewport;
- vec2 texel = 1.0 / size;
- vec2 cc = gl_FragCoord.xy / size * 2.0 - 1.0;
- float r2 = dot(cc, cc);
- vec2 uv = cc * (1.0 + BARREL * r2 * u_crt.effects.x) * 0.5 + 0.5;
-
- // Ripple and glitch are useful without the CRT. They only move samples;
- // skip every tube-specific light/mask/exposure adjustment.
- if (u_crt.effects.x <= 0.5) {
- // The scene texture is already sRGB. Avoid a needless transfer-curve
- // round trip so a coordinate-only effect preserves theme bytes.
- vec4 s = sceneSrgb(uv);
- o_col = vec4(clamp(s.rgb, 0.0, 1.0), clamp(s.a, 0.0, 1.0));
- return;
- }
-
- vec3 col = vec3(0.0);
- // Outside the tube nothing is drawn, so nothing is covered either: the
- // old opaque black there would have punched a rectangle out of the
- // compositor's backdrop.
- float alpha = 0.0;
- float edge = min(min(uv.x, uv.y), min(1.0 - uv.x, 1.0 - uv.y));
- if (edge > 0.0) {
- vec3 center = effectSceneLinear(uv);
- vec2 chroma = cc * (CHROMA_PX * r2) * texel;
- // Each radial sample follows the same ripple/glitch path from its own
- // pre-warp coordinate. This is also how the Metal kernel composes the
- // effects; sampling a once-warped center here made combined effects
- // diverge between the two GUI backends.
- col = vec3(
- effectSceneLinear(uv + chroma).r,
- center.g,
- effectSceneLinear(uv - chroma).b
- );
-
- vec2 bloom_px = texel * BLOOM_RADIUS_PX;
- vec3 bloom = brightPass(uv + vec2(bloom_px.x, 0.0)) +
- brightPass(uv - vec2(bloom_px.x, 0.0)) +
- brightPass(uv + vec2(0.0, bloom_px.y)) +
- brightPass(uv - vec2(0.0, bloom_px.y));
- col += bloom * (0.25 * BLOOM_STRENGTH);
-
- // The mask has unit mean and the scan loss stays small; neither needs
- // the old global lift that remapped every theme toward white.
- float scan_wave = 0.5 + 0.5 * cos(gl_FragCoord.y * TAU / SCAN_PERIOD_PX);
- col *= 1.0 - SCAN_STRENGTH * scan_wave * scan_wave;
- vec3 mask = 1.0 + MASK_STRENGTH * cos(
- gl_FragCoord.x * TAU / MASK_PERIOD_PX + vec3(0.0, TAU / 3.0, 2.0 * TAU / 3.0)
- );
- col *= mask;
-
- vec2 edge_px = min(uv, 1.0 - uv) * size;
- float tube = smoothstep(0.0, EDGE_SOFTNESS_PX, min(edge_px.x, edge_px.y));
- float vignette = 1.0 - VIGNETTE_STRENGTH * pow(clamp(r2 * 0.5, 0.0, 1.0), 1.6);
- float hum = 1.0 + HUM_STRENGTH * sin((uv.y * 1.35 - u_crt.time_seconds * 0.11) * TAU);
- col *= tube * vignette * hum * EXPOSURE;
- // The tube's edge falloff dims coverage as well as colour; everything
- // else here is a light adjustment and leaves it alone.
- alpha = sceneSrgb(uv).a * tube;
-
- float luma = dot(col, vec3(0.2126, 0.7152, 0.0722));
- vec2 noise_seed = gl_FragCoord.xy + vec2(u_crt.frame * 17.0, u_crt.frame * 59.0);
- col += (noiseAt(noise_seed) - 0.5) * NOISE_STRENGTH * (0.15 + 0.85 * clamp(luma, 0.0, 1.0));
- }
- o_col = vec4(linearToSrgb(clamp(col, 0.0, 1.0)), clamp(alpha, 0.0, 1.0));
-}