mirror of
				https://github.com/RetroDECK/Duckstation.git
				synced 2025-04-10 19:15:14 +00:00 
			
		
		
		
	
		
			
	
	
		
			446 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			HLSL
		
	
	
	
	
	
		
		
			
		
	
	
			446 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			HLSL
		
	
	
	
	
	
|   | #include "ReShade.fxh" | ||
|  | 
 | ||
|  | // | ||
|  | // PUBLIC DOMAIN CRT STYLED SCAN-LINE SHADER | ||
|  | // | ||
|  | //   by Timothy Lottes | ||
|  | // | ||
|  | // This is more along the style of a really good CGA arcade monitor. | ||
|  | // With RGB inputs instead of NTSC. | ||
|  | // The shadow mask example has the mask rotated 90 degrees for less chromatic aberration. | ||
|  | // | ||
|  | // Left it unoptimized to show the theory behind the algorithm. | ||
|  | // | ||
|  | // It is an example what I personally would want as a display option for pixel art games. | ||
|  | // Please take and use, change, or whatever. | ||
|  | // | ||
|  | 
 | ||
|  | #ifndef CRTS_DEBUG | ||
|  | 	#define CRTS_DEBUG	0 | ||
|  | #endif | ||
|  | 
 | ||
|  | #ifndef CRTS_2_TAP | ||
|  | 	#define CRTS_2_TAP	0 | ||
|  | #endif | ||
|  | 
 | ||
|  | uniform bool CRTS_WARP < | ||
|  | 	ui_type = "boolean"; | ||
|  | 	ui_label = "Enable Warping [CRT Lottes 2.0]"; | ||
|  | > = true; | ||
|  | 
 | ||
|  | uniform float CRTS_WARP_X < | ||
|  | 	ui_type = "drag"; | ||
|  | 	ui_min = 0.0; | ||
|  | 	ui_max = 512.0; | ||
|  | 	ui_label = "CRT Warping X [CRT Lottes 2.0]"; | ||
|  | > = 64.0; | ||
|  | 
 | ||
|  | uniform float CRTS_WARP_Y < | ||
|  | 	ui_type = "drag"; | ||
|  | 	ui_min = 0.0; | ||
|  | 	ui_max = 512.0; | ||
|  | 	ui_label = "CRT Warping Y [CRT Lottes 2.0]"; | ||
|  | > = 48.0; | ||
|  | 
 | ||
|  | uniform bool CRTS_TONE < | ||
|  | 	ui_type = "boolean"; | ||
|  | 	ui_label = "Enable CRT Tonemapping [CRT Lottes 2.0]"; | ||
|  | > = true; | ||
|  | 
 | ||
|  | uniform bool CRTS_CONTRAST < | ||
|  | 	ui_type = "boolean"; | ||
|  | 	ui_label = "Enable CRT Contrast [CRT Lottes 2.0]"; | ||
|  | > = false; | ||
|  | 
 | ||
|  | uniform bool CRTS_SATURATION < | ||
|  | 	ui_type = "boolean"; | ||
|  | 	ui_label = "Enable CRT Saturation [CRT Lottes 2.0]"; | ||
|  | > = false; | ||
|  | 
 | ||
|  | uniform int CRTS_MASK_TYPE < | ||
|  | 	ui_type = "combo"; | ||
|  | 	ui_items = "None\0Aperture Grille\0Aperture Grille (Lite)\0Shadow Mask\0"; | ||
|  | 	ui_label = "Mask Type [CRT Lottes 2.0]"; | ||
|  | > = 2; | ||
|  | 
 | ||
|  | //-------------------------------------------------------------- | ||
|  | // Scanline thinness | ||
|  | //  0.50 = fused scanlines | ||
|  | //  0.70 = recommended default | ||
|  | //  1.00 = thinner scanlines (too thin) | ||
|  | 
 | ||
|  | uniform float INPUT_THIN < | ||
|  | 	ui_type = "drag"; | ||
|  | 	ui_min = 0.5; | ||
|  | 	ui_max = 1.0; | ||
|  | 	ui_label = "Scanlines Thinnes [CRT Lottes 2.0]"; | ||
|  | > = 0.70; | ||
|  | 
 | ||
|  | //-------------------------------------------------------------- | ||
|  | // Horizonal scan blur | ||
|  | //  -3.0 = pixely | ||
|  | //  -2.5 = default | ||
|  | //  -2.0 = smooth | ||
|  | //  -1.0 = too blurry | ||
|  | 
 | ||
