Another bezel reflection shader

I saw this shader just landed on shadertoy, so I ported it to slang real quick (and made some things into parameters, added some mask code, etc.):

#version 450

// vt220
// by sprash3
// https://www.shadertoy.com/view/XdtfzX

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
   float curvature, width, height, smoothness, shine, blur_size, dimmer, csize, mask;
} params;

#pragma parameter curvature "Curve Radius" 3.0 0.0 10.0 0.1
#pragma parameter width "Width" 1.0 0.0 2.0 0.01
#pragma parameter height "Height" 1.06 0.0 2.0 0.01
#pragma parameter smoothness "Border Blur" 1.0 0.0 10.0 0.1
#pragma parameter shine "Screen Reflection" 1.0 0.0 10.0 0.1
#pragma parameter blur_size "Reflection Blur" 3.0 0.0 5.0 0.05
#pragma parameter dimmer "Ambient Brightness" 0.5 0.0 1.0 0.05
#pragma parameter csize "Corner size" 0.04 0.0 0.07 0.01
#pragma parameter mask "Mask Type" 2.0 1.0 17.0 1.0

int mask_picker = int(params.mask);

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

#define iTime (float(params.FrameCount) / 60.0)
const vec3 iMouse = vec3(0.0);
#define iResolution params.OutputSize.xy
#define iChannel0 Source
#define fragCoord (vec2(vTexCoord.x, 1.0-vTexCoord.y) * params.OutputSize.xy)
#define c FragColor

//#define LIGHTS_ON true
//#define LIGHTS_ON false
#define LIGHTS_ON sin(fract(iTime/23.)+2.74) + 0.1*abs(sin(iTime*1000.)) <.0

#define WIDTH 0.48 * params.width
#define HEIGHT 0.3 * params.height
#define CURVE params.curvature
#define SMOOTH 0.004 * params.smoothness
#define SHINE 0.66 * params.shine

#define PHOSPHOR_COL vec4(0.75, 0.75, 0.75, 0.0)
#define BEZEL_COL vec4(0.8, 0.8, 0.6, 0.0)

#define REFLECTION_BLUR_ITERATIONS 5
#define REFLECTION_BLUR_SIZE 0.04 * params.blur_size


precision highp float;

