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