|  | uniform float INPUT_BLUR < | ||
|  | 	ui_type = "drag"; | ||
|  | 	ui_min = -3.0; | ||
|  | 	ui_max = 0.0; | ||
|  | 	ui_label = "Horizontal Scan Blur [CRT Lottes 2.0]"; | ||
|  | > = -2.5; | ||
|  | 
 | ||
|  | //-------------------------------------------------------------- | ||
|  | // Shadow mask effect, ranges from, | ||
|  | //  0.25 = large amount of mask (not recommended, too dark) | ||
|  | //  0.50 = recommended default | ||
|  | //  1.00 = no shadow mask | ||
|  | 
 | ||
|  | uniform float INPUT_MASK < | ||
|  | 	ui_type = "drag"; | ||
|  | 	ui_min = 0.0; | ||
|  | 	ui_max = 1.0; | ||
|  | 	ui_label = "Shadow Mask Intensity [CRT Lottes 2.0]"; | ||
|  | > = 0.5; | ||
|  | 
 | ||
|  | //-------------------------------------------------------------- | ||
|  | 
 | ||
|  | uniform int INPUT_X < | ||
|  | 	ui_type = "drag"; | ||
|  | 	ui_min = 1; | ||
|  | 	ui_max = BUFFER_WIDTH; | ||
|  | 	ui_label = "Resolution Width [CRT Lottes 2.0]"; | ||
|  | > = 640; | ||
|  | 
 | ||
|  | uniform int INPUT_Y < | ||
|  | 	ui_type = "drag"; | ||
|  | 	ui_min = 1; | ||
|  | 	ui_max = BUFFER_HEIGHT; | ||
|  | 	ui_label = "Resolution Height [CRT Lottes 2.0]"; | ||
|  | > = 480; | ||
|  | 
 | ||
|  | //-------------------------------------------------------------- | ||
|  | // Setup the function which returns input image color | ||
|  | 
 | ||
|  | void ToLinear(inout float3 color) | ||
|  | { | ||
|  | 	float3 c1 = color.rgb / 12.92; | ||
|  | 	float3 c2 = pow((color.rgb + 0.055)/1.055, 2.4); | ||
|  | 
 | ||
|  | 	color.r = (color.r <= 0.04045) ? c1.r : c2.r; | ||
|  | 	color.g = (color.g <= 0.04045) ? c1.g : c2.g; | ||
|  | 	color.b = (color.b <= 0.04045) ? c1.b : c2.b; | ||
|  | } | ||
|  | 
 | ||
|  | void ToSRGB(inout float3 color) | ||
|  | { | ||
|  | 	float3 c1 = color.rgb * 12.92; | ||
|  | 	float3 c2 = 1.055 * pow(color.rgb, 0.4166) - 0.055; | ||
|  | 
 | ||
|  | 	color.r = (color.r < 0.0031308) ? c1.r : c2.r; | ||
|  | 	color.g = (color.g < 0.0031308) ? c1.g : c2.g; | ||
|  | 	color.b = (color.b < 0.0031308) ? c1.b : c2.b; | ||
|  | } | ||
|  | 
 | ||
|  | float3 CrtsFetch(float2 uv) | ||
|  | { | ||
|  | 	float3 color = tex2D(ReShade::BackBuffer, uv).rgb; | ||
|  | 	ToLinear(color); | ||
|  | 	return color; | ||
|  | } | ||
|  | 
 | ||
|  | float4 CrtsTone( | ||
|  | float contrast, | ||
|  | float saturation, | ||
|  | float thin, | ||
|  | float mask) | ||
|  | { | ||
|  | 	 | ||
|  | 	mask = INPUT_MASK; | ||
|  | 
 | ||
|  |   	if (CRTS_MASK_TYPE <= 0){ | ||
|  | 		mask=1.0; | ||
|  | 	} | ||
|  | 
 | ||
|  | 
 | ||
|  |   	if(CRTS_MASK_TYPE == 2){ | ||
|  | 		mask=0.5+INPUT_MASK*0.5;	 | ||
|  | 	} | ||
|  | 
 | ||
|  |   	float4 ret; | ||
|  |   	float midOut=0.18/((1.5-thin)*(0.5*mask+0.5)); | ||
|  |   	float pMidIn=pow(0.18,contrast); | ||
|  |   	ret.x=contrast; | ||
|  |   	ret.y=((-pMidIn)+midOut)/((1.0-pMidIn)*midOut); | ||
|  |   	ret.z=((-pMidIn)*midOut+pMidIn)/(midOut*(-pMidIn)+midOut); | ||
|  |   	ret.w=contrast+saturation; | ||
|  |   	return ret; | ||
|  | } | ||
|  | 
 | ||