//TODO - replace with include statement once location is settled
vec3 mask_weights(vec2 coord, float mask_intensity, int phosphor_layout){
   vec3 weights = vec3(0.,0.,0.);
   float intens = 1.;
   float inv = 1.-mask_intensity;
   vec3 green   = vec3(inv, intens, inv);
   vec3 magenta = vec3(intens,inv,intens);
   vec3 black   = vec3(inv,inv,inv);
   vec3 red     = vec3(intens,inv,inv);
   vec3 yellow  = vec3(intens,inv,intens);
   vec3 cyan    = vec3(inv,intens,intens);
   vec3 blue    = vec3(inv,inv,intens);
   int w, z = 0;
   
   vec3 aperture_weights = mix(magenta, green, floor(mod(coord.x, 2.0)));

   if(phosphor_layout == 1){
      // classic aperture for RGB panels; good for 1080p, too small for 4K+
      // aka aperture_1_2_bgr
      weights  = aperture_weights;
   }

   else if(phosphor_layout == 2){
      // 2x2 shadow mask for RGB panels; good for 1080p, too small for 4K+
      // aka delta_1_2x1_bgr
      vec3 inverse_aperture = mix(green, magenta, floor(mod(coord.x, 2.0)));
      weights               = mix(aperture_weights, inverse_aperture, floor(mod(coord.y, 2.0)));
   }

   else if(phosphor_layout == 3){
      // slot mask for RGB panels; looks okay at 1080p, looks better at 4K
      vec3 slotmask[3][4] = {
         {magenta, green, black,   black},
         {magenta, green, magenta, green},
         {black,   black, magenta, green}
      };
      
      // find the vertical index
      w = int(floor(mod(coord.y, 3.0)));

      // find the horizontal index
      z = int(floor(mod(coord.x, 4.0)));

      // use the indexes to find which color to apply to the current pixel
      weights = slotmask[w][z];
   }

   if(phosphor_layout == 4){
      // classic aperture for RBG panels; good for 1080p, too small for 4K+
      weights  = mix(yellow, blue, floor(mod(coord.x, 2.0)));
   }

   else if(phosphor_layout == 5){
      // 2x2 shadow mask for RBG panels; good for 1080p, too small for 4K+
      vec3 inverse_aperture = mix(blue, yellow, floor(mod(coord.x, 2.0)));
      weights               = mix(mix(yellow, blue, floor(mod(coord.x, 2.0))), inverse_aperture, floor(mod(coord.y, 2.0)));
   }
   
   else if(phosphor_layout == 6){
      // aperture_1_4_rgb; good for simulating lower 
      vec3 ap4[4] = vec3[](red, green, blue, black);
      
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = ap4[z];
   }
   
   else if(phosphor_layout == 7){
      // aperture_2_5_bgr
      vec3 ap3[5] = vec3[](red, magenta, blue, green, green);
      
      z = int(floor(mod(coord.x, 5.0)));
      
      weights = ap3[z];
   }
   
   else if(phosphor_layout == 8){
      // aperture_3_6_rgb
      
      vec3 big_ap[7] = vec3[](red, red, yellow, green, cyan, blue, blue);
      
      w = int(floor(mod(coord.x, 7.)));
      
      weights = big_ap[w];
   }
   
   else if(phosphor_layout == 9){
      // reduced TVL aperture for RGB panels
      // aperture_2_4_rgb
      
      vec3 big_ap_rgb[4] = vec3[](red, yellow, cyan, blue);
      
      w = int(floor(mod(coord.x, 4.)));
      
      weights = big_ap_rgb[w];
   }
   
   else if(phosphor_layout == 10){
      // reduced TVL aperture for RBG panels
      
      vec3 big_ap_rbg[4] = vec3[](red, magenta, cyan, green);
      
      w = int(floor(mod(coord.x, 4.)));
      
      weights = big_ap_rbg[w];
   }
   
   else if(phosphor_layout == 11){
      // delta_1_4x1_rgb; dunno why this is called 4x1 when it's obviously 4x2 /shrug
      vec3 delta1[2][4] = {
         {red,  green, blue, black},
         {blue, black, red,  green}
      };
      
      w = int(floor(mod(coord.y, 2.0)));
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = delta1[w][z];
   }
   
   else if(phosphor_layout == 12){
      // delta_2_4x1_rgb
      vec3 delta[2][4] = {
         {red, yellow, cyan, blue},
         {cyan, blue, red, yellow}
      };
      
      w = int(floor(mod(coord.y, 2.0)));
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = delta[w][z];
   }
   
   else if(phosphor_layout == 13){
      // delta_2_4x2_rgb
      vec3 delta[4][4] = {
         {red,  yellow, cyan, blue},
         {red,  yellow, cyan, blue},
         {cyan, blue,   red,  yellow},
         {cyan, blue,   red,  yellow}
      };
      
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = delta[w][z];
   }

   else if(phosphor_layout == 14){
      // slot mask for RGB panels; too low-pitch for 1080p, looks okay at 4K, but wants 8K+
      vec3 slotmask[3][6] = {
         {magenta, green, black, black,   black, black},
         {magenta, green, black, magenta, green, black},
         {black,   black, black, magenta, green, black}
      };
      
      // find the vertical index
      w = int(floor(mod(coord.y, 3.0)));

      // find the horizontal index
      z = int(floor(mod(coord.x, 6.0)));

      // use the indexes to find which color to apply to the current pixel
      weights = slotmask[w][z];
   }
   
   else if(phosphor_layout == 15){
      // slot_2_4x4_rgb
      vec3 slot2[4][8] = {
         {red,   yellow, cyan,  blue,  red,   yellow, cyan,  blue },
         {red,   yellow, cyan,  blue,  black, black,  black, black},
         {red,   yellow, cyan,  blue,  red,   yellow, cyan,  blue },
         {black, black,  black, black, red,   yellow, cyan,  blue }
      };
   
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 8.0)));
      
      weights = slot2[w][z];
   }

   else if(phosphor_layout == 16){
      // slot mask for RBG panels; too low-pitch for 1080p, looks okay at 4K, but wants 8K+
      vec3 slotmask[3][4] = {
         {yellow, blue,  black,  black},
         {yellow, blue,  yellow, blue},
         {black,  black, yellow, blue}
      };
      
      // find the vertical index
      w = int(floor(mod(coord.y, 3.0)));

      // find the horizontal index
      z = int(floor(mod(coord.x, 4.0)));

      // use the indexes to find which color to apply to the current pixel
      weights = slotmask[w][z];
   }
   
   else if(phosphor_layout == 17){
      // slot_2_5x4_bgr
      vec3 slot2[4][10] = {
         {red,   magenta, blue,  green, green, red,   magenta, blue,  green, green},
         {black, blue,    blue,  green, green, red,   red,     black, black, black},
         {red,   magenta, blue,  green, green, red,   magenta, blue,  green, green},
         {red,   red,     black, black, black, black, blue,    blue,  green, green}
      };
   
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 10.0)));
      
      weights = slot2[w][z];
   }
   
   else if(phosphor_layout == 18){
      // same as above but for RBG panels
      vec3 slot2[4][10] = {
         {red,   yellow, green, blue,  blue,  red,   yellow, green, blue,  blue },
         {black, green,  green, blue,  blue,  red,   red,    black, black, black},
         {red,   yellow, green, blue,  blue,  red,   yellow, green, blue,  blue },
         {red,   red,    black, black, black, black, green,  green, blue,  blue }
      };
   
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 10.0)));
      
      weights = slot2[w][z];
   }
   
   else if(phosphor_layout == 19){
      // slot_3_7x6_rgb
      vec3 slot[6][14] = {
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {red,   red,   yellow, green, cyan,  blue,  blue,  black, black, black,  black,  black, black, black},
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {black, black, black,  black, black, black, black, black, red,   red,    yellow, green, cyan,  blue}
      };
      
      w = int(floor(mod(coord.y, 6.0)));
      z = int(floor(mod(coord.x, 14.0)));
      
      weights = slot[w][z];
   }

   return weights;
}


