summaryrefslogtreecommitdiff
path: root/shaders/subpixel.glsl
diff options
context:
space:
mode:
authorhunter@kvog.sh <hunter@kvog.sh>2026-02-05 21:17:07 -0600
committerhunter@kvog.sh <hunter@kvog.sh>2026-02-05 21:17:07 -0600
commit9bb07a21660982f5ae809d8e48641699ba3f3b05 (patch)
tree43c6a78a073d8e72729431d8abdec53d78d225c4 /shaders/subpixel.glsl
parent117f1e009079140d1e7d56e526d61b74a05d8977 (diff)
shaders/subpixel
Diffstat (limited to 'shaders/subpixel.glsl')
-rw-r--r--shaders/subpixel.glsl90
1 files changed, 90 insertions, 0 deletions
diff --git a/shaders/subpixel.glsl b/shaders/subpixel.glsl
new file mode 100644
index 0000000..df65b91
--- /dev/null
+++ b/shaders/subpixel.glsl
@@ -0,0 +1,90 @@
+#version 330 core
+
+// --------------------------------------------------------------------------------
+// Basic subpixel rendering with LCD filtering.
+// Assumes R-G-B LCD with no compositor scaling
+//
+// ref: https://www.shadertoy.com/view/NtVXWc (spinning quad)
+//
+// SPDX-License-Identifier: 0BSD
+// --------------------------------------------------------------------------------
+
+// --------------------------------------------------------------------------------
+// Uniforms
+// --------------------------------------------------------------------------------
+uniform float u_time;
+uniform vec2 u_res;
+uniform vec2 u_quad;
+
+// --------------------------------------------------------------------------------
+// Vertex outputs
+// --------------------------------------------------------------------------------
+in vec2 v_p;
+in vec2 v_t;
+
+// --------------------------------------------------------------------------------
+// Fragment outputs
+// --------------------------------------------------------------------------------
+out vec4 f_c;
+
+// --------------------------------------------------------------------------------
+// Entry point
+// --------------------------------------------------------------------------------
+
+vec2 rotate(vec2 v, float theta)
+{
+ // ref: https://en.wikipedia.org/wiki/Rotation_matrix
+ return mat2(cos(theta), -sin(theta), sin(theta), cos(theta)) * v;
+}
+
+float box_d(vec2 v)
+{
+ return max(v.x, v.y);
+}
+
+float spinning_quad(vec2 pos)
+{
+ pos = rotate(pos, u_time * -0.25f);
+ vec2 halfsize = u_quad / 2.0f;
+ vec2 q = abs(pos) - halfsize;
+ return 1.0f - step(0.0f, box_d(q));
+}
+
+vec3 spinning_quad_subpixel(vec2 pos)
+{
+ vec2 one_third_x = vec2(1.0f / 3.0f, 0.0f);
+
+ float sL = spinning_quad(pos - one_third_x);
+ float sC = spinning_quad(pos);
+ float sR = spinning_quad(pos + one_third_x);
+
+#if 1
+ // 3-tap low pass filter
+ float weights[3] = float[3](0.25f, 0.50f, 0.25f);
+ float r = weights[1] * sL + weights[0] * sC;
+ float g = weights[0] * sL + weights[1] * sC + weights[2] * sR;
+ float b = weights[0] * sC + weights[1] * sR;
+
+ r *= (1.0f / 0.75f);
+ b *= (1.0f / 0.75f);
+#else
+ float r = sL;
+ float g = sC;
+ float b = sR;
+#endif
+
+ return vec3(r, g, b);
+}
+
+void main()
+{
+ vec2 cursor = gl_FragCoord.xy - (u_res / 2.0f);
+
+ // top quad
+ vec3 c1 = vec3(spinning_quad(cursor - vec2(0, u_res.y / 4.0f)));
+
+ // bottom quad
+ vec3 c2 = spinning_quad_subpixel(cursor + vec2(0, u_res.y / 4.0f));
+
+ f_c = vec4(mix(c2, c1, step(u_res.y / 2.0f, gl_FragCoord.y)), 1.0f);
+}