|  | float3 CrtsMask(float2 pos,float dark) | ||
|  | { | ||
|  | 	 | ||
|  | 	if (CRTS_MASK_TYPE == 1){    | ||
|  | 		float3 m=dark; | ||
|  | 		float x=frac(pos.x*(1.0/3.0)); | ||
|  | 		if(x<(1.0/3.0))m.r=1.0; | ||
|  | 		else if(x<(2.0/3.0))m.g=1.0; | ||
|  | 		else m.b=1.0; | ||
|  | 		return m; | ||
|  | 	} else if (CRTS_MASK_TYPE == 2){ | ||
|  | 		float3 m=1.0; | ||
|  | 		float x=frac(pos.x*(1.0/3.0)); | ||
|  | 		if(x<(1.0/3.0))m.r=dark; | ||
|  | 		else if(x<(2.0/3.0))m.g=dark; | ||
|  | 		else m.b=dark; | ||
|  | 		return m; | ||
|  | 	} else if(CRTS_MASK_TYPE <= 0){ | ||
|  | 	   return 1.0;	 | ||
|  | 	} else if(CRTS_MASK_TYPE >= 3){ | ||
|  | 		pos.x+=pos.y*3.0; | ||
|  | 		float3 m=dark; | ||
|  | 		float x=frac(pos.x*(1.0/6.0)); | ||
|  | 		if(x<(1.0/3.0))m.r=1.0; | ||
|  | 		else if(x<(2.0/3.0))m.g=1.0; | ||
|  | 		else m.b=1.0; | ||
|  | 		return m; | ||
|  | 	} else { | ||
|  | 		return 0.0; | ||
|  | 	} | ||
|  |  } | ||
|  | 
 | ||