float roundSquare(vec2 p, vec2 b, float r)
{
    return length(max(abs(p)-b,0.0))-r;
}

float rand(vec2 co){
    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);
}

vec2 CurvedSurface(vec2 uv, float r)
{
    return r * uv/sqrt(r * r - dot(uv, uv));
}

float corner(vec2 coord)
{
	coord = (coord - vec2(0.5)) * 1.0 + vec2(0.5);
	coord = min(coord, vec2(1.0)-coord) * vec2(1.0, params.OutputSize.y/params.OutputSize.x);
	vec2 cdist = vec2(max(params.csize, max((1.0-smoothstep(100.0,600.0,800.0))*0.01,0.002)));
	coord = (cdist - min(coord,cdist));
	float dist = sqrt(dot(coord,coord));
	return clamp((cdist.x-dist)*800.0,0.0, 1.0);
}

// Coordinates for content
vec2 crtCurvA(vec2 uv) 
{
    float r = CURVE;
    if (iMouse.z > 0.) r *= exp(0.5 - iMouse.y/iResolution.y);
    uv = (uv / iResolution.xy - 0.5) / vec2(iResolution.y/iResolution.x, 1.) * 1.88;
	uv = CurvedSurface(uv, r);
	uv *= 0.5 / vec2(WIDTH, HEIGHT);
   uv *= vec2(1.,0.95);
    uv = (uv / 2.0) + 0.5;        
   	if (iMouse.z > 0.) uv.x -= iMouse.x/iResolution.x - 0.5;
    
	return uv;
}

