Skip to content

Commit 7ba9207

Browse files
committed
Add CRT Consumer shader
1 parent 6bf955a commit 7ba9207

5 files changed

Lines changed: 1168 additions & 0 deletions

File tree

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
// Geargrafx port of crt-consumer from libretro slang-shaders.
2+
// Original shader licensed GPL v2 or later.
3+
4+
in vec2 vTexCoord;
5+
out vec4 FragColor;
6+
7+
uniform sampler2D Source;
8+
uniform vec4 SourceSize;
9+
uniform vec4 OutputSize;
10+
uniform vec4 BackgroundColor;
11+
uniform int FrameCount;
12+
uniform float PRE_SCALE;
13+
uniform float blurx;
14+
uniform float blury;
15+
uniform float warpx;
16+
uniform float warpy;
17+
uniform float corner;
18+
uniform float smoothness;
19+
uniform float inter = 1.0;
20+
uniform float Downscale = 2.0;
21+
uniform float scanlow;
22+
uniform float scanhigh;
23+
uniform float beamlow;
24+
uniform float beamhigh;
25+
uniform float preserve;
26+
uniform float brightboost1;
27+
uniform float brightboost2;
28+
uniform float glow;
29+
uniform float quality;
30+
uniform float glow_str;
31+
uniform float nois;
32+
uniform float postbr;
33+
uniform float GAMMA_OUT;
34+
uniform float sat;
35+
uniform float contrast;
36+
uniform float WP;
37+
uniform float vignette;
38+
uniform float vpower;
39+
uniform float vstr;
40+
41+
const float MaskDark = 0.2;
42+
const float masksize = 1.0;
43+
44+
const mat3 D65_to_XYZ = mat3(
45+
0.4306190, 0.2220379, 0.0201853,
46+
0.3415419, 0.7066384, 0.1295504,
47+
0.1783091, 0.0713236, 0.9390944);
48+
49+
const mat3 XYZ_to_D65 = mat3(
50+
3.0628971, -0.9692660, 0.0678775,
51+
-1.3931791, 1.8760108, -0.2288548,
52+
-0.4757517, 0.0415560, 1.0693490);
53+
54+
const mat3 D50_to_XYZ = mat3(
55+
0.4552773, 0.2323025, 0.0145457,
56+
0.3675500, 0.7077956, 0.1049154,
57+
0.1413926, 0.0599019, 0.7057489);
58+
59+
const mat3 XYZ_to_D50 = mat3(
60+
2.9603944, -0.9787684, 0.0844874,
61+
-1.4678519, 1.9161415, -0.2545973,
62+
-0.4685105, 0.0334540, 1.4216174);
63+
64+
vec2 warp_position(vec2 pos)
65+
{
66+
pos = pos * 2.0 - 1.0;
67+
pos *= vec2(1.0 + pos.y * pos.y * warpx, 1.0 + pos.x * pos.x * warpy);
68+
return pos * 0.5 + 0.5;
69+
}
70+
71+
bool outside_source(vec2 pos)
72+
{
73+
return pos.x < 0.0 || pos.x > 1.0 || pos.y < 0.0 || pos.y > 1.0;
74+
}
75+
76+
vec3 sample_source(vec2 pos)
77+
{
78+
if (outside_source(pos))
79+
return BackgroundColor.rgb;
80+
81+
return texture(Source, pos).rgb;
82+
}
83+
84+
float scanline_weight(float y, float luminance)
85+
{
86+
float beam = mix(scanlow, scanhigh, y);
87+
float scan = mix(beamlow, beamhigh, luminance);
88+
float ex = y * scan;
89+
return exp2(-beam * ex * ex);
90+
}
91+
92+
vec3 default_mask(vec2 pos, float luminance)
93+
{
94+
pos = floor(pos / masksize);
95+
96+
float phase = fract(pos.x * 0.4999);
97+
vec3 mask = phase < 0.4999 ? vec3(1.0, MaskDark, 1.0) : vec3(MaskDark, 1.0, MaskDark);
98+
return mix(mask, vec3(1.0), luminance * preserve);
99+
}
100+
101+
mat4 contrast_matrix(void)
102+
{
103+
float t = (1.0 - contrast) * 0.5;
104+
105+
return mat4(contrast, 0.0, 0.0, 0.0,
106+
0.0, contrast, 0.0, 0.0,
107+
0.0, 0.0, contrast, 0.0,
108+
t, t, t, 1.0);
109+
}
110+
111+
float vignette_amount(void)
112+
{
113+
if (vignette < 0.5)
114+
return 1.0;
115+
116+
vec2 pos = vTexCoord * (1.0 - vTexCoord);
117+
float value = pos.x * pos.y * vstr;
118+
return min(pow(value, vpower), 1.0);
119+
}
120+
121+
vec3 apply_saturation(vec3 color)
122+
{
123+
float luminance = length(color) * 0.5775;
124+
vec3 weights = vec3(0.4, 0.5, 0.1);
125+
if (luminance < 0.5)
126+
weights = weights * weights + weights * weights;
127+
128+
luminance = dot(color, weights);
129+
return mix(vec3(luminance), color, sat);
130+
}
131+
132+
vec3 glow_sample(vec2 texcoord)
133+
{
134+
vec2 size = SourceSize.zw / quality;
135+
vec3 sum = vec3(0.0);
136+
float factor = 1.0 / glow;
137+
138+
for (float x = -glow; x <= glow; x += 1.0)
139+
{
140+
for (float y = -glow; y <= glow; y += 1.0)
141+
{
142+
vec3 color = sample_source(texcoord + vec2(x, y) * size) * factor;
143+
sum += color * color;
144+
}
145+
}
146+
147+
return glow_str * sum / (glow * glow);
148+
}
149+
150+
float noise(vec2 coord)
151+
{
152+
float timer = float(FrameCount) / 60.0;
153+
return fract(sin(timer * dot(coord.xy, vec2(12.9898, 78.233))) * 43758.5453);
154+
}
155+
156+
float corner_mask(vec2 coord)
157+
{
158+
coord = min(coord, vec2(1.0) - coord) * vec2(1.0, SourceSize.y / SourceSize.x);
159+
vec2 distance = vec2(corner) - min(coord, vec2(corner));
160+
return clamp((corner - sqrt(dot(distance, distance))) * smoothness, 0.0, 1.0);
161+
}
162+
163+
vec3 apply_color_temperature(vec3 color)
164+
{
165+
if (WP == 0.0)
166+
return color;
167+
168+
vec3 warmer = XYZ_to_D65 * (D50_to_XYZ * color);
169+
vec3 cooler = XYZ_to_D50 * (D65_to_XYZ * color);
170+
float amount = abs(WP) / 100.0;
171+
vec3 target = WP < 0.0 ? cooler : warmer;
172+
return mix(color, clamp(target, 0.0, 1.0), amount);
173+
}
174+
175+
void main()
176+
{
177+
vec2 pos = warp_position(vTexCoord.xy);
178+
if (outside_source(pos))
179+
{
180+
FragColor = vec4(BackgroundColor.rgb, 1.0);
181+
return;
182+
}
183+
184+
vec2 tex_size = SourceSize.xy;
185+
float interlace_downscale = Downscale > 0.0 ? Downscale : 2.0;
186+
vec2 pixel_phase = fract(pos * tex_size);
187+
if (inter < 0.5 && tex_size.y > 400.0)
188+
pixel_phase.y = fract(pos.y * tex_size.y / interlace_downscale);
189+
190+
vec2 texel = pos * tex_size;
191+
vec2 texel_floored = floor(texel);
192+
float region_range = 0.5 - 0.5 / PRE_SCALE;
193+
vec2 center_dist = pixel_phase - 0.5;
194+
vec2 filtered_phase = (center_dist - clamp(center_dist, -region_range, region_range)) * PRE_SCALE + 0.5;
195+
vec2 coords = (texel_floored + filtered_phase) / SourceSize.xy;
196+
197+
vec3 sample1 = sample_source(vec2(coords.x + blurx * SourceSize.z, coords.y - blury * SourceSize.w));
198+
vec3 sample2 = sample_source(coords);
199+
vec3 sample3 = sample_source(vec2(coords.x - blurx * SourceSize.z, coords.y + blury * SourceSize.w));
200+
201+
vec3 color = vec3(sample1.r * 0.5 + sample2.r * 0.5,
202+
sample1.g * 0.25 + sample2.g * 0.5 + sample3.g * 0.25,
203+
sample2.b * 0.5 + sample3.b * 0.5);
204+
205+
color = apply_color_temperature(color);
206+
color = 2.0 * pow(color, vec3(2.8)) - pow(color, vec3(3.6));
207+
208+
float luminance = color.r * 0.3 + color.g * 0.6 + color.b * 0.1;
209+
float scan_pos = fract(pixel_phase.y - 0.5);
210+
if (inter <= 0.5 || tex_size.y <= 400.0)
211+
color = color * scanline_weight(scan_pos, luminance) + color * scanline_weight(1.0 - scan_pos, luminance);
212+
213+
float masked_luminance = color.r * 0.3 + color.g * 0.6 + color.b * 0.1;
214+
color *= default_mask(vTexCoord * OutputSize.xy, masked_luminance);
215+
color *= mix(brightboost1, brightboost2, max(max(color.r, color.g), color.b));
216+
color = pow(max(color, vec3(0.0)), vec3(1.0 / GAMMA_OUT));
217+
218+
if (glow_str != 0.0)
219+
color += glow_sample(coords);
220+
if (sat != 1.0)
221+
color = apply_saturation(color);
222+
if (corner != 0.0)
223+
color = mix(BackgroundColor.rgb, color, corner_mask(pos + 0.5 / tex_size));
224+
if (nois != 0.0)
225+
color *= 1.0 + noise(coords * 2.0) / nois;
226+
227+
color *= mix(1.0, postbr, luminance);
228+
vec4 result = vec4(color, 1.0);
229+
if (contrast != 1.0)
230+
result = contrast_matrix() * result;
231+
if (inter > 0.5 && SourceSize.y > 400.0 && fract(float(FrameCount) / 2.0) < 0.5)
232+
result *= 0.95;
233+
234+
result.rgb *= vignette_amount();
235+
FragColor = result;
236+
}

0 commit comments

Comments
 (0)