Dogway's grading shader (slang)

Thanks! Your work is greatly appreciated.

I made the following presets using Fudoh’s 240p test suite. I adjusted LCD gamma and black level to match the SMPTE standard. I also maximized the beam width variance while avoiding clipping of the scanlines or raising the “scanline dark” parameter, which can cause a loss of detail in very dark areas. I recommend cranking up the backlight, but normal backlight settings should be adequately bright.

If colors look under/oversaturated, it’s likely a color temp issue. I’ve set the color temp to ~7900K by default; this may need to be raised or lowered depending on the display being used.

One of the things I’m curious about is how display-specific the LCD gamma and black level settings are. If/when you get the time, I’d appreciate any feedback you can give me on these. I tested these at 1080p at 5x scale, not sure if scale matters or not for scanline variance (something I still need to test).

POW:

shaders = "8"
shader0 = "shaders_slang/misc/grade.slang"
filter_linear0 = "true"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = "WhitePointPass"
float_framebuffer0 = "false"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "shaders_slang/crt/shaders/guest/afterglow.slang"
filter_linear1 = "true"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = "AfterglowPass"
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "shaders_slang/crt/shaders/guest/avg-lum.slang"
filter_linear2 = "true"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "true"
alias2 = "AvgLumPass"
float_framebuffer2 = "true"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "shaders_slang/crt/shaders/guest/linearize.slang"
filter_linear3 = "true"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = "LinearizePass"
float_framebuffer3 = "true"
srgb_framebuffer3 = "false"
scale_type_x3 = "source"
scale_x3 = "1.000000"
scale_type_y3 = "source"
scale_y3 = "1.000000"
shader4 = "shaders_slang/crt/shaders/guest/blur_horiz.slang"
filter_linear4 = "true"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "false"
alias4 = ""
float_framebuffer4 = "true"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "1.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "shaders_slang/crt/shaders/guest/blur_vert.slang"
filter_linear5 = "true"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = "GlowPass"
float_framebuffer5 = "true"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "1.000000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/crt/shaders/guest/linearize_scanlines.slang"
filter_linear6 = "true"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = ""
float_framebuffer6 = "true"
srgb_framebuffer6 = "false"
scale_type_x6 = "source"
scale_x6 = "1.000000"
scale_type_y6 = "source"
scale_y6 = "1.000000"
shader7 = "shaders_slang/crt/shaders/guest/crt-guest-dr-venom.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = ""
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "viewport"
scale_x7 = "1.000000"
scale_type_y7 = "viewport"
scale_y7 = "1.000000"
parameters = "g_gamma_out;g_gamma_in;g_gamma_type;g_vignette;g_vstr;g_vpower;g_csize;g_bsize;g_crtgamut;wp_temperature;g_sat;g_vibr;g_lum;g_cntrst;g_mid;g_lift;blr;blg;blb;wlr;wlg;wlb;rg;rb;gr;gb;br;bg;LUT_Size1;LUT1_toggle;LUT_Size2;LUT2_toggle;SW;AR;PR;AG;PG;AB;PB;sat;lsmooth;GAMMA_INPUT;TAPSH;GLOW_FALLOFF_H;TAPSV;GLOW_FALLOFF_V;TATE;IOS;OS;BLOOM;brightboost;brightboost1;gsl;scanline1;scanline2;beam_min;beam_max;beam_size;h_sharp;s_sharp;csize;bsize;warpX;warpY;glow;shadowMask;masksize;vertmask;slotmask;slotwidth;double_slot;slotms;mcut;maskDark;maskLight;CGWG;gamma_out;spike;inter;interm;bloom;scans"
g_gamma_out = "2.200000"
g_gamma_in = "2.400000"
g_gamma_type = "0.000000"
g_vignette = "0.000000"
g_vstr = "40.000000"
g_vpower = "0.200000"
g_csize = "0.000000"
g_bsize = "600.000000"
g_crtgamut = "1.000000"
wp_temperature = "7943.000000"
g_sat = "0.000000"
g_vibr = "-0.000000"
g_lum = "0.000000"
g_cntrst = "0.000000"
g_mid = "0.500000"
g_lift = "0.000000"
blr = "0.000000"
blg = "0.000000"
blb = "0.000000"
wlr = "1.000000"
wlg = "1.000000"
wlb = "1.000000"
rg = "0.000000"
rb = "0.000000"
gr = "0.000000"
gb = "0.000000"
br = "0.000000"
bg = "0.000000"
LUT_Size1 = "16.000000"
LUT1_toggle = "0.000000"
LUT_Size2 = "64.000000"
LUT2_toggle = "0.000000"
SW = "1.000000"
AR = "0.070000"
PR = "0.050000"
AG = "0.070000"
PG = "0.050000"
AB = "0.070000"
PB = "0.050000"
sat = "0.100000"
lsmooth = "0.900000"
GAMMA_INPUT = "2.200000"
TAPSH = "4.000000"
GLOW_FALLOFF_H = "0.000000"
TAPSV = "4.000000"
GLOW_FALLOFF_V = "0.000000"
TATE = "0.000000"
IOS = "0.000000"
OS = "1.000000"
BLOOM = "0.000000"
brightboost = "1.000000"
brightboost1 = "1.000000"
gsl = "2.000000"
scanline1 = "15.000000"
scanline2 = "5.000000"
beam_min = "1.000001"
beam_max = "0.850000"
beam_size = "0.000000"
h_sharp = "4.000000"
s_sharp = "0.000000"
csize = "0.000000"
bsize = "600.000000"
warpX = "0.000000"
warpY = "0.000000"
glow = "0.000000"
shadowMask = "-1.000000"
masksize = "1.000000"
vertmask = "0.000000"
slotmask = "0.000000"
slotwidth = "2.000000"
double_slot = "1.000000"
slotms = "1.000000"
mcut = "0.000000"
maskDark = "0.500000"
maskLight = "1.500000"
CGWG = "0.500000"
gamma_out = "2.200000"
spike = "1.000000"
inter = "400.000000"
interm = "1.000000"
bloom = "0.000000"
scans = "0.000000"
textures = "SamplerLUT1;SamplerLUT2"
SamplerLUT1 = "shaders_slang/crt/shaders/guest/lut/sony_trinitron1.png"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT1_mipmap = "false"
SamplerLUT2 = "shaders_slang/crt/shaders/guest/lut/sony_trinitron2.png"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT2_mipmap = "false"