// Coordinates for the rest
vec2 crtCurvB(vec2 uv, float r) 
{
    r = CURVE * r;
    if (iMouse.z > 0.) r *= exp(0.5-iMouse.y/iResolution.y);
    uv = (uv / iResolution.xy - 0.5) / vec2(iResolution.y/iResolution.x, 1.) * 2.0;
	uv = CurvedSurface(uv, r);
    uv = (uv / 2.0) + 0.5;
  	if (iMouse.z > 0.) uv.x -= iMouse.x/iResolution.x - 0.5;
    
	return uv;
}

// Coordinates for the shine
vec2 crtCurvS(vec2 uv, float r) 
{
    r = CURVE * r;
    if (iMouse.z > 0.) r *= exp(0.5-iMouse.y/iResolution.y);
    uv = (uv / iResolution.xy - 0.5) / vec2(iResolution.y/iResolution.x, 1.) * 2.0;
	uv = CurvedSurface(uv, r);
    uv = (uv / 2.0) + 0.5;
    
	return uv;
}


// standard roundSquare
float stdRS(vec2 uv, float r)
{
    return roundSquare(uv - 0.5, vec2(WIDTH, HEIGHT) + r, 0.05);
}

// Calculate normal to distance function and move along
// normal with distance to get point of reflection
vec2 borderReflect(vec2 p, float r)
{
    float eps = 0.0001;
    vec2 epsx = vec2(eps,0.0);
    vec2 epsy = vec2(0.0,eps);
    vec2 b = (1.+vec2(r,r))* 0.5;
    r /= 3.0;
    
    p -= 0.5;
    vec2 normal = vec2(roundSquare(p-epsx,b,r)-roundSquare(p+epsx,b,r),
                       roundSquare(p-epsy,b,r)-roundSquare(p+epsy,b,r))/eps;
    float d = roundSquare(p, b, r);
    p += 0.5;
    p = vec2(1.-p.x,p.y);
    return p + d*normal;
}