|  |  float3 CrtsFilter( | ||
|  | //-------------------------------------------------------------- | ||
|  |   // SV_POSITION, fragCoord.xy | ||
|  |   float2 ipos, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // inputSize / outputSize (in pixels) | ||
|  |   float2 inputSizeDivOutputSize,      | ||
|  | //-------------------------------------------------------------- | ||
|  |   // 0.5 * inputSize (in pixels) | ||
|  |   float2 halfInputSize, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // 1.0 / inputSize (in pixels) | ||
|  |   float2 rcpInputSize, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // 1.0 / outputSize (in pixels) | ||
|  |   float2 rcpOutputSize, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // 2.0 / outputSize (in pixels) | ||
|  |   float2 twoDivOutputSize,    | ||
|  | //-------------------------------------------------------------- | ||
|  |   // inputSize.y | ||
|  |   float inputHeight, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // Warp scanlines but not phosphor mask | ||
|  |   //  0.0 = no warp | ||
|  |   //  1.0/64.0 = light warping | ||
|  |   //  1.0/32.0 = more warping | ||
|  |   // Want x and y warping to be different (based on aspect) | ||
|  |   float2 warp, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // Scanline thinness | ||
|  |   //  0.50 = fused scanlines | ||
|  |   //  0.70 = recommended default | ||
|  |   //  1.00 = thinner scanlines (too thin) | ||
|  |   // Shared with CrtsTone() function | ||
|  |   float thin, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // Horizonal scan blur | ||
|  |   //  -3.0 = pixely | ||
|  |   //  -2.5 = default | ||
|  |   //  -2.0 = smooth | ||
|  |   //  -1.0 = too blurry | ||
|  |   float blur, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // Shadow mask effect, ranges from, | ||
|  |   //  0.25 = large amount of mask (not recommended, too dark) | ||
|  |   //  0.50 = recommended default | ||
|  |   //  1.00 = no shadow mask | ||
|  |   // Shared with CrtsTone() function | ||
|  |   float mask, | ||
|  | //-------------------------------------------------------------- | ||
|  |   // Tonal curve parameters generated by CrtsTone() | ||
|  |   float4 tone | ||
|  | //-------------------------------------------------------------- | ||
|  |  ){ | ||
|  | //-------------------------------------------------------------- | ||
|  | 	#if (CRTS_DEBUG == 1) | ||
|  | 		float2 uv=ipos*rcpOutputSize; | ||
|  | 		// Show second half processed, and first half un-processed | ||
|  | 		if(uv.x<0.5) | ||
|  | 		{ | ||
|  | 				// Force nearest to get squares | ||
|  | 				uv*=1.0/rcpInputSize; | ||
|  | 				uv=floor(uv)+float2(0.5,0.5); | ||
|  | 				uv*=rcpInputSize; | ||
|  | 				float3 color=CrtsFetch(uv); | ||
|  | 				return color; | ||
|  | 		} | ||
|  | 	#endif | ||
|  | 
 | ||
|  |   	float2 pos; | ||
|  | 	float vin; | ||
|  | 
 | ||
|  | 	if (CRTS_WARP){ | ||
|  | 		// Convert to {-1 to 1} range | ||
|  | 		pos=ipos*twoDivOutputSize-float2(1.0,1.0); | ||
|  | 		// Distort pushes image outside {-1 to 1} range | ||
|  | 		pos*=float2(1.0+(pos.y*pos.y)*warp.x,1.0+(pos.x*pos.x)*warp.y); | ||
|  | 		// TODO: Vignette needs optimization | ||
|  | 		vin=1.0-((1.0-saturate(pos.x*pos.x))*(1.0-saturate(pos.y*pos.y))); | ||
|  | 		vin=saturate((-vin)*inputHeight+inputHeight); | ||
|  | 		// Leave in {0 to inputSize} | ||
|  | 		pos=pos*halfInputSize+halfInputSize;      | ||
|  | 	} else { | ||
|  | 		pos=ipos*inputSizeDivOutputSize; | ||
|  | 	} | ||
|  | 	 | ||
|  |   	// Snap to center of first scanline | ||
|  |   	float y0=floor(pos.y-0.5)+0.5; | ||
|  | 
 | ||
|  | 	#if (CRTS_2_TAP == 1) | ||
|  | 		// Using Inigo's "Improved Texture Interpolation" | ||
|  | 		// http://iquilezles.org/www/articles/texture/texture.htm | ||
|  | 		pos.x+=0.5; | ||
|  | 		float xi=floor(pos.x); | ||
|  | 		float xf=pos.x-xi; | ||
|  | 		xf=xf*xf*xf*(xf*(xf*6.0-15.0)+10.0);   | ||
|  | 		float x0=xi+xf-0.5; | ||
|  | 		float2 p=float2(x0*rcpInputSize.x,y0*rcpInputSize.y);      | ||
|  | 		// Coordinate adjusted bilinear fetch from 2 nearest scanlines | ||
|  | 		float3 colA=CrtsFetch(p); | ||
|  | 		p.y+=rcpInputSize.y; | ||
|  | 		float3 colB=CrtsFetch(p); | ||
|  | 	#else | ||
|  | 		// Snap to center of one of four pixels | ||
|  | 		float x0=floor(pos.x-1.5)+0.5; | ||
|  | 		// Inital UV position | ||
|  | 		float2 p=float2(x0*rcpInputSize.x,y0*rcpInputSize.y);      | ||
|  | 		// Fetch 4 nearest texels from 2 nearest scanlines | ||
|  | 		float3 colA0=CrtsFetch(p); | ||
|  | 		p.x+=rcpInputSize.x; | ||
|  | 		float3 colA1=CrtsFetch(p); | ||
|  | 		p.x+=rcpInputSize.x; | ||
|  | 		float3 colA2=CrtsFetch(p); | ||
|  | 		p.x+=rcpInputSize.x; | ||
|  | 		float3 colA3=CrtsFetch(p); | ||
|  | 		p.y+=rcpInputSize.y; | ||
|  | 		float3 colB3=CrtsFetch(p); | ||
|  | 		p.x-=rcpInputSize.x; | ||
|  | 		float3 colB2=CrtsFetch(p); | ||
|  | 		p.x-=rcpInputSize.x; | ||
|  | 		float3 colB1=CrtsFetch(p); | ||
|  | 		p.x-=rcpInputSize.x; | ||
|  | 		float3 colB0=CrtsFetch(p); | ||
|  | 	#endif | ||
|  | 
 | ||
|  |   	// Vertical filter | ||
|  |   	// Scanline intensity is using sine wave | ||
|  |   	// Easy filter window and integral used later in exposure | ||
|  |   	float off=pos.y-y0; | ||
|  |   	float pi2=6.28318530717958; | ||
|  |   	float hlf=0.5; | ||
|  |   	float scanA=cos(min(0.5,  off *thin     )*pi2)*hlf+hlf; | ||
|  |   	float scanB=cos(min(0.5,(-off)*thin+thin)*pi2)*hlf+hlf; | ||
|  | 
 | ||
|  | 	#if (CRTS_2_TAP == 1) | ||
|  | 	if (CRTS_WARP){ | ||
|  | 			// Get rid of wrong pixels on edge | ||
|  | 			scanA*=vin; | ||
|  | 			scanB*=vin; | ||
|  | 	} | ||
|  | 		// Apply vertical filter | ||
|  | 		float3 color=(colA*scanA)+(colB*scanB); | ||
|  | 	#else | ||
|  | 		 // Horizontal kernel is simple gaussian filter | ||
|  | 		float off0=pos.x-x0; | ||
|  | 		float off1=off0-1.0; | ||
|  | 		float off2=off0-2.0; | ||
|  | 		float off3=off0-3.0; | ||
|  | 		float pix0=exp2(blur*off0*off0); | ||
|  | 		float pix1=exp2(blur*off1*off1); | ||
|  | 		float pix2=exp2(blur*off2*off2); | ||
|  | 		float pix3=exp2(blur*off3*off3); | ||
|  | 		float pixT=rcp(pix0+pix1+pix2+pix3); | ||
|  | 
 | ||
|  | 	if (CRTS_WARP){ | ||
|  | 		// Get rid of wrong pixels on edge | ||
|  | 		pixT*=vin; | ||
|  | 		} | ||
|  | 		scanA*=pixT; | ||
|  | 		scanB*=pixT; | ||
|  | 		// Apply horizontal and vertical filters | ||
|  | 		float3 color= | ||
|  | 			(colA0*pix0+colA1*pix1+colA2*pix2+colA3*pix3)*scanA + | ||
|  | 			(colB0*pix0+colB1*pix1+colB2*pix2+colB3*pix3)*scanB; | ||
|  | 	#endif | ||
|  | 	 | ||
|  | 		// Apply phosphor mask           | ||
|  | 		color*=CrtsMask(ipos,mask); | ||
|  | 		// Optional color processing | ||
|  | 	if (CRTS_TONE){ | ||
|  | 		// Tonal control, start by protecting from /0 | ||
|  | 		float peak=max(1.0/(256.0*65536.0),max(color.r,max(color.g,color.b))); | ||
|  | 		// Compute the ratios of {R,G,B} | ||
|  | 		float3 ratio=color*rcp(peak); | ||
|  | 		// Apply tonal curve to peak value | ||
|  | 	if (CRTS_CONTRAST){ | ||
|  | 			peak=pow(peak,tone.x); | ||
|  | 	} | ||
|  | 		peak=peak*rcp(peak*tone.y+tone.z); | ||
|  | 		// Apply saturation | ||
|  | 	if (CRTS_SATURATION){ | ||
|  | 			ratio=pow(ratio,float3(tone.w,tone.w,tone.w)); | ||
|  | 	} | ||
|  | 		// Reconstruct color | ||
|  | 		return ratio*peak; | ||
|  | 	} else { | ||
|  | 		return color; | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
|  | // | ||
|  | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
|  | // | ||
|  | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
|  | 
 | ||
|  | void PS_CRTLottes2018(float4 vpos : SV_Position, float2 texcoord : TEXCOORD, out float4 color : SV_Target0) | ||
|  | { | ||
|  | 	color = tex2D(ReShade::BackBuffer, texcoord.xy); | ||
|  | 
 | ||
|  |  	color.rgb=CrtsFilter( | ||
|  |   	vpos.xy, | ||
|  |   	float2(INPUT_X,INPUT_Y)/ReShade::ScreenSize.xy, | ||
|  |   	float2(INPUT_X,INPUT_Y)*0.5, | ||
|  |   	1.0/float2(INPUT_X,INPUT_Y), | ||
|  |   	1.0/ReShade::ScreenSize.xy, | ||
|  |   	2.0/ReShade::ScreenSize.xy, | ||
|  |   	INPUT_Y, | ||
|  |   	float2(1.0/CRTS_WARP_X,1.0/CRTS_WARP_Y), | ||
|  |   	INPUT_THIN, | ||
|  |   	INPUT_BLUR, | ||
|  |   	INPUT_MASK, | ||
|  |   	CrtsTone(1.0,0.0,INPUT_THIN,INPUT_MASK)); | ||
|  |  	 | ||
|  | 	 ToSRGB(color.rgb); | ||
|  | } | ||
|  | 
 | ||
|  | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
|  | // | ||
|  | //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | ||
|  | 
 | ||
|  | 
 | ||
|  | technique CRTLottes2018 | ||
|  | { | ||
|  | 	pass | ||
|  | 	{ | ||
|  | 		VertexShader = PostProcessVS; | ||
|  | 		PixelShader = PS_CRTLottes2018; | ||
|  | 	} | ||
|  | } |