crt-geom.fs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. #version 150
  2. //#define INTERLACED
  3. uniform sampler2D source[];
  4. uniform vec4 sourceSize[];
  5. uniform vec4 targetSize;
  6. uniform int phase;
  7. in Vertex {
  8. vec2 texCoord;
  9. };
  10. out vec4 fragColor;
  11. // Comment the next line to disable interpolation in linear gamma (and gain speed).
  12. //#define LINEAR_PROCESSING
  13. // Enable screen curvature.
  14. //#define CURVATURE
  15. // Enable 3x oversampling of the beam profile
  16. #define OVERSAMPLE
  17. // Use the older, purely gaussian beam profile
  18. //#define USEGAUSSIAN
  19. // vertex params //
  20. // gamma of simulated CRT
  21. #define CRTgamma 2.4
  22. // gamma of display monitor (typically 2.2 is correct)
  23. #define monitorgamma 2.2
  24. // overscan (e.g. 1.02 for 2% overscan)
  25. #define overscan vec2(1.0 , 1.0)//(0.93 , 0.915)
  26. // aspect ratio
  27. #define aspect vec2(1.0, 0.75)
  28. // lengths are measured in units of (approximately) the width of the monitor
  29. // simulated distance from viewer to monitor
  30. #define d 2.0
  31. // radius of curvature
  32. #define R 2.0
  33. // tilt angle in radians
  34. // (behavior might be a bit wrong if both components are nonzero)
  35. #define angle vec2(0.0,-0.0)
  36. // size of curved corners
  37. #define cornersize 0.02
  38. // border smoothness parameter
  39. // decrease if borders are too aliased
  40. #define cornersmooth 80.0
  41. #define sinangle sin(angle)
  42. #define cosangle cos(angle)
  43. #define stretch maxscale()
  44. #define ilfac vec2(1.0, floor(sourceSize[0].y / 200.0))
  45. #define one (ilfac / sourceSize[0].xy)
  46. #define mod_factor (texCoord.x * targetSize.x)
  47. // END of vertex params //
  48. // Macros.
  49. #define FIX(c) max(abs(c), 1e-5);
  50. #define PI 3.141592653589
  51. #ifdef LINEAR_PROCESSING
  52. # define TEX2D(c) pow(texture(source[0], (c)), vec4(CRTgamma))
  53. #else
  54. # define TEX2D(c) texture(source[0], (c))
  55. #endif
  56. #define FIX(c) max(abs(c), 1e-5);
  57. float intersect(vec2 xy)
  58. {
  59. float A = dot(xy,xy)+d*d;
  60. float B = 2.0*(R*(dot(xy,sinangle)-d*cosangle.x*cosangle.y)-d*d);
  61. float C = d*d + 2.0*R*d*cosangle.x*cosangle.y;
  62. return (-B-sqrt(B*B-4.0*A*C))/(2.0*A);
  63. }
  64. vec2 bkwtrans(vec2 xy)
  65. {
  66. float c = intersect(xy);
  67. vec2 point = vec2(c)*xy;
  68. point -= vec2(-R)*sinangle;
  69. point /= vec2(R);
  70. vec2 tang = sinangle/cosangle;
  71. vec2 poc = point/cosangle;
  72. float A = dot(tang,tang)+1.0;
  73. float B = -2.0*dot(poc,tang);
  74. float C = dot(poc,poc)-1.0;
  75. float a = (-B+sqrt(B*B-4.0*A*C))/(2.0*A);
  76. vec2 uv = (point-a*sinangle)/cosangle;
  77. float r = R*acos(a);
  78. return uv*r/sin(r/R);
  79. }
  80. vec2 fwtrans(vec2 uv)
  81. {
  82. float r = FIX(sqrt(dot(uv,uv)));
  83. uv *= sin(r/R)/r;
  84. float x = 1.0-cos(r/R);
  85. float D = d/R + x*cosangle.x*cosangle.y+dot(uv,sinangle);
  86. return d*(uv*cosangle-x*sinangle)/D;
  87. }
  88. vec3 maxscale()
  89. {
  90. vec2 c = bkwtrans(-R * sinangle / (1.0 + R/d*cosangle.x*cosangle.y));
  91. vec2 a = vec2(0.5,0.5)*aspect;
  92. vec2 lo = vec2(fwtrans(vec2(-a.x,c.y)).x,
  93. fwtrans(vec2(c.x,-a.y)).y)/aspect;
  94. vec2 hi = vec2(fwtrans(vec2(+a.x,c.y)).x,
  95. fwtrans(vec2(c.x,+a.y)).y)/aspect;
  96. return vec3((hi+lo)*aspect*0.5,max(hi.x-lo.x,hi.y-lo.y));
  97. }
  98. vec2 transform(vec2 coord)
  99. {
  100. coord = (coord-vec2(0.5))*aspect*stretch.z+stretch.xy;
  101. return (bkwtrans(coord)/overscan/aspect+vec2(0.5));
  102. }
  103. float corner(vec2 coord)
  104. {
  105. coord = (coord - vec2(0.5)) * overscan + vec2(0.5);
  106. coord = min(coord, vec2(1.0)-coord) * aspect;
  107. vec2 cdist = vec2(cornersize);
  108. coord = (cdist - min(coord,cdist));
  109. float dist = sqrt(dot(coord,coord));
  110. return clamp((cdist.x-dist)*cornersmooth,0.0, 1.0);
  111. }
  112. // Calculate the influence of a scanline on the current pixel.
  113. //
  114. // 'distance' is the distance in texture coordinates from the current
  115. // pixel to the scanline in question.
  116. // 'color' is the colour of the scanline at the horizontal location of
  117. // the current pixel.
  118. vec4 scanlineWeights(float distance, vec4 color)
  119. {
  120. // "wid" controls the width of the scanline beam, for each RGB channel
  121. // The "weights" lines basically specify the formula that gives
  122. // you the profile of the beam, i.e. the intensity as
  123. // a function of distance from the vertical center of the
  124. // scanline. In this case, it is gaussian if width=2, and
  125. // becomes nongaussian for larger widths. Ideally this should
  126. // be normalized so that the integral across the beam is
  127. // independent of its width. That is, for a narrower beam
  128. // "weights" should have a higher peak at the center of the
  129. // scanline than for a wider beam.
  130. #ifdef USEGAUSSIAN
  131. vec4 wid = 0.3 + 0.1 * pow(color, vec4(3.0));
  132. vec4 weights = vec4(distance / wid);
  133. return 0.4 * exp(-weights * weights) / wid;
  134. #else
  135. vec4 wid = 2.0 + 2.0 * pow(color, vec4(4.0));
  136. vec4 weights = vec4(distance / 0.3);
  137. return 1.4 * exp(-pow(weights * inversesqrt(0.5 * wid), wid)) / (0.6 + 0.2 * wid);
  138. #endif
  139. }
  140. void main()
  141. {
  142. // Here's a helpful diagram to keep in mind while trying to
  143. // understand the code:
  144. //
  145. // | | | | |
  146. // -------------------------------
  147. // | | | | |
  148. // | 01 | 11 | 21 | 31 | <-- current scanline
  149. // | | @ | | |
  150. // -------------------------------
  151. // | | | | |
  152. // | 02 | 12 | 22 | 32 | <-- next scanline
  153. // | | | | |
  154. // -------------------------------
  155. // | | | | |
  156. //
  157. // Each character-cell represents a pixel on the output
  158. // surface, "@" represents the current pixel (always somewhere
  159. // in the bottom half of the current scan-line, or the top-half
  160. // of the next scanline). The grid of lines represents the
  161. // edges of the texels of the underlying texture.
  162. // Texture coordinates of the texel containing the active pixel.
  163. #ifdef CURVATURE
  164. vec2 xy = transform(texCoord);
  165. #else
  166. vec2 xy = texCoord;
  167. #endif
  168. float cval = corner(xy);
  169. // Of all the pixels that are mapped onto the texel we are
  170. // currently rendering, which pixel are we currently rendering?
  171. #ifdef INTERLACED
  172. vec2 ilvec = vec2(0.0,ilfac.y > 1.5 ? mod(float(phase),2.0) : 0.0);
  173. #else
  174. vec2 ilvec = vec2(0.0,ilfac.y);
  175. #endif
  176. vec2 ratio_scale = (xy * sourceSize[0].xy - vec2(0.5) + ilvec)/ilfac;
  177. #ifdef OVERSAMPLE
  178. float filter_ = sourceSize[0].y / targetSize.y;
  179. #endif
  180. vec2 uv_ratio = fract(ratio_scale);
  181. // Snap to the center of the underlying texel.
  182. xy = (floor(ratio_scale)*ilfac + vec2(0.5) - ilvec) / sourceSize[0].xy;
  183. // Calculate Lanczos scaling coefficients describing the effect
  184. // of various neighbour texels in a scanline on the current
  185. // pixel.
  186. vec4 coeffs = PI * vec4(1.0 + uv_ratio.x, uv_ratio.x, 1.0 - uv_ratio.x, 2.0 - uv_ratio.x);
  187. // Prevent division by zero.
  188. coeffs = FIX(coeffs);
  189. // Lanczos2 kernel.
  190. coeffs = 2.0 * sin(coeffs) * sin(coeffs / 2.0) / (coeffs * coeffs);
  191. // Normalize.
  192. coeffs /= dot(coeffs, vec4(1.0));
  193. // Calculate the effective colour of the current and next
  194. // scanlines at the horizontal location of the current pixel,
  195. // using the Lanczos coefficients above.
  196. vec4 col = clamp(mat4(
  197. TEX2D(xy + vec2(-one.x, 0.0)),
  198. TEX2D(xy),
  199. TEX2D(xy + vec2(one.x, 0.0)),
  200. TEX2D(xy + vec2(2.0 * one.x, 0.0))) * coeffs,
  201. 0.0, 1.0);
  202. vec4 col2 = clamp(mat4(
  203. TEX2D(xy + vec2(-one.x, one.y)),
  204. TEX2D(xy + vec2(0.0, one.y)),
  205. TEX2D(xy + one),
  206. TEX2D(xy + vec2(2.0 * one.x, one.y))) * coeffs,
  207. 0.0, 1.0);
  208. #ifndef LINEAR_PROCESSING
  209. col = pow(col , vec4(CRTgamma));
  210. col2 = pow(col2, vec4(CRTgamma));
  211. #endif
  212. // Calculate the influence of the current and next scanlines on
  213. // the current pixel.
  214. vec4 weights = scanlineWeights(uv_ratio.y, col);
  215. vec4 weights2 = scanlineWeights(1.0 - uv_ratio.y, col2);
  216. #ifdef OVERSAMPLE
  217. uv_ratio.y =uv_ratio.y+1.0/3.0*filter_;
  218. weights = (weights+scanlineWeights(uv_ratio.y, col))/3.0;
  219. weights2=(weights2+scanlineWeights(abs(1.0-uv_ratio.y), col2))/3.0;
  220. uv_ratio.y =uv_ratio.y-2.0/3.0*filter_;
  221. weights=weights+scanlineWeights(abs(uv_ratio.y), col)/3.0;
  222. weights2=weights2+scanlineWeights(abs(1.0-uv_ratio.y), col2)/3.0;
  223. #endif
  224. vec3 mul_res = (col * weights + col2 * weights2).rgb * vec3(cval);
  225. // dot-mask emulation:
  226. // Output pixels are alternately tinted green and magenta.
  227. vec3 dotMaskWeights = mix(
  228. vec3(1.0, 0.7, 1.0),
  229. vec3(0.7, 1.0, 0.7),
  230. floor(mod(mod_factor, 2.0))
  231. );
  232. mul_res *= dotMaskWeights;
  233. // Convert the image gamma for display on our output device.
  234. mul_res = pow(mul_res, vec3(1.0 / monitorgamma));
  235. // Color the texel.
  236. fragColor = vec4(mul_res, 1.0);
  237. }