CRTLottes2.fx (11628B)
1 #include "ReShade.fxh" 2 3 // 4 // PUBLIC DOMAIN CRT STYLED SCAN-LINE SHADER 5 // 6 // by Timothy Lottes 7 // 8 // This is more along the style of a really good CGA arcade monitor. 9 // With RGB inputs instead of NTSC. 10 // The shadow mask example has the mask rotated 90 degrees for less chromatic aberration. 11 // 12 // Left it unoptimized to show the theory behind the algorithm. 13 // 14 // It is an example what I personally would want as a display option for pixel art games. 15 // Please take and use, change, or whatever. 16 // 17 18 #ifndef CRTS_DEBUG 19 #define CRTS_DEBUG 0 20 #endif 21 22 #ifndef CRTS_2_TAP 23 #define CRTS_2_TAP 0 24 #endif 25 26 uniform bool CRTS_WARP < 27 ui_type = "boolean"; 28 ui_label = "Enable Warping [CRT Lottes 2.0]"; 29 > = true; 30 31 uniform float CRTS_WARP_X < 32 ui_type = "drag"; 33 ui_min = 0.0; 34 ui_max = 512.0; 35 ui_label = "CRT Warping X [CRT Lottes 2.0]"; 36 > = 64.0; 37 38 uniform float CRTS_WARP_Y < 39 ui_type = "drag"; 40 ui_min = 0.0; 41 ui_max = 512.0; 42 ui_label = "CRT Warping Y [CRT Lottes 2.0]"; 43 > = 48.0; 44 45 uniform bool CRTS_TONE < 46 ui_type = "boolean"; 47 ui_label = "Enable CRT Tonemapping [CRT Lottes 2.0]"; 48 > = true; 49 50 uniform bool CRTS_CONTRAST < 51 ui_type = "boolean"; 52 ui_label = "Enable CRT Contrast [CRT Lottes 2.0]"; 53 > = false; 54 55 uniform bool CRTS_SATURATION < 56 ui_type = "boolean"; 57 ui_label = "Enable CRT Saturation [CRT Lottes 2.0]"; 58 > = false; 59 60 uniform int CRTS_MASK_TYPE < 61 ui_type = "combo"; 62 ui_items = "None\0Aperture Grille\0Aperture Grille (Lite)\0Shadow Mask\0"; 63 ui_label = "Mask Type [CRT Lottes 2.0]"; 64 > = 2; 65 66 //-------------------------------------------------------------- 67 // Scanline thinness 68 // 0.50 = fused scanlines 69 // 0.70 = recommended default 70 // 1.00 = thinner scanlines (too thin) 71 72 uniform float INPUT_THIN < 73 ui_type = "drag"; 74 ui_min = 0.5; 75 ui_max = 1.0; 76 ui_label = "Scanlines Thinnes [CRT Lottes 2.0]"; 77 > = 0.70; 78 79 //-------------------------------------------------------------- 80 // Horizonal scan blur 81 // -3.0 = pixely 82 // -2.5 = default 83 // -2.0 = smooth 84 // -1.0 = too blurry 85 86 uniform float INPUT_BLUR < 87 ui_type = "drag"; 88 ui_min = -3.0; 89 ui_max = 0.0; 90 ui_label = "Horizontal Scan Blur [CRT Lottes 2.0]"; 91 > = -2.5; 92 93 //-------------------------------------------------------------- 94 // Shadow mask effect, ranges from, 95 // 0.25 = large amount of mask (not recommended, too dark) 96 // 0.50 = recommended default 97 // 1.00 = no shadow mask 98 99 uniform float INPUT_MASK < 100 ui_type = "drag"; 101 ui_min = 0.0; 102 ui_max = 1.0; 103 ui_label = "Shadow Mask Intensity [CRT Lottes 2.0]"; 104 > = 0.5; 105 106 //-------------------------------------------------------------- 107 108 uniform int INPUT_X < 109 ui_type = "drag"; 110 ui_min = 1; 111 ui_max = BUFFER_WIDTH; 112 ui_label = "Resolution Width [CRT Lottes 2.0]"; 113 > = 640; 114 115 uniform int INPUT_Y < 116 ui_type = "drag"; 117 ui_min = 1; 118 ui_max = BUFFER_HEIGHT; 119 ui_label = "Resolution Height [CRT Lottes 2.0]"; 120 > = 480; 121 122 //-------------------------------------------------------------- 123 // Setup the function which returns input image color 124 125 void ToLinear(inout float3 color) 126 { 127 float3 c1 = color.rgb / 12.92; 128 float3 c2 = pow((color.rgb + 0.055)/1.055, 2.4); 129 130 color.r = (color.r <= 0.04045) ? c1.r : c2.r; 131 color.g = (color.g <= 0.04045) ? c1.g : c2.g; 132 color.b = (color.b <= 0.04045) ? c1.b : c2.b; 133 } 134 135 void ToSRGB(inout float3 color) 136 { 137 float3 c1 = color.rgb * 12.92; 138 float3 c2 = 1.055 * pow(color.rgb, 0.4166) - 0.055; 139 140 color.r = (color.r < 0.0031308) ? c1.r : c2.r; 141 color.g = (color.g < 0.0031308) ? c1.g : c2.g; 142 color.b = (color.b < 0.0031308) ? c1.b : c2.b; 143 } 144 145 float3 CrtsFetch(float2 uv) 146 { 147 float3 color = tex2D(ReShade::BackBuffer, uv).rgb; 148 ToLinear(color); 149 return color; 150 } 151 152 float4 CrtsTone( 153 float contrast, 154 float saturation, 155 float thin, 156 float mask) 157 { 158 159 mask = INPUT_MASK; 160 161 if (CRTS_MASK_TYPE <= 0){ 162 mask=1.0; 163 } 164 165 166 if(CRTS_MASK_TYPE == 2){ 167 mask=0.5+INPUT_MASK*0.5; 168 } 169 170 float4 ret; 171 float midOut=0.18/((1.5-thin)*(0.5*mask+0.5)); 172 float pMidIn=pow(0.18,contrast); 173 ret.x=contrast; 174 ret.y=((-pMidIn)+midOut)/((1.0-pMidIn)*midOut); 175 ret.z=((-pMidIn)*midOut+pMidIn)/(midOut*(-pMidIn)+midOut); 176 ret.w=contrast+saturation; 177 return ret; 178 } 179 180 float3 CrtsMask(float2 pos,float dark) 181 { 182 183 if (CRTS_MASK_TYPE == 1){ 184 float3 m=dark; 185 float x=frac(pos.x*(1.0/3.0)); 186 if(x<(1.0/3.0))m.r=1.0; 187 else if(x<(2.0/3.0))m.g=1.0; 188 else m.b=1.0; 189 return m; 190 } else if (CRTS_MASK_TYPE == 2){ 191 float3 m=1.0; 192 float x=frac(pos.x*(1.0/3.0)); 193 if(x<(1.0/3.0))m.r=dark; 194 else if(x<(2.0/3.0))m.g=dark; 195 else m.b=dark; 196 return m; 197 } else if(CRTS_MASK_TYPE <= 0){ 198 return 1.0; 199 } else if(CRTS_MASK_TYPE >= 3){ 200 pos.x+=pos.y*3.0; 201 float3 m=dark; 202 float x=frac(pos.x*(1.0/6.0)); 203 if(x<(1.0/3.0))m.r=1.0; 204 else if(x<(2.0/3.0))m.g=1.0; 205 else m.b=1.0; 206 return m; 207 } else { 208 return 0.0; 209 } 210 } 211 212 float3 CrtsFilter( 213 //-------------------------------------------------------------- 214 // SV_POSITION, fragCoord.xy 215 float2 ipos, 216 //-------------------------------------------------------------- 217 // inputSize / outputSize (in pixels) 218 float2 inputSizeDivOutputSize, 219 //-------------------------------------------------------------- 220 // 0.5 * inputSize (in pixels) 221 float2 halfInputSize, 222 //-------------------------------------------------------------- 223 // 1.0 / inputSize (in pixels) 224 float2 rcpInputSize, 225 //-------------------------------------------------------------- 226 // 1.0 / outputSize (in pixels) 227 float2 rcpOutputSize, 228 //-------------------------------------------------------------- 229 // 2.0 / outputSize (in pixels) 230 float2 twoDivOutputSize, 231 //-------------------------------------------------------------- 232 // inputSize.y 233 float inputHeight, 234 //-------------------------------------------------------------- 235 // Warp scanlines but not phosphor mask 236 // 0.0 = no warp 237 // 1.0/64.0 = light warping 238 // 1.0/32.0 = more warping 239 // Want x and y warping to be different (based on aspect) 240 float2 warp, 241 //-------------------------------------------------------------- 242 // Scanline thinness 243 // 0.50 = fused scanlines 244 // 0.70 = recommended default 245 // 1.00 = thinner scanlines (too thin) 246 // Shared with CrtsTone() function 247 float thin, 248 //-------------------------------------------------------------- 249 // Horizonal scan blur 250 // -3.0 = pixely 251 // -2.5 = default 252 // -2.0 = smooth 253 // -1.0 = too blurry 254 float blur, 255 //-------------------------------------------------------------- 256 // Shadow mask effect, ranges from, 257 // 0.25 = large amount of mask (not recommended, too dark) 258 // 0.50 = recommended default 259 // 1.00 = no shadow mask 260 // Shared with CrtsTone() function 261 float mask, 262 //-------------------------------------------------------------- 263 // Tonal curve parameters generated by CrtsTone() 264 float4 tone 265 //-------------------------------------------------------------- 266 ){ 267 //-------------------------------------------------------------- 268 #if (CRTS_DEBUG == 1) 269 float2 uv=ipos*rcpOutputSize; 270 // Show second half processed, and first half un-processed 271 if(uv.x<0.5) 272 { 273 // Force nearest to get squares 274 uv*=1.0/rcpInputSize; 275 uv=floor(uv)+float2(0.5,0.5); 276 uv*=rcpInputSize; 277 float3 color=CrtsFetch(uv); 278 return color; 279 } 280 #endif 281 282 float2 pos; 283 float vin; 284 285 if (CRTS_WARP){ 286 // Convert to {-1 to 1} range 287 pos=ipos*twoDivOutputSize-float2(1.0,1.0); 288 // Distort pushes image outside {-1 to 1} range 289 pos*=float2(1.0+(pos.y*pos.y)*warp.x,1.0+(pos.x*pos.x)*warp.y); 290 // TODO: Vignette needs optimization 291 vin=1.0-((1.0-saturate(pos.x*pos.x))*(1.0-saturate(pos.y*pos.y))); 292 vin=saturate((-vin)*inputHeight+inputHeight); 293 // Leave in {0 to inputSize} 294 pos=pos*halfInputSize+halfInputSize; 295 } else { 296 pos=ipos*inputSizeDivOutputSize; 297 } 298 299 // Snap to center of first scanline 300 float y0=floor(pos.y-0.5)+0.5; 301 302 #if (CRTS_2_TAP == 1) 303 // Using Inigo's "Improved Texture Interpolation" 304 // http://iquilezles.org/www/articles/texture/texture.htm 305 pos.x+=0.5; 306 float xi=floor(pos.x); 307 float xf=pos.x-xi; 308 xf=xf*xf*xf*(xf*(xf*6.0-15.0)+10.0); 309 float x0=xi+xf-0.5; 310 float2 p=float2(x0*rcpInputSize.x,y0*rcpInputSize.y); 311 // Coordinate adjusted bilinear fetch from 2 nearest scanlines 312 float3 colA=CrtsFetch(p); 313 p.y+=rcpInputSize.y; 314 float3 colB=CrtsFetch(p); 315 #else 316 // Snap to center of one of four pixels 317 float x0=floor(pos.x-1.5)+0.5; 318 // Inital UV position 319 float2 p=float2(x0*rcpInputSize.x,y0*rcpInputSize.y); 320 // Fetch 4 nearest texels from 2 nearest scanlines 321 float3 colA0=CrtsFetch(p); 322 p.x+=rcpInputSize.x; 323 float3 colA1=CrtsFetch(p); 324 p.x+=rcpInputSize.x; 325 float3 colA2=CrtsFetch(p); 326 p.x+=rcpInputSize.x; 327 float3 colA3=CrtsFetch(p); 328 p.y+=rcpInputSize.y; 329 float3 colB3=CrtsFetch(p); 330 p.x-=rcpInputSize.x; 331 float3 colB2=CrtsFetch(p); 332 p.x-=rcpInputSize.x; 333 float3 colB1=CrtsFetch(p); 334 p.x-=rcpInputSize.x; 335 float3 colB0=CrtsFetch(p); 336 #endif 337 338 // Vertical filter 339 // Scanline intensity is using sine wave 340 // Easy filter window and integral used later in exposure 341 float off=pos.y-y0; 342 float pi2=6.28318530717958; 343 float hlf=0.5; 344 float scanA=cos(min(0.5, off *thin )*pi2)*hlf+hlf; 345 float scanB=cos(min(0.5,(-off)*thin+thin)*pi2)*hlf+hlf; 346 347 #if (CRTS_2_TAP == 1) 348 if (CRTS_WARP){ 349 // Get rid of wrong pixels on edge 350 scanA*=vin; 351 scanB*=vin; 352 } 353 // Apply vertical filter 354 float3 color=(colA*scanA)+(colB*scanB); 355 #else 356 // Horizontal kernel is simple gaussian filter 357 float off0=pos.x-x0; 358 float off1=off0-1.0; 359 float off2=off0-2.0; 360 float off3=off0-3.0; 361 float pix0=exp2(blur*off0*off0); 362 float pix1=exp2(blur*off1*off1); 363 float pix2=exp2(blur*off2*off2); 364 float pix3=exp2(blur*off3*off3); 365 float pixT=rcp(pix0+pix1+pix2+pix3); 366 367 if (CRTS_WARP){ 368 // Get rid of wrong pixels on edge 369 pixT*=vin; 370 } 371 scanA*=pixT; 372 scanB*=pixT; 373 // Apply horizontal and vertical filters 374 float3 color= 375 (colA0*pix0+colA1*pix1+colA2*pix2+colA3*pix3)*scanA + 376 (colB0*pix0+colB1*pix1+colB2*pix2+colB3*pix3)*scanB; 377 #endif 378 379 // Apply phosphor mask 380 color*=CrtsMask(ipos,mask); 381 // Optional color processing 382 if (CRTS_TONE){ 383 // Tonal control, start by protecting from /0 384 float peak=max(1.0/(256.0*65536.0),max(color.r,max(color.g,color.b))); 385 // Compute the ratios of {R,G,B} 386 float3 ratio=color*rcp(peak); 387 // Apply tonal curve to peak value 388 if (CRTS_CONTRAST){ 389 peak=pow(peak,tone.x); 390 } 391 peak=peak*rcp(peak*tone.y+tone.z); 392 // Apply saturation 393 if (CRTS_SATURATION){ 394 ratio=pow(ratio,float3(tone.w,tone.w,tone.w)); 395 } 396 // Reconstruct color 397 return ratio*peak; 398 } else { 399 return color; 400 } 401 } 402 403 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 404 // 405 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 406 407 408 409 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 410 // 411 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 412 413 void PS_CRTLottes2018(float4 vpos : SV_Position, float2 texcoord : TEXCOORD, out float4 color : SV_Target0) 414 { 415 color = tex2D(ReShade::BackBuffer, texcoord.xy); 416 417 color.rgb=CrtsFilter( 418 vpos.xy, 419 float2(INPUT_X,INPUT_Y)/ReShade::ScreenSize.xy, 420 float2(INPUT_X,INPUT_Y)*0.5, 421 1.0/float2(INPUT_X,INPUT_Y), 422 1.0/ReShade::ScreenSize.xy, 423 2.0/ReShade::ScreenSize.xy, 424 INPUT_Y, 425 float2(1.0/CRTS_WARP_X,1.0/CRTS_WARP_Y), 426 INPUT_THIN, 427 INPUT_BLUR, 428 INPUT_MASK, 429 CrtsTone(1.0,0.0,INPUT_THIN,INPUT_MASK)); 430 431 ToSRGB(color.rgb); 432 } 433 434 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 435 // 436 //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 437 438 439 technique CRTLottes2018 440 { 441 pass 442 { 443 VertexShader = PostProcessVS; 444 PixelShader = PS_CRTLottes2018; 445 } 446 }