void main()
{
    c = vec4(0.0, 0.0, 0.0, 0.0);
    
    vec2 uvC = crtCurvA(fragCoord);			// Content Layer
    vec2 uvS = crtCurvB(fragCoord, 1.);		// Screen Layer
    vec2 uvE = crtCurvB(fragCoord, 1.25);	// Enclosure Layer
    vec4 c1 = vec4(0.0); vec4 c2 = vec4(0.0);
//    if (LIGHTS_ON) {
        // From my shader https://www.shadertoy.com/view/MtBXW3
        
   float ambient = 0.1;

        // Glass Shine 
        vec2 uvSh = crtCurvS(fragCoord, 1.);
    	c1 += max(0.0, SHINE - distance(uvSh, vec2(0.5, 1.0))) *
            smoothstep(SMOOTH/2.0, -SMOOTH/2.0, stdRS(uvS + vec2(0., 0.03), 0.0));

	    // Ambient
	    c1 += max(0.0, ambient - 0.5*distance(uvS, vec2(0.5,0.5))) *
	        smoothstep(SMOOTH, -SMOOTH, stdRS(uvS, 0.0));

	    // Enclosure Layer 
        uvSh = crtCurvS(fragCoord, 1.25);
    	vec4 b = vec4(0., 0., 0., 0.);
    	for(int i=0; i<12; i++)
			b += (clamp(BEZEL_COL + rand(uvSh+float(i))*0.05-0.025, 0., 1.) +
				rand(uvE+1.0+float(i))*0.25 * cos((uvSh.x-0.5)*3.1415*1.5))/12.;
        
        // Inner Border
        const float HHW = 0.5 * HEIGHT/WIDTH;
        
    	c1 += b/3.*( 1. + smoothstep(HHW - 0.025, HHW + 0.025, abs(atan(uvS.x-0.5, uvS.y-0.5))/3.1415) 
       		+ smoothstep(HHW + 0.025, HHW - 0.025, abs(atan(uvS.x-0.5, 0.5-uvS.y))/3.1415) )* 
			smoothstep(-SMOOTH, SMOOTH, stdRS(uvS, 0.0)) * 
			smoothstep(SMOOTH, -SMOOTH, stdRS(uvE, 0.05));
    
		// Inner Border Shine
  		c1 += (b - 0.4)* 
			smoothstep(-SMOOTH*2.0, SMOOTH*2.0, roundSquare(uvE-vec2(0.5, 0.505), vec2(WIDTH, HEIGHT) + 0.05, 0.05)) * 
			smoothstep(SMOOTH*2.0, -SMOOTH*2.0, roundSquare(uvE-vec2(0.5, 0.495), vec2(WIDTH, HEIGHT) + 0.05, 0.05));
        
    	// Outer Border
    	c1 += b * 
			smoothstep(-SMOOTH, SMOOTH, roundSquare(uvE-vec2(0.5, 0.5), vec2(WIDTH, HEIGHT) + 0.05, 0.05)) * 
			smoothstep(SMOOTH, -SMOOTH, roundSquare(uvE-vec2(0.5, 0.5), vec2(WIDTH, HEIGHT) + 0.15, 0.05)); 

    	// Outer Border Shine
		c1 += (b - 0.4)* 
			smoothstep(-SMOOTH*2.0, SMOOTH*2.0, roundSquare(uvE-vec2(0.5, 0.495), vec2(WIDTH, HEIGHT) + 0.15, 0.05)) * 
			smoothstep(SMOOTH*2.0, -SMOOTH*2.0, roundSquare(uvE-vec2(0.5, 0.505), vec2(WIDTH, HEIGHT) + 0.15, 0.05));
        
        // Table and room
        c1 += max(0. , (1. - 2.0* fragCoord.y/iResolution.y)) * vec4(1, 1, 1, 0.) *
            smoothstep(-0.25, 0.25, roundSquare(uvC - vec2(0.5, -0.2), vec2(WIDTH+0.25, HEIGHT-0.15), .1)) *
            smoothstep(-SMOOTH*2.0, SMOOTH*2.0, roundSquare(uvE-vec2(0.5, 0.5), vec2(WIDTH, HEIGHT) + 0.15, 0.05));
        
//    } else {
        // From my shader https://www.shadertoy.com/view/lt2SDK
        
   ambient = 0.2;

        // Ambient
	    c2 += max(0.0, ambient - 0.3*distance(uvS, vec2(0.5,0.5))) *
	        smoothstep(SMOOTH, -SMOOTH, stdRS(uvS, 0.0));
        
	    // Inner Border               
	  	c2 += BEZEL_COL * ambient * 0.7 *
	        smoothstep(-SMOOTH, SMOOTH, stdRS(uvS, 0.0)) * 
	        smoothstep(SMOOTH, -SMOOTH, stdRS(uvE, 0.05));
    
	    // Corner
	  	c2 -= (BEZEL_COL )* 
	        smoothstep(-SMOOTH*2.0, SMOOTH*10.0, stdRS(uvE, 0.05)) * 
	        smoothstep(SMOOTH*2.0, -SMOOTH*2.0, stdRS(uvE, 0.05));

	    // Outer Border
	    c2 += BEZEL_COL * ambient *
	       	smoothstep(-SMOOTH, SMOOTH, stdRS(uvE, 0.05)) * 
	        smoothstep(SMOOTH, -SMOOTH, stdRS(uvE, 0.15)); 
    
	    // Inner Border Reflection
	    for(int i = 0; i < REFLECTION_BLUR_ITERATIONS; i++)
	    {
	    	vec2 uvR = borderReflect(uvC + (vec2(rand(uvC+float(i)), rand(uvC+float(i)+0.1))-0.5)*REFLECTION_BLUR_SIZE, 0.05) ;
	    	c2 += (PHOSPHOR_COL - BEZEL_COL*ambient) * texture(iChannel0, 1.-vec2(uvR.x, uvR.y)) / float(REFLECTION_BLUR_ITERATIONS) * 
		        smoothstep(-SMOOTH, SMOOTH, stdRS(uvS, 0.0)) * 
				smoothstep(SMOOTH, -SMOOTH, stdRS(uvE, 0.05));
	    }
// commenting because mipmaps are a crapshoot with slang; TODO: try again with GLSL
//	    // Bloom using composed MipMaps
//	    c2 += textureLod(iChannel0, vec2(uvC.x, 1.0-uvC.y), 3.) * smoothstep(0., -SMOOTH*20., stdRS(uvS, -0.02)) * 0.5;
//	   	c2 += textureLod(iChannel0, vec2(uvC.x, 1.0-uvC.y), 4.) * smoothstep(0., -SMOOTH*20., stdRS(uvS, -0.02)) * 0.5;
//	    c2 += textureLod(iChannel0, vec2(uvC.x, 1.0-uvC.y), 5.) * smoothstep(0., -SMOOTH*20., stdRS(uvS, -0.02)) * 0.5;
//    }
    // mix light and dark for variable brightness
    c = mix(c2, c1, params.dimmer);

      vec4 image = vec4(texture(iChannel0, vec2(uvC.x, 1.0-uvC.y)).rgb * corner(uvC) * mask_weights(fragCoord.xy, 1.0, mask_picker), 1.0);
   if (uvC.x > 0. && uvC.x < 1. && uvC.y > 0. && uvC.y < 1.)
      c += vec4(image);
}

