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):