SMPTE-C:

shaders = "8"
shader0 = "shaders_slang/misc/grade.slang"
filter_linear0 = "true"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = "WhitePointPass"
float_framebuffer0 = "false"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "shaders_slang/crt/shaders/guest/afterglow.slang"
filter_linear1 = "true"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = "AfterglowPass"
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "shaders_slang/crt/shaders/guest/avg-lum.slang"
filter_linear2 = "true"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "true"
alias2 = "AvgLumPass"
float_framebuffer2 = "true"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "shaders_slang/crt/shaders/guest/linearize.slang"
filter_linear3 = "true"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = "LinearizePass"
float_framebuffer3 = "true"
srgb_framebuffer3 = "false"
scale_type_x3 = "source"
scale_x3 = "1.000000"
scale_type_y3 = "source"
scale_y3 = "1.000000"
shader4 = "shaders_slang/crt/shaders/guest/blur_horiz.slang"
filter_linear4 = "true"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "false"
alias4 = ""
float_framebuffer4 = "true"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "1.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "shaders_slang/crt/shaders/guest/blur_vert.slang"
filter_linear5 = "true"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = "GlowPass"
float_framebuffer5 = "true"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "1.000000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/crt/shaders/guest/linearize_scanlines.slang"
filter_linear6 = "true"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = ""
float_framebuffer6 = "true"
srgb_framebuffer6 = "false"
scale_type_x6 = "source"
scale_x6 = "1.000000"
scale_type_y6 = "source"
scale_y6 = "1.000000"
shader7 = "shaders_slang/crt/shaders/guest/crt-guest-dr-venom.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = ""
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "viewport"
scale_x7 = "1.000000"
scale_type_y7 = "viewport"
scale_y7 = "1.000000"
parameters = "g_gamma_out;g_gamma_in;g_gamma_type;g_vignette;g_vstr;g_vpower;g_csize;g_bsize;g_crtgamut;wp_temperature;g_sat;g_vibr;g_lum;g_cntrst;g_mid;g_lift;blr;blg;blb;wlr;wlg;wlb;rg;rb;gr;gb;br;bg;LUT_Size1;LUT1_toggle;LUT_Size2;LUT2_toggle;SW;AR;PR;AG;PG;AB;PB;sat;lsmooth;GAMMA_INPUT;TAPSH;GLOW_FALLOFF_H;TAPSV;GLOW_FALLOFF_V;TATE;IOS;OS;BLOOM;brightboost;brightboost1;gsl;scanline1;scanline2;beam_min;beam_max;beam_size;h_sharp;s_sharp;csize;bsize;warpX;warpY;glow;shadowMask;masksize;vertmask;slotmask;slotwidth;double_slot;slotms;mcut;maskDark;maskLight;CGWG;gamma_out;spike;inter;interm;bloom;scans"
g_gamma_out = "1.800000"
g_gamma_in = "2.400000"
g_gamma_type = "2.000000"
g_vignette = "0.000000"
g_vstr = "40.000000"
g_vpower = "0.200000"
g_csize = "0.000000"
g_bsize = "600.000000"
g_crtgamut = "1.000000"
wp_temperature = "7943.000000"
g_sat = "0.000000"
g_vibr = "-0.000000"
g_lum = "0.000000"
g_cntrst = "0.000000"
g_mid = "0.500000"
g_lift = "-0.280000"
blr = "0.000000"
blg = "0.000000"
blb = "0.000000"
wlr = "1.000000"
wlg = "1.000000"
wlb = "1.000000"
rg = "0.000000"
rb = "0.000000"
gr = "0.000000"
gb = "0.000000"
br = "0.000000"
bg = "0.000000"
LUT_Size1 = "16.000000"
LUT1_toggle = "0.000000"
LUT_Size2 = "64.000000"
LUT2_toggle = "0.000000"
SW = "1.000000"
AR = "0.070000"
PR = "0.050000"
AG = "0.070000"
PG = "0.050000"
AB = "0.070000"
PB = "0.050000"
sat = "0.100000"
lsmooth = "0.900000"
GAMMA_INPUT = "2.200000"
TAPSH = "4.000000"
GLOW_FALLOFF_H = "0.000000"
TAPSV = "4.000000"
GLOW_FALLOFF_V = "0.000000"
TATE = "0.000000"
IOS = "0.000000"
OS = "1.000000"
BLOOM = "0.000000"
brightboost = "1.000000"
brightboost1 = "1.000000"
gsl = "2.000000"
scanline1 = "15.000000"
scanline2 = "5.000000"
beam_min = "1.000001"
beam_max = "0.850000"
beam_size = "0.000000"
h_sharp = "4.000000"
s_sharp = "0.000000"
csize = "0.000000"
bsize = "600.000000"
warpX = "0.000000"
warpY = "0.000000"
glow = "0.000000"
shadowMask = "-1.000000"
masksize = "1.000000"
vertmask = "0.000000"
slotmask = "0.000000"
slotwidth = "2.000000"
double_slot = "1.000000"
slotms = "1.000000"
mcut = "0.000000"
maskDark = "0.500000"
maskLight = "1.500000"
CGWG = "0.500000"
gamma_out = "2.200000"
spike = "1.000000"
inter = "400.000000"
interm = "1.000000"
bloom = "0.000000"
scans = "0.000000"
textures = "SamplerLUT1;SamplerLUT2"
SamplerLUT1 = "shaders_slang/crt/shaders/guest/lut/sony_trinitron1.png"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT1_mipmap = "false"
SamplerLUT2 = "shaders_slang/crt/shaders/guest/lut/sony_trinitron2.png"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT2_mipmap = "false"