Looks like this (dunno why my fps says 2.11; it runs pretty fast):

8 Likes

Yet again, I end up with an edge case or something. I canā€™t get it working here, and all the logs say is that the shader failed to compile. Any other Radeon-havers encountering same?

thatā€™s weird. I was originally testing it on a radeon card.

Wow, thatā€™s usually the major factor. Iā€™ll troubleshoot further.

Hello, what I found here is Mask Type 17 on 1080p lcd 24" seems quite realistic compared to the real CRT (size of dots and how it produce colors, even one white dot contain RGB elements)

Can you please add option to code that disable monitor frame effect and ā€œglass reflectionā€ on it?

Because when you far away from lcd image is almost same as on CRT, maybe little darker. But more important that gfx of 8-16 bit games seems very close to CRT.

Sure, here you go:

#version 450

layout(push_constant) uniform Push
{
	vec4 SourceSize;
	vec4 OriginalSize;
	vec4 OutputSize;
	uint FrameCount;
   float mask, mask_strength;
} params;

#pragma parameter mask "Mask Type" 17.0 0.0 19.0 1.0
#pragma parameter mask_strength "Mask Strength" 0.5 0.0 1.0 0.05

int mask_picker = int(params.mask);

layout(std140, set = 0, binding = 0) uniform UBO
{
	mat4 MVP;
} global;

#pragma stage vertex
layout(location = 0) in vec4 Position;
layout(location = 1) in vec2 TexCoord;
layout(location = 0) out vec2 vTexCoord;

void main()
{
   gl_Position = global.MVP * Position;
   vTexCoord = TexCoord;
}

#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;

