The code:
#version 450
layout(push_constant) uniform Push
{
uint FrameCount;
float HALATION_INTENSITY;
float HALATION_RADIUS;
float HALATION_SAMPLES;
float HALATION_RANDOM;
float HALATION_DEBUG;
float SHARPNESS_H;
float SHARPNESS_V;
float BLUR_H;
float BLUR_V;
float GAMMA_INPUT;
float GAMMA_OUTPUT;
float GAMMA_SAMP;
float RED_LVL;
float GREEN_LVL;
float BLUE_LVL;
float BRIGHTNESS_LVL;
float CONTRAST_LVL;
float SATURATION_LVL;
float COLOR_BLEED;
float UNPURE_BLACKS;
float PHOSPHOR_SIZE;
float SPLIT;
float PHOSPHORS;
float TRIAD_INV;
float MASK;
float SCANLINES;
float SCAN_THICK;
float FIELD_SHIFT;
} params;
#pragma parameter HALATION_INTENSITY "Halation Intensity" 0.15 0.0 2.0 0.05
#pragma parameter HALATION_RADIUS "Halation Radius" 7.0 0.0 50.0 1.0
#pragma parameter HALATION_SAMPLES "Number of Halation Samples" 28.0 4.0 800.0 4.0
#pragma parameter HALATION_RANDOM "Halation Randomness" 1.0 0.0 1.0 1.0
#pragma parameter HALATION_DEBUG "Halation Output" 0.0 0.0 1.0 1.0
#pragma parameter SHARPNESS_H "Sharpness Horizontal" 0.0 0.0 1.0 0.1
#pragma parameter SHARPNESS_V "Sharpness Vertical" 0.0 0.0 1.0 0.1
#pragma parameter BLUR_H "Horizontal Blur" 1.0 0.0 10.0 0.1
#pragma parameter BLUR_V "Vertical Blur" 1.0 0.0 10.0 0.1
#pragma parameter GAMMA_INPUT "Gamma Input" 2.5 0.1 5.0 0.1
#pragma parameter GAMMA_OUTPUT "Gamma Output" 2.2 0.1 5.0 0.1
#pragma parameter GAMMA_SAMP "Sampling Gamma" 2.0 0.1 5.0 0.1
#pragma parameter RED_LVL "Red Level" 1.0 0.0 2.0 0.1
#pragma parameter GREEN_LVL "Green Level" 1.0 0.0 2.0 0.1
#pragma parameter BLUE_LVL "Blue Level" 1.0 0.0 2.0 0.1
#pragma parameter BRIGHTNESS_LVL "Brightness" 5.0 0.0 10.0 0.1
#pragma parameter CONTRAST_LVL "Contrast" 5.0 0.0 10.0 0.1
#pragma parameter SATURATION_LVL "Saturation" 5.0 0.0 10.0 0.1
#pragma parameter COLOR_BLEED "Color Bleed" 0.2 0.0 1.0 0.1
#pragma parameter UNPURE_BLACKS "Brightness of pure blacks" 0.0 0.0 1.0 0.1
#pragma parameter SPLIT "Split" 0.0 -1.0 1.0 0.1
#pragma parameter PHOSPHOR_SIZE "Phosphor Size" 3.0 2.0 3.0 1.0
#pragma parameter PHOSPHORS "Phosphors" 0.5 0.0 1.0 0.1
#pragma parameter TRIAD_INV "Invert Phosphor Order" 0.0 0.0 1.0 1.0
#pragma parameter MASK "Slot Mask" 0.5 0.0 1.0 0.1
#pragma parameter SCANLINES "Scanlines" 0.0 0.0 1.0 0.05
#pragma parameter SCAN_THICK "Scanline Thickness" 0.5 0.0 1.0 0.05
#pragma parameter FIELD_SHIFT "Alternating Field Shift" 0.0 0.0 2.0 0.1
layout(std140, set = 0, binding = 0) uniform UBO
{
mat4 MVP;
vec4 OutputSize;
vec4 OriginalSize;
vec4 SourceSize;
} 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;
}
/*
* CRT Shader by Doriphor
* (Based on EasyMode's CRT Shader)
* License: GPL
*
* A fancy, fast and heavily configurable CRT shader.
*/
#pragma stage fragment
layout(location = 0) in vec2 vTexCoord;
layout(location = 0) out vec4 FragColor;
layout(set = 0, binding = 2) uniform sampler2D Source;
#define PI 3.141592653589
float rand()
{
float timer = float(params.FrameCount);
return fract(sin(vTexCoord.x * vTexCoord.y * timer) * 10000.0);
}
vec4 GammaTexture(sampler2D Source, vec2 coords)
{
vec2 dx = vec2(global.SourceSize.z, 0.0);
vec2 dy = vec2(0.0, global.SourceSize.w);
vec2 pix_co = (coords + params.SHARPNESS_H/2 * dx + params.SHARPNESS_V/2 * dy) * global.SourceSize.xy - vec2(0.5, 0.5);
vec2 tex_co = (floor(pix_co) + vec2(0.5, 0.5)) * global.SourceSize.zw;
vec2 x_shift = params.FIELD_SHIFT * vec2(floor(mod(tex_co.y, 2.0 * dy.y)/dy.y)*dx.x, 0.0);
vec2 y_shift = params.FIELD_SHIFT * vec2(floor(mod(tex_co.y + dy.y, 2.0 * dy.y)/dy.y)*dx.x, 0.0);
vec2 pix_co_x = (coords + params.SHARPNESS_H/2 * dx + params.SHARPNESS_V/2 * dy + x_shift) * global.SourceSize.xy - vec2(0.5, 0.5);
vec2 tex_co_x = (floor(pix_co_x) + vec2(0.5, 0.5)) * global.SourceSize.zw;
vec2 pix_co_y = (coords + params.SHARPNESS_H/2 * dx + params.SHARPNESS_V/2 * dy + y_shift) * global.SourceSize.xy - vec2(0.5, 0.5);
vec2 tex_co_y = (floor(pix_co_y) + vec2(0.5, 0.5)) * global.SourceSize.zw;
vec2 dist = fract(pix_co);
vec2 dist_x = fract(pix_co_x);
vec2 dist_y = fract(pix_co_y);
vec3 gamma = vec3(params.GAMMA_SAMP);
vec3 col, col2, col3, col4;
col = pow(texture(Source, tex_co_x).rgb, gamma);
col2 = pow(texture(Source, tex_co_x + dx).rgb, gamma);
col3 = pow(texture(Source, tex_co_y + dy).rgb, gamma);
col4 = pow(texture(Source, tex_co_y + dx + dy).rgb, gamma);
col = mix(col, col2, (1 - params.SHARPNESS_H) * dist_x.x);
col3 = mix(col3, col4, (1 - params.SHARPNESS_H) * dist_y.x);
col = pow(mix(col, col3, (1 - params.SHARPNESS_V) * dist.y), 1/gamma);
return vec4(col, 1.0);
}
vec3 BlurSampler(vec2 coords)
{
vec2 dx = vec2(global.SourceSize.z, 0.0);
vec2 dy = vec2(0.0, global.SourceSize.w);
float x_bleed = params.BLUR_H / 20.0;
float y_bleed = params.BLUR_V / 20.0;
vec3 gamma = vec3(params.GAMMA_SAMP);
vec3 UL, U, UR, L, C, R, DL, D, DR;
UL = pow(GammaTexture(Source, coords - dx - dy ).rgb, gamma);
U = pow(GammaTexture(Source, coords - dy ).rgb, gamma);
UR = pow(GammaTexture(Source, coords + dx - dy ).rgb, gamma);
L = pow(GammaTexture(Source, coords - dx ).rgb, gamma);
C = pow(GammaTexture(Source, coords ).rgb, gamma);
R = pow(GammaTexture(Source, coords + dx ).rgb, gamma);
DL = pow(GammaTexture(Source, coords - dx + dy ).rgb, gamma);
D = pow(GammaTexture(Source, coords + dy ).rgb, gamma);
DR = pow(GammaTexture(Source, coords + dx + dy ).rgb, gamma);
U = (UL * x_bleed + U + UR * x_bleed) / (1.0 + 2.0 * x_bleed);
C = (L * x_bleed + C + R * x_bleed) / (1.0 + 2.0 * x_bleed);
D = (DL * x_bleed + D + DR * x_bleed) / (1.0 + 2.0 * x_bleed);
C = (U * y_bleed + C + D * y_bleed) / (1.0 + 2.0 * y_bleed);
return pow(C, 1.0 / gamma);
}
vec3 Halation(vec2 coords)
{
vec2 dx = vec2(global.SourceSize.z, 0.0);
vec2 dy = vec2(0.0, global.SourceSize.w);
float radius = params.HALATION_RADIUS;
float samples = params.HALATION_SAMPLES / 4.0;
float div_total = 0.0;
vec3 col = vec3(0.0, 0.0, 0.0);
float ratio = 1.0;
if(params.HALATION_RANDOM == 1.0)
ratio = rand() * 2.0;
float div = 1.0;
for( float i = 1 ; i <= samples; i++)
{
/*div = 1 / pow(radius * i / samples, 2.0);*/
div = 1/sqrt(i);
div_total += 4.0 * div;
col += texture(Source, coords + vec2(
radius * i / samples * cos(i * ratio) * dx.x,
radius * i / samples * sin(i * ratio) * dy.y
)).rgb * div;
col += texture(Source, coords - vec2(
radius * i / samples * cos(i * ratio) * dx.x,
radius * i / samples * sin(i * ratio) * dy.y
)).rgb * div;
col += texture(Source, coords + vec2(
radius * i / samples * cos(i * ratio + PI/2) * dx.x,
radius * i / samples * sin(i * ratio + PI/2) * dy.y
)).rgb * div;
col += texture(Source, coords - vec2(
radius * i / samples * cos(i * ratio + PI/2) * dx.x,
radius * i / samples * sin(i * ratio + PI/2) * dy.y
)).rgb * div;
}
col = max(col, 0.0);
col /= div_total;
col = sqrt(col);
return col;
}
/* main_fragment */
void main()
{
vec3 col;
vec3 levels = vec3(params.RED_LVL, params.GREEN_LVL, params.BLUE_LVL);
if(vTexCoord.x - params.SPLIT - 1.0 > 0.0 || vTexCoord.x - params.SPLIT < 0.0)
{
FragColor = texture(Source, vTexCoord);
}
else
{
col = BlurSampler(vTexCoord);
col = pow(col, vec3(params.GAMMA_INPUT));
float p = 1.0 - params.PHOSPHORS;
vec2 mod_fac = floor(vTexCoord * global.OutputSize.xy);
int dot_no = int(mod(mod_fac.x, params.PHOSPHOR_SIZE));
vec3 mask_weight;
if (params.PHOSPHOR_SIZE == 3.0)
{
if (params.TRIAD_INV == 0.0)
{
if (dot_no == 0)
{
mask_weight = vec3(1.0, p, p);
}
else if (dot_no == 1)
{
mask_weight = vec3(p, 1.0, p);
}
else
{
mask_weight = vec3(p, p, 1.0);
}
}
else
{
if (dot_no == 0)
{
mask_weight = vec3(p, p, 1.0);
}
else if (dot_no == 1)
{
mask_weight = vec3(p, 1.0, p);
}
else
{
mask_weight = vec3(1.0, p, p);
}
}
}
else
{
if (params.TRIAD_INV == 0.0)
{
if (dot_no == 0)
{
mask_weight = vec3(1.0, p, 1.0);
}
else
{
mask_weight = vec3(p, 1.0, p);
}
}
else
{
if (dot_no == 0)
{
mask_weight = vec3(p, 1.0, p);
}
else
{
mask_weight = vec3(1.0, p, 1.0);
}
}
}
col /= max(max(1.0, col.r), max(col.g, col.b));
col *= mask_weight;
/*col = col - vec3(params.SCANLINES * max(0.0, step(abs(cos(vTexCoord.y * global.SourceSize.y * PI)), params.SCAN_THICK)));*/
/*col = col - vec3(params.SCANLINES * max(0.0, params.SCAN_THICK - 1.0 + abs(cos(vTexCoord.y * global.SourceSize.y * PI))));*/
col -= col * max(0.0, pow(cos(vTexCoord.y * PI * 2.0 * global.OriginalSize.y), 1.0 - params.SCAN_THICK) * params.SCANLINES) ;
if (((mod(ceil(vTexCoord.y * global.OutputSize.y), params.PHOSPHOR_SIZE + 1.0) == 0.0)
|| (mod(ceil(vTexCoord.x * global.OutputSize.x / params.PHOSPHOR_SIZE), 2.0) == 0.0))
&& ((mod(ceil(vTexCoord.y * global.OutputSize.y), params.PHOSPHOR_SIZE + 1.0) == 2.0)
|| (mod(ceil(vTexCoord.x * global.OutputSize.x / params.PHOSPHOR_SIZE), 2.0) == 1.0)))
{
col = col * vec3(1 - params.MASK);
}
if (params.HALATION_INTENSITY > 0.0)
{
if(params.HALATION_DEBUG == 0.0)
col += params.HALATION_INTENSITY * pow(Halation(vTexCoord), vec3(params.GAMMA_INPUT));
else
col = Halation(vTexCoord);
}
col = max(vec3(params.UNPURE_BLACKS / 5.0), col);
/*
*OLD BRIGHTNESS AND CONTRAST, PROBABLY NOT WORTH IT
*
*float brightness = (params.BRIGHTNESS_LVL - 5.0) / 5.0;
*float contrast_fac = 10.1 * (params.CONTRAST_LVL + 5)/(10 * (15.1 - params.CONTRAST_LVL));
*col = contrast_fac * (col - vec3(0.5)) + vec3(0.5) + brightness;
*/
col *= params.CONTRAST_LVL / 5.0;
col += params.BRIGHTNESS_LVL - 5.0;
vec3 saturation = vec3(0.3 * col.r, 0.59 * col.g,0.11 * col.b) * (5.0 - params.SATURATION_LVL) / 5.0;
col = (col * params.SATURATION_LVL / 5.0) + vec3(saturation.r + saturation.g + saturation.b);
vec3 bleed = vec3(0.0);
if(params.COLOR_BLEED != 0.0)
{
if(col.r > 0.5)
{
bleed.g = (col.r - 0.5);
}
if(col.g > 0.5)
{
bleed.r = (col.g);
bleed.b = (col.g);
}
if(col.b > 0.5)
{
bleed.g = (col.b - 0.5);
}
if(params.COLOR_BLEED <= 1.0)
col = max(col, params.COLOR_BLEED * bleed);
else
col = bleed;
}
col *= levels;
col = pow(col, vec3(1.0 / params.GAMMA_OUTPUT));
FragColor = vec4(col, 1.0);
}
}