summaryrefslogtreecommitdiff
path: root/shaders
diff options
context:
space:
mode:
Diffstat (limited to 'shaders')
-rw-r--r--shaders/crt.frag.glsl39
-rw-r--r--shaders/crt.vert.glsl9
2 files changed, 48 insertions, 0 deletions
diff --git a/shaders/crt.frag.glsl b/shaders/crt.frag.glsl
new file mode 100644
index 00000000..1c9b15a8
--- /dev/null
+++ b/shaders/crt.frag.glsl
@@ -0,0 +1,39 @@
+#version 450
+
+// The Crt builtin's post-process, one pass, fully static (no animation, no
+// glitches). Screen point cc shows the scene at uv' = cc*(1 + BARREL*r2),
+// and src/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(location = 0) out vec4 o_col;
+
+const float BARREL = 0.012; // barrel curvature; MUST match src/crt.zig barrel
+const float VIGNETTE = 0.07; // corner darkening: *= 1 - VIGNETTE*r2^2
+const vec2 PHOSPHOR = vec2(2.0, 2.0); // uv quantize cell in scene px (~1/7 of a glyph cell)
+const float SCAN = 0.15; // scanline depth, one raised-cosine band per PHOSPHOR.y rows
+const float CHROMA = 0.001; // R/B radial sample offset, grows toward the edges
+const float BLOOM = 0.4; // bright-pass neighbor glow added back
+const float BLOOM_MIN = 0.5; // channel level where the bright pass starts
+const float LIFT = 1.08; // output brightness lift (offsets scan+vignette losses)
+
+void main() {
+ vec2 size = vec2(textureSize(u_scene, 0));
+ vec2 cc = gl_FragCoord.xy / size * 2.0 - 1.0;
+ float r2 = dot(cc, cc);
+ vec2 uv = cc * (1.0 + BARREL * r2) * 0.5 + 0.5;
+ vec3 col = vec3(0.0);
+ if (all(greaterThanEqual(uv, vec2(0.0))) && all(lessThanEqual(uv, vec2(1.0)))) {
+ vec2 q = (floor(uv * size / PHOSPHOR) + 0.5) * PHOSPHOR / size;
+ vec2 ca = cc * (CHROMA * r2);
+ col = vec3(texture(u_scene, q + ca).r, texture(u_scene, q).g, texture(u_scene, q - ca).b);
+ vec2 px = PHOSPHOR / size;
+ vec3 nb = texture(u_scene, q + vec2(px.x, 0.0)).rgb + texture(u_scene, q - vec2(px.x, 0.0)).rgb +
+ texture(u_scene, q + vec2(0.0, px.y)).rgb + texture(u_scene, q - vec2(0.0, px.y)).rgb;
+ col += max(nb * 0.25 - BLOOM_MIN, 0.0) * BLOOM;
+ }
+ col *= 1.0 - VIGNETTE * r2 * r2;
+ col *= 1.0 - SCAN * (0.5 + 0.5 * cos(gl_FragCoord.y * 6.2831853 / PHOSPHOR.y));
+ o_col = vec4(col * LIFT, 1.0);
+}
diff --git a/shaders/crt.vert.glsl b/shaders/crt.vert.glsl
new file mode 100644
index 00000000..4e15fcdf
--- /dev/null
+++ b/shaders/crt.vert.glsl
@@ -0,0 +1,9 @@
+#version 450
+
+// CRT post-process pass: one fullscreen triangle from gl_VertexIndex, no
+// vertex buffer. The fragment shader derives uv from gl_FragCoord.
+
+void main() {
+ vec2 p = vec2(float((gl_VertexIndex << 1) & 2), float(gl_VertexIndex & 2));
+ gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
+}