vec3 mask_weights(vec2 coord, float mask_intensity, int phosphor_layout){
   vec3 weights = vec3(0.,0.,0.);
   float intens = 1.;
   float inv = 1.-mask_intensity;
   vec3 green   = vec3(inv, intens, inv);
   vec3 magenta = vec3(intens,inv,intens);
   vec3 black   = vec3(inv,inv,inv);
   vec3 red     = vec3(intens,inv,inv);
   vec3 yellow  = vec3(intens,inv,intens);
   vec3 cyan    = vec3(inv,intens,intens);
   vec3 blue    = vec3(inv,inv,intens);
   int w, z = 0;
   
   vec3 aperture_weights = mix(magenta, green, floor(mod(coord.x, 2.0)));

   if(phosphor_layout == 1){
      // classic aperture for RGB panels; good for 1080p, too small for 4K+
      // aka aperture_1_2_bgr
      weights  = aperture_weights;
   }

   else if(phosphor_layout == 2){
      // 2x2 shadow mask for RGB panels; good for 1080p, too small for 4K+
      // aka delta_1_2x1_bgr
      vec3 inverse_aperture = mix(green, magenta, floor(mod(coord.x, 2.0)));
      weights               = mix(aperture_weights, inverse_aperture, floor(mod(coord.y, 2.0)));
   }

   else if(phosphor_layout == 3){
      // slot mask for RGB panels; looks okay at 1080p, looks better at 4K
      vec3 slotmask[3][4] = {
         {magenta, green, black,   black},
         {magenta, green, magenta, green},
         {black,   black, magenta, green}
      };
      
      // find the vertical index
      w = int(floor(mod(coord.y, 3.0)));

      // find the horizontal index
      z = int(floor(mod(coord.x, 4.0)));

      // use the indexes to find which color to apply to the current pixel
      weights = slotmask[w][z];
   }

   if(phosphor_layout == 4){
      // classic aperture for RBG panels; good for 1080p, too small for 4K+
      weights  = mix(yellow, blue, floor(mod(coord.x, 2.0)));
   }

   else if(phosphor_layout == 5){
      // 2x2 shadow mask for RBG panels; good for 1080p, too small for 4K+
      vec3 inverse_aperture = mix(blue, yellow, floor(mod(coord.x, 2.0)));
      weights               = mix(mix(yellow, blue, floor(mod(coord.x, 2.0))), inverse_aperture, floor(mod(coord.y, 2.0)));
   }
   
   else if(phosphor_layout == 6){
      // aperture_1_4_rgb; good for simulating lower 
      vec3 ap4[4] = vec3[](red, green, blue, black);
      
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = ap4[z];
   }
   
   else if(phosphor_layout == 7){
      // aperture_2_5_bgr
      vec3 ap3[5] = vec3[](red, magenta, blue, green, green);
      
      z = int(floor(mod(coord.x, 5.0)));
      
      weights = ap3[z];
   }
   
   else if(phosphor_layout == 8){
      // aperture_3_6_rgb
      
      vec3 big_ap[7] = vec3[](red, red, yellow, green, cyan, blue, blue);
      
      w = int(floor(mod(coord.x, 7.)));
      
      weights = big_ap[w];
   }
   
   else if(phosphor_layout == 9){
      // reduced TVL aperture for RGB panels
      // aperture_2_4_rgb
      
      vec3 big_ap_rgb[4] = vec3[](red, yellow, cyan, blue);
      
      w = int(floor(mod(coord.x, 4.)));
      
      weights = big_ap_rgb[w];
   }
   
   else if(phosphor_layout == 10){
      // reduced TVL aperture for RBG panels
      
      vec3 big_ap_rbg[4] = vec3[](red, magenta, cyan, green);
      
      w = int(floor(mod(coord.x, 4.)));
      
      weights = big_ap_rbg[w];
   }
   
   else if(phosphor_layout == 11){
      // delta_1_4x1_rgb; dunno why this is called 4x1 when it's obviously 4x2 /shrug
      vec3 delta1[2][4] = {
         {red,  green, blue, black},
         {blue, black, red,  green}
      };
      
      w = int(floor(mod(coord.y, 2.0)));
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = delta1[w][z];
   }
   
   else if(phosphor_layout == 12){
      // delta_2_4x1_rgb
      vec3 delta[2][4] = {
         {red, yellow, cyan, blue},
         {cyan, blue, red, yellow}
      };
      
      w = int(floor(mod(coord.y, 2.0)));
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = delta[w][z];
   }
   
   else if(phosphor_layout == 13){
      // delta_2_4x2_rgb
      vec3 delta[4][4] = {
         {red,  yellow, cyan, blue},
         {red,  yellow, cyan, blue},
         {cyan, blue,   red,  yellow},
         {cyan, blue,   red,  yellow}
      };
      
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 4.0)));
      
      weights = delta[w][z];
   }

   else if(phosphor_layout == 14){
      // slot mask for RGB panels; too low-pitch for 1080p, looks okay at 4K, but wants 8K+
      vec3 slotmask[3][6] = {
         {magenta, green, black, black,   black, black},
         {magenta, green, black, magenta, green, black},
         {black,   black, black, magenta, green, black}
      };
      
      // find the vertical index
      w = int(floor(mod(coord.y, 3.0)));

      // find the horizontal index
      z = int(floor(mod(coord.x, 6.0)));

      // use the indexes to find which color to apply to the current pixel
      weights = slotmask[w][z];
   }
   
   else if(phosphor_layout == 15){
      // slot_2_4x4_rgb
      vec3 slot2[4][8] = {
         {red,   yellow, cyan,  blue,  red,   yellow, cyan,  blue },
         {red,   yellow, cyan,  blue,  black, black,  black, black},
         {red,   yellow, cyan,  blue,  red,   yellow, cyan,  blue },
         {black, black,  black, black, red,   yellow, cyan,  blue }
      };
   
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 8.0)));
      
      weights = slot2[w][z];
   }

   else if(phosphor_layout == 16){
      // slot mask for RBG panels; too low-pitch for 1080p, looks okay at 4K, but wants 8K+
      vec3 slotmask[3][4] = {
         {yellow, blue,  black,  black},
         {yellow, blue,  yellow, blue},
         {black,  black, yellow, blue}
      };
      
      // find the vertical index
      w = int(floor(mod(coord.y, 3.0)));

      // find the horizontal index
      z = int(floor(mod(coord.x, 4.0)));

      // use the indexes to find which color to apply to the current pixel
      weights = slotmask[w][z];
   }
   
   else if(phosphor_layout == 17){
      // slot_2_5x4_bgr
      vec3 slot2[4][10] = {
         {red,   magenta, blue,  green, green, red,   magenta, blue,  green, green},
         {black, blue,    blue,  green, green, red,   red,     black, black, black},
         {red,   magenta, blue,  green, green, red,   magenta, blue,  green, green},
         {red,   red,     black, black, black, black, blue,    blue,  green, green}
      };
   
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 10.0)));
      
      weights = slot2[w][z];
   }
   
   else if(phosphor_layout == 18){
      // same as above but for RBG panels
      vec3 slot2[4][10] = {
         {red,   yellow, green, blue,  blue,  red,   yellow, green, blue,  blue },
         {black, green,  green, blue,  blue,  red,   red,    black, black, black},
         {red,   yellow, green, blue,  blue,  red,   yellow, green, blue,  blue },
         {red,   red,    black, black, black, black, green,  green, blue,  blue }
      };
   
      w = int(floor(mod(coord.y, 4.0)));
      z = int(floor(mod(coord.x, 10.0)));
      
      weights = slot2[w][z];
   }
   
   else if(phosphor_layout == 19){
      // slot_3_7x6_rgb
      vec3 slot[6][14] = {
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {red,   red,   yellow, green, cyan,  blue,  blue,  black, black, black,  black,  black, black, black},
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {red,   red,   yellow, green, cyan,  blue,  blue,  red,   red,   yellow, green,  cyan,  blue,  blue},
         {black, black, black,  black, black, black, black, black, red,   red,    yellow, green, cyan,  blue}
      };
      
      w = int(floor(mod(coord.y, 6.0)));
      z = int(floor(mod(coord.x, 14.0)));
      
      weights = slot[w][z];
   }

   return weights;
}

void main()
{
   FragColor = vec4(texture(Source, vTexCoord).rgb * mask_weights(vTexCoord.xy * params.OutputSize.xy, params.mask_strength, mask_picker), 1.0);
}

I set the mask strength to a default of 50% to make it brighter for you and defaulted it to mask type 17.

EDIT: the shader from the OP is in the slang repo now. Itā€™s called ā€˜vt220ā€™ and it has a couple of nice new additions.

2 Likes

Wow, thanks man! Now this preset is my favorite one. Very close to original CRT TV that I have with this settings.

about vt220 - nice addition. MASK 19 somehow is very slow, compared to other masks. Black&White and QUALITY only work with NTSC toggle.

1 Like

Some masks are faster then others it has to do with how the mask is made/applied.

The black/white and quality only working with ntsc enabled is most likely because they are in the same block of code that the ntsc code is a part of, so you disable ntsc and all the things that are a part of that code block are automatically disabled as well.

2 Likes