1/*
2    CRT shader
3
4    Copyright (C) 2010, 2011 cgwg, Themaister and DOLLS
5
6    This program is free software; you can redistribute it and/or modify it
7    under the terms of the GNU General Public License as published by the Free
8    Software Foundation; either version 2 of the License, or (at your option)
9    any later version.
10
11    (cgwg gave their consent to have the original version of this shader
12    distributed under the GPL in this message:
13
14        http://board.byuu.org/viewtopic.php?p=26075#p26075
15
16        "Feel free to distribute my shaders under the GPL. After all, the
17        barrel distortion code was taken from the Curvature shader, which is
18        under the GPL."
19    )
20
21    modified by slime73 for use with love2d and mari0
22*/
23
24extern vec2 inputSize;
25extern vec2 outputSize;
26extern vec2 textureSize;
27
28
29#define SCANLINES
30
31// Comment the next line to disable interpolation in linear gamma (and gain speed).
32#define LINEAR_PROCESSING
33
34// Compensate for 16-235 level range as per Rec. 601.
35#define REF_LEVELS
36
37// Enable screen curvature.
38#define CURVATURE
39
40// Controls the intensity of the barrel distortion used to emulate the
41// curvature of a CRT. 0.0 is perfectly flat, 1.0 is annoyingly
42// distorted, higher values are increasingly ridiculous.
43#define distortion 0.2
44
45// Simulate a CRT gamma of 2.4.
46#define inputGamma  2.4
47
48// Compensate for the standard sRGB gamma of 2.2.
49#define outputGamma 2.2
50
51// Macros.
52#define FIX(c) max(abs(c), 1e-5);
53#define PI 3.141592653589
54
55#ifdef REF_LEVELS
56# 	define LEVELS(c) max((c - 16.0 / 255.0) * 255.0 / (235.0 - 16.0), 0.0)
57#else
58#	define LEVELS(c) c
59#endif
60
61#ifdef LINEAR_PROCESSING
62#	define TEX2D(c) pow(LEVELS(checkTexelBounds(_tex0_, (c))), vec4(inputGamma))
63#else
64#	define TEX2D(c) LEVELS(checkTexelBounds(_tex0_, (c)))
65#endif
66
67
68vec2 bounds = vec2(inputSize.x / textureSize.x, 1.0 - inputSize.y / textureSize.y);
69
70
71vec2 radialDistortion(vec2 coord, const vec2 ratio)
72{
73	float offsety = 1.0 - ratio.y;
74	coord.y -= offsety;
75	coord /= ratio;
76
77	vec2 cc = coord - 0.5;
78	float dist = dot(cc, cc) * distortion;
79	vec2 result = coord + cc * (1.0 + dist) * dist;
80
81	result *= ratio;
82	result.y += offsety;
83
84	return result;
85}
86
87#ifdef CURVATURE
88vec4 checkTexelBounds(Image texture, vec2 coords)
89{
90	vec2 ss = step(coords, vec2(bounds.x, 1.0)) * step(vec2(0.0, bounds.y), coords);
91
92	return Texel(texture, coords) * ss.x * ss.y;
93	// return texcolor;
94}
95#else
96vec4 checkTexelBounds(Image texture, vec2 coords)
97{
98	return Texel(texture, coords);
99}
100#endif
101
102
103// Calculate the influence of a scanline on the current pixel.
104//
105// 'distance' is the distance in texture coordinates from the current
106// pixel to the scanline in question.
107// 'color' is the colour of the scanline at the horizontal location of
108// the current pixel.
109vec4 scanlineWeights(float distance, vec4 color)
110{
111	// The "width" of the scanline beam is set as 2*(1 + x^4) for
112	// each RGB channel.
113	vec4 wid = 2.0 + 2.0 * pow(color, vec4(4.0));
114
115	// The "weights" lines basically specify the formula that gives
116	// you the profile of the beam, i.e. the intensity as
117	// a function of distance from the vertical center of the
118	// scanline. In this case, it is gaussian if width=2, and
119	// becomes nongaussian for larger widths. Ideally this should
120	// be normalized so that the integral across the beam is
121	// independent of its width. That is, for a narrower beam
122	// "weights" should have a higher peak at the center of the
123	// scanline than for a wider beam.
124	vec4 weights = vec4(distance / 0.3);
125	return 1.4 * exp(-pow(weights * inversesqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
126}
127
128vec4 effect(vec4 vcolor, Image texture, vec2 texCoord, vec2 pixel_coords)
129{
130	vec2 one = 1.0 / textureSize;
131	float mod_factor = texCoord.x * textureSize.x * outputSize.x / inputSize.x;
132
133	// Here's a helpful diagram to keep in mind while trying to
134	// understand the code:
135	//
136	//  |      |      |      |      |
137	// -------------------------------
138	//  |      |      |      |      |
139	//  |  01  |  11  |  21  |  31  | <-- current scanline
140	//  |      | @    |      |      |
141	// -------------------------------
142	//  |      |      |      |      |
143	//  |  02  |  12  |  22  |  32  | <-- next scanline
144	//  |      |      |      |      |
145	// -------------------------------
146	//  |      |      |      |      |
147	//
148	// Each character-cell represents a pixel on the output
149	// surface, "@" represents the current pixel (always somewhere
150	// in the bottom half of the current scan-line, or the top-half
151	// of the next scanline). The grid of lines represents the
152	// edges of the texels of the underlying texture.
153
154	// Texture coordinates of the texel containing the active pixel.
155#ifdef CURVATURE
156	vec2 xy = radialDistortion(texCoord, inputSize / textureSize);
157#else
158	vec2 xy = texCoord;
159#endif
160
161#ifdef SCANLINES
162
163	// Of all the pixels that are mapped onto the texel we are
164	// currently rendering, which pixel are we currently rendering?
165	vec2 ratio_scale = xy * textureSize - 0.5;
166	vec2 uv_ratio = fract(ratio_scale);
167
168	// Snap to the center of the underlying texel.
169	xy = (floor(ratio_scale) + 0.5) / textureSize;
170
171	// Calculate Lanczos scaling coefficients describing the effect
172	// of various neighbour texels in a scanline on the current
173	// pixel.
174	vec4 coeffs = PI * vec4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);
175
176	// Prevent division by zero.
177	coeffs = FIX(coeffs);
178
179	// Lanczos2 kernel.
180	coeffs = 2.0 * sin(coeffs) * sin(coeffs / 2.0) / (coeffs * coeffs);
181
182	// Normalize.
183	coeffs /= dot(coeffs, vec4(1.0));
184
185	// Calculate the effective colour of the current and next
186	// scanlines at the horizontal location of the current pixel,
187	// using the Lanczos coefficients above.
188	vec4 col  = clamp(mat4(
189		TEX2D(xy + vec2(-one.x, 0.0)),
190		TEX2D(xy),
191		TEX2D(xy + vec2(one.x, 0.0)),
192		TEX2D(xy + vec2(2.0 * one.x, 0.0))) * coeffs,
193		0.0, 1.0);
194	vec4 col2 = clamp(mat4(
195		TEX2D(xy + vec2(-one.x, one.y)),
196		TEX2D(xy + vec2(0.0, one.y)),
197		TEX2D(xy + one),
198		TEX2D(xy + vec2(2.0 * one.x, one.y))) * coeffs,
199		0.0, 1.0);
200
201#ifndef LINEAR_PROCESSING
202        col  = pow(col , vec4(inputGamma));
203        col2 = pow(col2, vec4(inputGamma));
204#endif
205
206	// Calculate the influence of the current and next scanlines on
207	// the current pixel.
208	vec4 weights  = scanlineWeights(uv_ratio.y, col);
209	vec4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
210
211	vec4 mul_res_f = (col * weights + col2 * weights2);
212	vec3 mul_res = mul_res_f.rgb;
213
214
215#else
216
217	vec4 mul_res_f = TEX2D(xy);
218	vec3 mul_res = mul_res_f.rgb;
219
220#endif
221
222
223	// dot-mask emulation:
224	// Output pixels are alternately tinted green and magenta.
225	vec3 dotMaskWeights = mix(
226		vec3(1.0, 0.7, 1.0),
227		vec3(0.7, 1.0, 0.7),
228		floor(mod(mod_factor, 2.0))
229	);
230
231	mul_res *= dotMaskWeights;
232
233	// Convert the image gamma for display on our output device.
234	mul_res = pow(mul_res, vec3(1.0 / outputGamma));
235
236	// Color the texel.
237	return vec4(mul_res * 1.0, 1.0);
238}
239
240