sRGB:

shaders = "8"
shader0 = "shaders_slang/misc/grade.slang"
filter_linear0 = "true"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = "WhitePointPass"
float_framebuffer0 = "false"
srgb_framebuffer0 = "false"
scale_type_x0 = "source"
scale_x0 = "1.000000"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "shaders_slang/crt/shaders/guest/afterglow.slang"
filter_linear1 = "true"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = "AfterglowPass"
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "shaders_slang/crt/shaders/guest/avg-lum.slang"
filter_linear2 = "true"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "true"
alias2 = "AvgLumPass"
float_framebuffer2 = "true"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "shaders_slang/crt/shaders/guest/linearize.slang"
filter_linear3 = "true"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = "LinearizePass"
float_framebuffer3 = "true"
srgb_framebuffer3 = "false"
scale_type_x3 = "source"
scale_x3 = "1.000000"
scale_type_y3 = "source"
scale_y3 = "1.000000"
shader4 = "shaders_slang/crt/shaders/guest/blur_horiz.slang"
filter_linear4 = "true"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "false"
alias4 = ""
float_framebuffer4 = "true"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "1.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "shaders_slang/crt/shaders/guest/blur_vert.slang"
filter_linear5 = "true"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = "GlowPass"
float_framebuffer5 = "true"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "1.000000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/crt/shaders/guest/linearize_scanlines.slang"
filter_linear6 = "true"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = ""
float_framebuffer6 = "true"
srgb_framebuffer6 = "false"
scale_type_x6 = "source"
scale_x6 = "1.000000"
scale_type_y6 = "source"
scale_y6 = "1.000000"
shader7 = "shaders_slang/crt/shaders/guest/crt-guest-dr-venom.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = ""
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "viewport"
scale_x7 = "1.000000"
scale_type_y7 = "viewport"
scale_y7 = "1.000000"
parameters = "g_gamma_out;g_gamma_in;g_gamma_type;g_vignette;g_vstr;g_vpower;g_csize;g_bsize;g_crtgamut;wp_temperature;g_sat;g_vibr;g_lum;g_cntrst;g_mid;g_lift;blr;blg;blb;wlr;wlg;wlb;rg;rb;gr;gb;br;bg;LUT_Size1;LUT1_toggle;LUT_Size2;LUT2_toggle;SW;AR;PR;AG;PG;AB;PB;sat;lsmooth;GAMMA_INPUT;TAPSH;GLOW_FALLOFF_H;TAPSV;GLOW_FALLOFF_V;TATE;IOS;OS;BLOOM;brightboost;brightboost1;gsl;scanline1;scanline2;beam_min;beam_max;beam_size;h_sharp;s_sharp;csize;bsize;warpX;warpY;glow;shadowMask;masksize;vertmask;slotmask;slotwidth;double_slot;slotms;mcut;maskDark;maskLight;CGWG;gamma_out;spike;inter;interm;bloom;scans"
g_gamma_out = "1.800000"
g_gamma_in = "2.400000"
g_gamma_type = "1.000000"
g_vignette = "0.000000"
g_vstr = "40.000000"
g_vpower = "0.200000"
g_csize = "0.000000"
g_bsize = "600.000000"
g_crtgamut = "1.000000"
wp_temperature = "7943.000000"
g_sat = "0.000000"
g_vibr = "-0.000000"
g_lum = "0.000000"
g_cntrst = "0.000000"
g_mid = "0.500000"
g_lift = "-0.020000"
blr = "0.000000"
blg = "0.000000"
blb = "0.000000"
wlr = "1.000000"
wlg = "1.000000"
wlb = "1.000000"
rg = "0.000000"
rb = "0.000000"
gr = "0.000000"
gb = "0.000000"
br = "0.000000"
bg = "0.000000"
LUT_Size1 = "16.000000"
LUT1_toggle = "0.000000"
LUT_Size2 = "64.000000"
LUT2_toggle = "0.000000"
SW = "1.000000"
AR = "0.070000"
PR = "0.050000"
AG = "0.070000"
PG = "0.050000"
AB = "0.070000"
PB = "0.050000"
sat = "0.100000"
lsmooth = "0.900000"
GAMMA_INPUT = "2.200000"
TAPSH = "4.000000"
GLOW_FALLOFF_H = "0.000000"
TAPSV = "4.000000"
GLOW_FALLOFF_V = "0.000000"
TATE = "0.000000"
IOS = "0.000000"
OS = "1.000000"
BLOOM = "0.000000"
brightboost = "1.000000"
brightboost1 = "1.000000"
gsl = "2.000000"
scanline1 = "15.000000"
scanline2 = "5.000000"
beam_min = "1.000001"
beam_max = "0.850000"
beam_size = "0.000000"
h_sharp = "4.000000"
s_sharp = "0.000000"
csize = "0.000000"
bsize = "600.000000"
warpX = "0.000000"
warpY = "0.000000"
glow = "0.000000"
shadowMask = "-1.000000"
masksize = "1.000000"
vertmask = "0.000000"
slotmask = "0.000000"
slotwidth = "2.000000"
double_slot = "1.000000"
slotms = "1.000000"
mcut = "0.000000"
maskDark = "0.500000"
maskLight = "1.500000"
CGWG = "0.500000"
gamma_out = "2.200000"
spike = "1.000000"
inter = "400.000000"
interm = "1.000000"
bloom = "0.000000"
scans = "0.000000"
textures = "SamplerLUT1;SamplerLUT2"
SamplerLUT1 = "shaders_slang/crt/shaders/guest/lut/sony_trinitron1.png"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT1_mipmap = "false"
SamplerLUT2 = "shaders_slang/crt/shaders/guest/lut/sony_trinitron2.png"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT2_mipmap = "false"
5 Likes

