I was inspired by your question and this page and wrote a quick and dirty composite shader from scratch. it’s not perfect, but it gets pretty damn close most of the time (NES requires NTSC palette): (compare to the shots on the page. You have to hover your mouse over it to get the composite version)
#version 450
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OriginalSize;
vec4 OutputSize;
uint FrameCount;
float SPLIT;
float I_SHIFT;
float Q_SHIFT;
float Y_MUL;
float I_MUL;
float Q_MUL;
} params;
#pragma parameter SPLIT "Split" 0.0 -1.0 1.0 0.1
#pragma parameter I_SHIFT "I Shift" 0.0 -1.0 1.0 0.02
#pragma parameter Q_SHIFT "Q Shift" 0.0 -1.0 1.0 0.02
#pragma parameter Y_MUL "Y Multiplier" 1.0 0.0 2.0 0.1
#pragma parameter I_MUL "I Multiplier" 1.0 0.0 2.0 0.1
#pragma parameter Q_MUL "Q Multiplier" 1.0 0.0 2.0 0.1
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;
vec4 RGB_YIQ(vec4 col)
{
mat3 conv_mat = mat3(0.299, 0.587, 0.114,
0.5959, -0.2746,-0.3213,
0.2115, -0.5227, 0.3113);
col.rgb *= conv_mat;
return col;
}
vec4 YIQ_RGB(vec4 col)
{
mat3 conv_mat = mat3(1.0, 0.956, 0.619,
1.0, -0.272, -0.647,
1.0, -1.106, 1.703);
col.rgb *= conv_mat;
return col;
}
void main()
{
#define ms *pow(10.0, -9.0)
#define MHz *pow(10.0, 9.0);
const float max_col_res = 0.5 * 52.6 ms * 315.0/88.0 MHz;
const float max_lum_res = 52.6 ms * 315.0/88.0 MHz;
const int viewport_col_res = int(ceil((params.OutputSize.x / params.OriginalSize.x) * (params.OriginalSize.x / max_col_res)));
const int viewport_lum_res = int(ceil((params.OutputSize.x / params.OriginalSize.x) * (params.OriginalSize.x / max_lum_res)));
if(vTexCoord.x - params.SPLIT - 1.0 > 0.0 || vTexCoord.x - params.SPLIT < 0.0)
{
FragColor = vec4(texture(Source, vTexCoord).rgb, 1.0);
}
else
{
vec4 col = vec4(0.0, 0.0, 0.0, 1.0);
col += RGB_YIQ(texture(Source, vTexCoord));
for(int i = 1; i < viewport_col_res; i++)
{
col.yz += RGB_YIQ(texture(Source, vTexCoord - vec2((i - viewport_col_res/2) * params.OutputSize.z, 0.0))).yz;
}
for(int i = 1; i < viewport_lum_res; i++)
{
col.x += RGB_YIQ(texture(Source, vTexCoord - vec2((i - viewport_col_res/2) * params.OutputSize.z, 0.0))).x;
}
col.yz /= viewport_col_res;
col.x /= viewport_lum_res;
col.y = mod((col.y + 1.0) + params.I_SHIFT, 2.0) - 1.0;
col.y = 0.9 * col.y + 0.1 * col.y * col.x;
col.z = mod((col.z + 1.0) + params.Q_SHIFT, 2.0) - 1.0;
col.z = 0.4 * col.z + 0.6 * col.z * col.x;
col.x += 0.5*col.y;
col.z *= params.Q_MUL;
col.y *= params.I_MUL;
col.x *= params.Y_MUL;
col = YIQ_RGB(col);
FragColor = col;
}
}