I tested your configs and for me they are a little bit dark but as you said it depends heavily on the display settings. For example in the castlevania example (I used snes since I have saturn with gl) I can’t see the last shade of red in the sky, my example might be a little too bright for your display though.

I wanted to compare this with Donkey Kong 2, as it was my benchmark game. On my display all your configs clip blacks, mine could be a little bit too bright maybe but all details in the shades can be hinted, I think that should be the target, hint the darkest shades. Test with this game if you can and tell how it shows in your display.

My config (SMPTE)----------------------------------------SMPTE

sRGB-------------------------------------------------------------POW

My config (SMPTE)----------------------------------------SMPTE

sRGB-------------------------------------------------------------POW

3 Likes

Thanks for taking the time to test those!

I’m not sure what the correct approach is, because obviously there is some lost detail with the settings I posted, but I think those details would also be lost on a properly calibrated CRT.

When a monitor is properly adjusted, the rightmost pluge bar should be just barely visible, while the left two should appear indistinguishable from each other and completely black. (https://en.wikipedia.org/wiki/SMPTE_color_bars)

Below are the results using SMPTE-C with Fudoh’s 240p test suite in SNES9x with default color palette. Depending on the display, you might be able to raise black level by one or two notches before the black bars unmerge. In other words, I don’t think these settings are actually very display-dependent; they should be fairly universal but some additional tests are needed using other other displays.

SMPTE-C

If you look closely at the upper left in the DKC2 screen, you’ll see that it’s actually got a lot of garbled nonsense and a lot of it is very blocky and awkward, like the stuff that gets cropped at the top/bottom of games sometimes. Check out the bottom-right of the chest; it’s just weird blocky stuff that gets lost. I don’t think this stuff is supposed to even be visible, and it all disappears when black level is corrected.

The other problem I saw with SMPTE-C was just a general lack of saturation, which is mostly fixed by correcting the black level.

So, I’m not sure what to think except that some detail would be lost on a properly calibrated CRT. It’s definitely weird, though, and I could be completely wrong. :man_shrugging:

1 Like

Yes, I guess it’s a matter of goals. If your goal is to calibrate a virtual NTSC standard CRT, using color bars is the way to go and that’s totally fine. Personally I’m calibrating against games directly and also some personal flare to my display. I marked in bold that my screens were a tad too bright, but I’m also saying this from my 2.2 gamma monitor, and not my 2.4 gamma HDTV where things should look darker. In that sense I probably should have set LCD gamma to 2.40 and not 2.20 but since I mostly tune settings on my PC I left it so for convenience.

If I could I would target a C1 NES TV and a SF1 SNES TV. I think those were used by developers.

This is with gamma 2.4 corrected. The chest macroblock is not going to disappear unless you clip a lot whole of detail

And this is less washed out (as I tend to like that look) probably what you might want to target.

3 Likes

Ok, now I get it. I didn’t realize that’s what you were doing but that makes sense now. That’s a pretty unique approach.

I’d definitely be interested in some color LUTs for those displays.

Yeah that DKC2 title screen is strange. With black level corrected to the NTSC standard none of that weird stuff is visible, including the macro block on the chest. The red shade in the Castlevania shot is more troubling. Maybe these graphics were included by the developers as a quick and easy way to calibrate brightness while working on the game…? It’s also possible that they just never properly graded their games for NTSC, but that seems pretty unlikely.

2 Likes

There’s a substantial number of people who do audio mixing who prefer to calibrate their final mixes to crummy consumer speakers (nowadays many of them use basic Apple earbuds) because they know that’s what most people will be consuming it on and they want it to sound best there, even at the expense of people with the equipment and acumen to appreciate a technically better mix (this is part of what makes Steely Dan such a darling of audiophiles; they never composed nor mixed to the lowest common denominator).

I suspect many games were made with that same concept in mind, and this applies to many aspects of production (areas which can be of great contention these days), including aspect ratio, colors, etc.

That’s not to say that we shouldn’t try to make these things look/sound as good as we can, but I do think it precludes being technically able to find any one, single true/correct value that applies to all content all the time.

Just as listening to one of the mixed-for-crap-speakers songs/albums on a really neutral/accurate system tends to reveal the warts, I think we can inadvertently reveal things like the chest macro block and/or ugly color combinations, etc. with properly calibrated, high-quality displays.

3 Likes

Yes, who knows, the standard for Japanese CRTs were D93 I think not D65. And the function transform wasn’t specified but these special TVs were targeted to videogame screenshots and so they were representative of a consumer TV with all its brightness quirks. So it won’t look the same than in a PVM for sure.

I guess it depends, I work backwards from the image to the function transform as a mean of reverse engineering trying to have the wider dynamic range in the image while still keeping some contrast, in the end you will end with something that might be too bright because the darkest game forced you to push gamma that way. There’s no right or wrong, every developer set his own standard.

EDIT: By the way, I updated glass.glsl to fix the reflection blur setting, colored the outer reflection to some pleasing blue hue and added a slider to switch from curved reflection to flat (want to use it also for handhelds). Still have an ugly deep black issue with the masks…

3 Likes

Good point. If a higher black level reveals more detail, and it looks like stuff that should be displayed, then go for it. It’s not like the NTSC police are going to show up at your door and fine you :stuck_out_tongue:

I’m not sure we’ll ever know what’s up with that DKC2 screen though. It’s possible the artists intended for these graphics to be displayed with a (much) higher black level than the NTSC standard, but those extra details are quite ugly and don’t seem like they’re even part of the artwork, and all that stuff goes away with the correct black level, so I’m more inclined to think that this stuff isn’t meant to be visible at all and is actually there to help the artists properly adjust their monitors without needing to display a test pattern. I’m just speculating, though.

I do think it’s useful to have a preset that is NTSC-compliant, even if it’s only used as a starting point. Games either adhered to this standard or the developers did their own thing, so the NTSC settings will work fine most of the time but some games may need game-specific adjustments depending on what the artists did and individual preferences.

For the three presets I posted, I adjusted LCD gamma and black level to comply with the NTSC standard, and scanline beam variance has been maximized as much as possible without introducing clipping or increasing the “scanline dark” parameter. IDK, are these useful enough to be included in the presets directory? Maybe they’re not as spicy as some of the other presets, but they’re accurate.

1 Like

Sure. I’m not super-particular about what goes into the presets directory. I’m much more protective of trying to minimize duplicated code elsewhere in the repo, lest we end up with 10x copies of crt-royale with nothing changed but 2-3 SLOC per.

If @Dogway feels his grade shader is in a good place, we can throw it in there, too. I was mostly waiting on it to settle down to minimize the number of commits while it’s in flux.

3 Likes

The deeper I dive into this the more confused I become. Instructions for using SMPTE color bars say to lower black level until black and superblack are indistinguishable from each other. However, when you do this on a CRT (or LCD) you get pretty severe black crush and the bottom end of the gray scale becomes very non-linear. In practice, most CRTs had a black level higher than the NTSC standard. So what’s the point of the NTSC standard, then? Should we just ignore SMPTE color bars? Was this standard ever really intended for CRTs, or is this maybe something that is intended more for 35mm projectors? Would we be better off calibrating black level with a gray scale test pattern?

I calibrated black level using gray ramp until all shades were visible and landed on LCD gamma 1.80 and black level -10 with SMPTE-C mode. I have to admit, I like this better than the image with the correct NTSC black level, even if you can still see that pesky macro block :stuck_out_tongue: Also added a mask effect because why not.

2 Likes

This article was very informative.

Basically, NTSC was never a real standard that was actually used (lol) because technology was never able to meet that standard, and SMPTE-C is very similar to sRGB, with sRGB actually being a slightly wider gamut. So… just use sRGB? :joy:

Good times.

NTSC Color Gamut

The first official Color Gamut Standard for displays was the NTSC Color Gamut, which made its debut in 1953 for the beginning of US color television broadcasting. But the NTSC primary colors were too saturated and couldn’t be made bright enough for use in the consumer (CRT) TVs of that era, so the NTSC Color Gamut was Never actually used for volume commercial production of color TVs. As a result, the NTSC Gamut was Never really an actual Standard Color Gamut, and there is essentially no consumer content based on the true NTSC Color Gamut.”

“ Instead of the official NTSC Gamut colors, the practical phosphor colors that were actually used in early color TVs were developed by the Conrac Corporation, which eventually became the SMPTE-C Color Gamut Standard. TV production studios used Conrac color monitors to produce their broadcast TV content, so it was the Conrac Color Gamut rather than the NTSC Gamut that was the real color television Standard Gamut. The SMPTE-C Gamut is not that different from today’s sRGB / Rec.709 Gamut, which is 13 percent larger than SMPTE-C.”

http://www.displaymate.com/Display_Color_Gamuts_1.htm

3 Likes

Given the earth-shattering revelations in my previous post, I’m still very confused about the SMPTE-C mode and why there’s such a large discrepancy between it and sRGB.

I take it that the point of the SMPTE-C mode is to show the colors as they would actually appear on a CRT with SMPTE-C phosphors.

Since sRGB is a slightly wider gamut, you’d expect the colors (by default, no shaders) would be slightly more saturated compared to what you’d see on a CRT with SMPTE-C phosphors. I guess the SMPTE-C mode is supposed to correct the oversaturation…?

So, it makes sense that the SMPTE-C mode appears less saturated compared to the sRGB mode, but it’s a really large difference even after adjusting my TV settings accordingly and… am I really doing this right?

@Dogway if you get a chance can you post some reference shots just showing the color differences, sans CRT effects?

@Nesguy, I updated grade with a bunch of color gamuts, I think they are more accurate than the ones included in venom’s but who knows, at least they show a visible change in colors. The one not used at all is NTSC-FCC with illuminant C, that’s the 1953 one, I included it as gamut #2 just for the curious. gamut #1 (P22) is very similar to #3 (SMPTE). Also included #4 for Japanese phosphors, and #5 for EBU (PAL). If you find me some Conrac phosphor primaries I can convert it to a color matrix.

To go along I also added plane control over YIQ or YUV for a more analogue feel. But That forced me to remove a few settings as I topped the max limit of 32 pragma settings. Tell me if you miss them, or if you prefer the YIQ control for a variety aside the more common grade color functions.

By the way, I think that after I settle on this color gamut thing the shader would be ready to upload to official repo, if everyone agrees on settings and last changes.

@Nesguy, download again now, I made a mess when updating (I fetched it instead)

1 Like

If you just move the parameters to the global struct, you can have as many as you want (up to like 128 or something, I think…?)

2 Likes

To answer your question (I was absent studying and making this shader) I took several shots with their gamut and likely function transform. Since DKC2 is an European game I think EBU makes more sense(?). So I guess you can start with a “correct” template and then tweak from there to whim.

EDIT: I think I forgot to set it to D65 as it’s the spec for those.

sRGB gamut/function transform

SMPTE-C gamut/function transform

EBU gamut/sRGB function transform

2 Likes

So for the content to look correct we need to use the right color gamut and the right function transform?

What CRT gamut should we use for sRGB? P22? (my understanding is that sRGB has basically the same gamut as a typical CRT display) I’m guessing the gamut used for POW gamma will be the same as that used for sRGB gamma?

It’s my understanding that SMPTE-C = Conrac phosphors.

I’m not sure I’m matching the gamuts with the right function transforms because I’m still winding up with crushed blacks, elevated black levels and clipping with some combinations. Is that to be expected?

2 Likes

The sRGB gamut was derived from CRT gamuts, it was a consensus between EBU and SMPTE-C (and some Japanese standard I think). If you play mostly Japanese games I think you can use the Japanese gamut standard (until I find those NES TV phosphor primaries!), if you play american games SMPTE gamut. I don’t know if someone can confirm but game localization didn’t involve gamut mapping in the 90’s right?

As for the function transforms, I think keeping with sRGB is a safe bet. The power function has some issues and it just doesn’t look good. The SMPTE-C transform… yeah… food for thought. I think and I might test (I mentioned it some time before in this thread), it’s compensating for the IRE 7.5. So probably I shouldn’t convert back from… uhm I removed the PC_to_TV() call leaving the inverse transform to PC levels and it crushes values in a manner that makes sense.

EDIT:For the time being give me a day or two to clear this issue and check what is happening, it has to do with this latter transform I added today. If you just want to test the gamuts I PM you without that function call.

Check these pics and compare with the ones above (macroblock is gone):

SMPTE function transform (with clipped TV levels)

sRGB function transform (with clipped TV levels)

Power function transform -for comparison- (old default, no clipping values)

3 Likes

Yes, both SMPTE and sRGB function transforms are looking much better in the most recent examples! Still a few things here and there that maybe shouldn’t be visible in the SMPTE shot but it’s a big improvement.

I agree that the 7.5 IRE thing probably has something to do with this.

2 Likes

Loving this so much. Having a great time. Syh sent me a folder that helped me out quite a bit. It helped me to figure out what locations mean what. No complaints, great thing you made here. Just a question:

When I use “crt-royale_braun-ntsc-256px-320px-svideo-grade_1080p_slotmsk.slangp” I get this massive mask. Is it intended to look like that? My monitor is 4K, so I know if there is a problem, it’s probably that. But is there some number I can tweak to make it look like originally intended. Maybe reduce triad size or something? I did try that, but is there some sort of general rule when using 1080 shaders on a 4K monitor, like “double or half every parameter” or something? But hey if this is how it’s supposed to look, I don’t hate it. I just figured the mask looks a bit intrusive.

3 Likes

Hey thanks for liking it, it’s become a bit of a monster but I think it pays off. EDIT: to anyone reading; want to hear how’s the performance of this on weaker machines to check if I should optimize further.

That preset as the name suggests is for 1080p because it uses a B&W pattern mask. If you are on a 4K you can do much better with the standard royale (RGB pattern and settings) “crt-royale-ntsc-256px-320px-svideo-grade”.

It has settings for pattern tiling to accomodate a 4K, I forgot the exact settings but I think there’s a guide somewhere. On one of the files it reads:

//  MaxTriadSize    BlurSize    MinTriadCountsByResolution
//  3.0             9.0         480/640/960/1920 triads at 1080p/1440p/2160p/4320p, 4:3 aspect
//  6.0             17.0        240/320/480/960 triads at 1080p/1440p/2160p/4320p, 4:3 aspect
//  9.0             25.0        160/213/320/640 triads at 1080p/1440p/2160p/4320p, 4:3 aspect
//  12.0            31.0        120/160/240/480 triads at 1080p/1440p/2160p/4320p, 4:3 aspect
//  18.0            43.0        80/107/160/320 triads at 1080p/1440p/2160p/4320p, 4:3 aspect
2 Likes