After giving it some more thought almost exact results can be achieved with stronger scanlines, no need for a special code or extra parameter. The only difference between this and a very rigorous approach is maybe a very small amount of general vertical image shift.
Hello, the sharpening works great. On my laptop with a 2060 NTSC Blend mode 1 causes what looks like tearing on the letters in Super Mario World. I had other strange artifacts in games that I think were also the cause but I can’t say for sure.
I could reproduce. Some additional color clamping was needed.
New Release Version (2021-07-16-r1):
Notable changes:
- small fix with NTSC mode 1.0
Download link:
https://mega.nz/file/51pwWYxB#9budbJsOK-BHlvC_NKrgiGSpis9aBobojMfAUXpWNPw
Get them hot fix patches!!!
As an experiment I re-created my softer more masky preset from Guest Advanced with the GV NTSC preset. I can getting nicer dark outlines (especially in 8bit games) with the NTSC and I abuse the gamma for a stronger mask in the darks. The sacrifice is details get mushed. Which do you guys prefer?
New Release Version (2021-07-17-r1):
Notable changes:
-
NTSC resolution scale option added. While true scaling tricks are possible i managed to tweak the ntsc shaders into simulated scaling, so the 1st pass x - scale can stay at 4.0x. Default value for the control parameter is 1.0 and the effect is the same as with prior versions.
-
Raising the value produces a sharper, cleaner image, but dithering becomes visible. Anti-dithering passes can still be used as prior shaders in the preset though.
-
Lowering the value can be useful for higher resolution input resolutions, for the ‘NTSC look’.
-
I didn’t find any new issues, but feedback is, as always, welcome.
Download link:
https://mega.nz/file/E1wWCIyK#4C4stc9FV7nfyemaN3p9CSH6MVliNyfDEAk2hrOL4ps
The first one looks better to me.
Imho, the first looks better almost everywhere with two exceptions imo.
The lamp posts (lamp lights w/e), and the fence both look better in the second shot imho, idk about the rest of the image, nothing “technically” wrong with it.
The scaling is neat. Here is Venom Advanced vs Venom NTSC. It’s just interesting to see what happens when you blur one and sharpen the other. First is advanced, second is NTSC.
About to look at those pictures so will add an addendum to this comment but it’d be interesting to see some ps1 shots messing with scaling and with/without ntsc as well, imho. (Just thought that’d be “more interesting”).
Edit: So… liked the shots. Uhhh, I don’t really have much to add, the sharpend ntsc (composite?) is interesting looking tbh, probably meh for dithering tho.
The shots are more to show the difference/similarities now with all the options. The NTSC filters give extra dark shading so you get thicker black lines. Both options provide different ways to beat up the image for a more natural look.
I tested the new ntsc options with the ps2 core and the code translates to the hlsl without problems, which could cause problems in general, since even a shader warning stops the shaders from being used.
The new pseudo scaling works quite well, the values are best lowered with increased internal resolution.
Screenie:
I think this topic passed some time ago also, but maybe it has some of your interest with the updated color spaces (“modern” addition).
Would you be interested to add a feature or create a separate shader in which clipping of a color profile versus the chosen color space can be displayed? For example with some preconfigured fixed color replacing the clipping colors?
I would like to measure my own monitors color space and would be interested to know how well it can display the CRT color profiles without clipping. Would be fun and useful to see in realtime which games and when are clipping colors or not.
This is a nice idea and i produced a simple debug code. It should work nicely with conjuncture with the new brightness parameter. The brightness parameter works properly only when lowering brightness and it can help to determine the amount of clipping.
Here is the modified file:
#version 450
/*
CRT Advanced Afterglow, color altering
Copyright (C) 2019-2021 guest(r) and Dr. Venom
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OriginalSize;
vec4 OutputSize;
uint FrameCount;
float TNTC;
float LS;
float LUTLOW, LUTBR;
float CP, CS;
float WP;
float wp_saturation;
float AS, sat;
float BP;
float vigstr;
float vigdef;
float sega_fix;
float pre_bb;
float dclip;
} params;
#pragma parameter AS " Afterglow Strength" 0.20 0.0 0.60 0.01
#define AS params.AS
#pragma parameter sat " Afterglow saturation" 0.20 0.0 1.0 0.01
#define sat params.sat
#pragma parameter bogus_color "[ COLOR TWEAKS ]:" 0.0 0.0 1.0 1.0
#pragma parameter CS " Display Gamut: sRGB, Modern, DCI, Adobe, Rec.2020" 0.0 0.0 4.0 1.0
#pragma parameter CP " CRT Profile: EBU | P22 | SMPTE-C | Philips | Trin." 0.0 -1.0 5.0 1.0
#define CP params.CP
#define CS params.CS
#pragma parameter TNTC " LUT Colors: Trin. | invTrin. | Nec Mult. | NTSC" 0.0 0.0 4.0 1.0
#define TNTC params.TNTC
#pragma parameter LS " LUT Size" 32.0 32.0 64.0 32.0
#define LS params.LS
#define LUTLOW 5.0 // "Fix LUT Dark - Range" from 0.0 to 50.0 - RGB singletons
#define LUTBR 1.0 // "Fix LUT Brightness" from 0.0 to 1.0
#pragma parameter WP " Color Temperature %" 0.0 -100.0 100.0 5.0
#pragma parameter wp_saturation " Saturation Adjustment" 1.0 0.0 2.0 0.05
#pragma parameter pre_bb " Brightness Adjustment" 1.0 0.0 2.0 0.005
#pragma parameter sega_fix " Sega Brightness Fix" 0.0 0.0 1.0 1.0
#pragma parameter BP " Raise Black Level" 0.0 0.0 25.0 1.0
#pragma parameter vigstr " Vignette Strength" 0.0 0.0 2.0 0.025
#pragma parameter vigdef " Vignette Definition" 7.0 0.4 15.0 0.2
#pragma parameter dclip " Determine Clipping (1.0: declip, 2.0: debug)" 0.0 0.0 2.0 1.0
#define WP params.WP
#define wp_saturation params.wp_saturation
#define BP params.BP
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 StockPass;
layout(set = 0, binding = 3) uniform sampler2D AfterglowPass;
layout(set = 0, binding = 4) uniform sampler2D SamplerLUT1;
layout(set = 0, binding = 5) uniform sampler2D SamplerLUT2;
layout(set = 0, binding = 6) uniform sampler2D SamplerLUT3;
layout(set = 0, binding = 7) uniform sampler2D SamplerLUT4;
#define COMPAT_TEXTURE(c,d) texture(c,d)
// Color profile matrices
const mat3 Profile0 =
mat3(
0.412391, 0.212639, 0.019331,
0.357584, 0.715169, 0.119195,
0.180481, 0.072192, 0.950532
);
const mat3 Profile1 =
mat3(
0.430554, 0.222004, 0.020182,
0.341550, 0.706655, 0.129553,
0.178352, 0.071341, 0.939322
);
const mat3 Profile2 =
mat3(
0.396686, 0.210299, 0.006131,
0.372504, 0.713766, 0.115356,
0.181266, 0.075936, 0.967571
);
const mat3 Profile3 =
mat3(
0.393521, 0.212376, 0.018739,
0.365258, 0.701060, 0.111934,
0.191677, 0.086564, 0.958385
);
const mat3 Profile4 =
mat3(
0.392258, 0.209410, 0.016061,
0.351135, 0.725680, 0.093636,
0.166603, 0.064910, 0.850324
);
const mat3 Profile5 =
mat3(
0.377923, 0.195679, 0.010514,
0.317366, 0.722319, 0.097826,
0.207738, 0.082002, 1.076960
);
const mat3 ToSRGB =
mat3(
3.240970, -0.969244, 0.055630,
-1.537383, 1.875968, -0.203977,
-0.498611, 0.041555, 1.056972
);
const mat3 ToModern =
mat3(
2.791723, -0.894766, 0.041678,
-1.173165, 1.815586, -0.130886,
-0.440973, 0.032000, 1.002034
);
const mat3 ToDCI =
mat3(
2.493497, -0.829489, 0.035846,
-0.931384, 1.762664, -0.076172,
-0.402711, 0.023625, 0.956885
);
const mat3 ToAdobe =
mat3(
2.041588, -0.969244, 0.013444,
-0.565007, 1.875968, -0.11836,
-0.344731, 0.041555, 1.015175
);
const mat3 ToREC =
mat3(
1.716651, -0.666684, 0.017640,
-0.355671, 1.616481, -0.042771,
-0.253366, 0.015769, 0.942103
);
// Color temperature matrices
const mat3 D65_to_D55 = mat3 (
0.4850339153, 0.2500956126, 0.0227359648,
0.3488957224, 0.6977914447, 0.1162985741,
0.1302823568, 0.0521129427, 0.6861537456);
const mat3 D65_to_D93 = mat3 (
0.3683017655, 0.1899055978, 0.0172641453,
0.3555467892, 0.7110935785, 0.1185155964,
0.2475020592, 0.0990008237, 1.3035108450);
vec3 fix_lut(vec3 lutcolor, vec3 ref)
{
float r = length(ref);
float l = length(lutcolor);
float m = max(max(ref.r,ref.g),ref.b);
ref = normalize(lutcolor + 0.0000001) * mix(r, l, pow(m,1.25));
return mix(lutcolor, ref, LUTBR);
}
vec2 ctransform (vec2 inputc)
{
return vec2( inputc.x * sqrt(1.0 - 0.5*inputc.y*inputc.y), inputc.y * sqrt(1.0 - 0.5*inputc.x*inputc.x));
}
float vignette (vec2 coords)
{
vec2 ccoords = ctransform(2.0*(coords-0.5));
ccoords = ccoords * ccoords;
float vstr = sqrt(ccoords.x+ccoords.y);
vstr = pow(vstr, params.vigdef);
return max(mix(1.0, 1.0-vstr, params.vigstr), 0.0);
}
vec3 declip(vec3 c, float b)
{
float m = max(max(c.r,c.g),c.b);
if (m > b) c = c*b/m;
return c;
}
void main()
{
vec4 imgColor = COMPAT_TEXTURE(StockPass, vTexCoord.xy);
vec4 aftglow = COMPAT_TEXTURE(AfterglowPass, vTexCoord.xy);
float w = 1.0-aftglow.w;
float l = length(aftglow.rgb);
aftglow.rgb = AS*w*normalize(pow(aftglow.rgb + 0.01, vec3(sat)))*l;
float bp = w * BP/255.0;
imgColor.rgb = imgColor.rgb * params.pre_bb;
if (params.sega_fix > 0.5) imgColor.rgb = imgColor.rgb * (255.0 / 239.0);
imgColor.rgb = min(imgColor.rgb, 1.0);
vec3 color = imgColor.rgb;
if (int(TNTC) == 0)
{
color.rgb = imgColor.rgb;
}
else
{
float lutlow = LUTLOW/255.0; float invLS = 1.0/LS;
vec3 lut_ref = imgColor.rgb + lutlow*(1.0 - pow(imgColor.rgb, 0.333.xxx));
float lutb = lut_ref.b * (1.0-0.5*invLS);
lut_ref.rg = lut_ref.rg * (1.0-invLS) + 0.5*invLS;
float tile1 = ceil (lutb * (LS-1.0));
float tile0 = max(tile1 - 1.0, 0.0);
float f = fract(lutb * (LS-1.0)); if (f == 0.0) f = 1.0;
vec2 coord0 = vec2(tile0 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0);
vec2 coord1 = vec2(tile1 + lut_ref.r, lut_ref.g)*vec2(invLS, 1.0);
vec4 color1, color2, res;
if (int(TNTC) == 1)
{
color1 = COMPAT_TEXTURE(SamplerLUT1, coord0);
color2 = COMPAT_TEXTURE(SamplerLUT1, coord1);
res = mix(color1, color2, f);
}
else if (int(TNTC) == 2)
{
color1 = COMPAT_TEXTURE(SamplerLUT2, coord0);
color2 = COMPAT_TEXTURE(SamplerLUT2, coord1);
res = mix(color1, color2, f);
}
else if (int(TNTC) == 3)
{
color1 = COMPAT_TEXTURE(SamplerLUT3, coord0);
color2 = COMPAT_TEXTURE(SamplerLUT3, coord1);
res = mix(color1, color2, f);
}
else if (int(TNTC) == 4)
{
color1 = COMPAT_TEXTURE(SamplerLUT4, coord0);
color2 = COMPAT_TEXTURE(SamplerLUT4, coord1);
res = mix(color1, color2, f);
}
res.rgb = fix_lut (res.rgb, imgColor.rgb);
color = mix(imgColor.rgb, res.rgb, min(TNTC,1.0));
}
vec3 c = clamp(color, 0.0, 1.0);
float p;
mat3 m_out;
if (CS == 0.0) { p = 2.2; m_out = ToSRGB; } else
if (CS == 1.0) { p = 2.2; m_out = ToModern; } else
if (CS == 2.0) { p = 2.6; m_out = ToDCI; } else
if (CS == 3.0) { p = 2.2; m_out = ToAdobe; } else
if (CS == 4.0) { p = 2.4; m_out = ToREC; }
color = pow(c, vec3(p));
mat3 m_in = Profile0;
if (CP == 0.0) { m_in = Profile0; } else
if (CP == 1.0) { m_in = Profile1; } else
if (CP == 2.0) { m_in = Profile2; } else
if (CP == 3.0) { m_in = Profile3; } else
if (CP == 4.0) { m_in = Profile4; } else
if (CP == 5.0) { m_in = Profile5; }
color = m_in*color;
color = m_out*color;
if ( (params.dclip == 1.0) && (max(max(color.r,color.g),color.b) > (1.0 + 1.5/255.0))) color = declip(color, 1.0);
if ( (params.dclip == 2.0) && (max(max(color.r,color.g),color.b) > (1.0 + 1.5/255.0))) color = vec3(1.0, 0.0, 1.0);
color = clamp(color, 0.0, 1.0);
color = pow(color, vec3(1.0/p));
if (CP == -1.0) color = c;
vec3 scolor1 = normalize(pow(color + 0.000000001, vec3(wp_saturation)))*length(color);
float luma = dot(color, vec3(0.2126, 0.7152, 0.0722));
vec3 scolor2 = mix(vec3(luma), color, wp_saturation);
color = (wp_saturation > 1.0) ? scolor1 : scolor2;
p = 2.2;
color = pow(color, vec3(p));
color = clamp(color, 0.0, 1.0);
vec3 warmer = D65_to_D55*color;
warmer = ToSRGB*warmer;
vec3 cooler = D65_to_D93*color;
cooler = ToSRGB*cooler;
float m = abs(WP)/100.0;
vec3 comp = (WP < 0.0) ? cooler : warmer;
color = mix(color, comp, m);
color = pow(max(color, 0.0), vec3(1.0/p));
color = color + aftglow.rgb + bp;
FragColor = vec4(color, vignette(vTexCoord.xy));
}
Otherwise LUT’s shouldn’t clip colors in vanilla usage, color profiles can.
Edit: declip mode added.
Tweaking a consumer-grade crt preset based on a photo of a 13" slot mask TV.
What’s the best way to keep the right hand (the hand holding the sword) bright, while keeping the black outline around the sprite as solid as possible? There must be some tricky combination of sharpness settings that will accomplish this.
Here’s the photo for reference
Maybe someone can improve on this further?
shaders = "17"
shader0 = "shaders_slang/misc/grade.slang"
filter_linear0 = "false"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = ""
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/advanced/stock.slang"
filter_linear1 = "false"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = "StockPass"
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/advanced/afterglow0.slang"
filter_linear2 = "true"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
alias2 = "AfterglowPass"
float_framebuffer2 = "false"
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/advanced/pre-shaders-afterglow.slang"
filter_linear3 = "true"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = "PrePass0"
float_framebuffer3 = "false"
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/advanced/ntsc/ntsc-pass1.slang"
filter_linear4 = "false"
wrap_mode4 = "clamp_to_border"
frame_count_mod4 = "2"
mipmap_input4 = "false"
alias4 = ""
float_framebuffer4 = "true"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "4.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "shaders_slang/crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang"
filter_linear5 = "false"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = ""
float_framebuffer5 = "false"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "0.500000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/crt/shaders/guest/advanced/custom-fast-sharpen.slang"
filter_linear6 = "true"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = "NtscPass"
float_framebuffer6 = "false"
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/advanced/convert-ntsc.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = ""
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "source"
scale_x7 = "0.500000"
scale_type_y7 = "source"
scale_y7 = "1.000000"
shader8 = "shaders_slang/crt/shaders/guest/advanced/stock.slang"
filter_linear8 = "true"
wrap_mode8 = "clamp_to_border"
mipmap_input8 = "true"
alias8 = "PrePass"
float_framebuffer8 = "false"
srgb_framebuffer8 = "false"
scale_type_x8 = "source"
scale_x8 = "1.000000"
scale_type_y8 = "source"
scale_y8 = "1.000000"
shader9 = "shaders_slang/crt/shaders/guest/advanced/avg-lum-ntsc.slang"
filter_linear9 = "true"
wrap_mode9 = "clamp_to_border"
mipmap_input9 = "true"
alias9 = "AvgLumPass"
float_framebuffer9 = "false"
srgb_framebuffer9 = "false"
scale_type_x9 = "source"
scale_x9 = "1.000000"
scale_type_y9 = "source"
scale_y9 = "1.000000"
shader10 = "shaders_slang/crt/shaders/guest/advanced/linearize-ntsc.slang"
filter_linear10 = "true"
wrap_mode10 = "clamp_to_border"
mipmap_input10 = "false"
alias10 = "LinearizePass"
float_framebuffer10 = "true"
srgb_framebuffer10 = "false"
scale_type_x10 = "source"
scale_x10 = "1.000000"
scale_type_y10 = "source"
scale_y10 = "1.000000"
shader11 = "shaders_slang/crt/shaders/guest/advanced/gaussian_horizontal.slang"
filter_linear11 = "true"
wrap_mode11 = "clamp_to_border"
mipmap_input11 = "false"
alias11 = ""
float_framebuffer11 = "true"
srgb_framebuffer11 = "false"
scale_type_x11 = "absolute"
scale_x11 = "640"
scale_type_y11 = "source"
scale_y11 = "1.000000"
shader12 = "shaders_slang/crt/shaders/guest/advanced/gaussian_vertical.slang"
filter_linear12 = "true"
wrap_mode12 = "clamp_to_border"
mipmap_input12 = "false"
alias12 = "GlowPass"
float_framebuffer12 = "true"
srgb_framebuffer12 = "false"
scale_type_x12 = "absolute"
scale_x12 = "640"
scale_type_y12 = "absolute"
scale_y12 = "480"
shader13 = "shaders_slang/crt/shaders/guest/advanced/bloom_horizontal.slang"
filter_linear13 = "true"
wrap_mode13 = "clamp_to_border"
mipmap_input13 = "false"
alias13 = ""
float_framebuffer13 = "true"
srgb_framebuffer13 = "false"
scale_type_x13 = "absolute"
scale_x13 = "640"
scale_type_y13 = "absolute"
scale_y13 = "480"
shader14 = "shaders_slang/crt/shaders/guest/advanced/bloom_vertical.slang"
filter_linear14 = "true"
wrap_mode14 = "clamp_to_border"
mipmap_input14 = "false"
alias14 = "BloomPass"
float_framebuffer14 = "true"
srgb_framebuffer14 = "false"
scale_type_x14 = "absolute"
scale_x14 = "640"
scale_type_y14 = "absolute"
scale_y14 = "480"
shader15 = "shaders_slang/crt/shaders/guest/advanced/crt-guest-advanced-ntsc.slang"
filter_linear15 = "true"
wrap_mode15 = "clamp_to_border"
mipmap_input15 = "false"
alias15 = ""
float_framebuffer15 = "false"
srgb_framebuffer15 = "false"
scale_type_x15 = "viewport"
scale_x15 = "1.000000"
scale_type_y15 = "viewport"
scale_y15 = "1.000000"
shader16 = "shaders_slang/crt/shaders/guest/advanced/deconvergence.slang"
filter_linear16 = "true"
wrap_mode16 = "clamp_to_border"
mipmap_input16 = "false"
alias16 = ""
float_framebuffer16 = "false"
srgb_framebuffer16 = "false"
scale_type_x16 = "viewport"
scale_x16 = "1.000000"
scale_type_y16 = "viewport"
scale_y16 = "1.000000"
g_crtgamut = "0.000000"
g_vignette = "0.000000"
WP = "-100.000000"
quality = "-1.000000"
SHARPEN = "1.000000"
CONTR = "0.250000"
GAMMA_INPUT = "2.400000"
glow = "0.020000"
bloom = "0.100000"
halation = "0.025000"
gamma_c = "1.060000"
brightboost = "1.000000"
brightboost1 = "1.999996"
bogus_scanline = "1.000000"
beam_min = "1.000000"
beam_size = "0.000000"
scans = "1.000000"
spike = "0.000000"
h_sharp = "3.599998"
s_sharp = "0.800000"
shadowMask = "7.000000"
maskstr = "0.600000"
mcut = "0.600000"
masksize = "2.000000"
slotwidth = "3.000000"
mclip = "0.000000"
gamma_out = "2.400001"
deconrr = "1.000000"
deconrb = "-1.000000"
decons = "1.000000"
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4"
SamplerLUT1 = "shaders_slang/crt/shaders/guest/advanced/lut/trinitron-lut.png"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT1_mipmap = "false"
SamplerLUT2 = "shaders_slang/crt/shaders/guest/advanced/lut/inv-trinitron-lut.png"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT2_mipmap = "false"
SamplerLUT3 = "shaders_slang/crt/shaders/guest/advanced/lut/nec-lut.png"
SamplerLUT3_linear = "true"
SamplerLUT3_wrap_mode = "clamp_to_border"
SamplerLUT3_mipmap = "false"
SamplerLUT4 = "shaders_slang/crt/shaders/guest/advanced/lut/ntsc-lut.png"
SamplerLUT4_linear = "true"
SamplerLUT4_wrap_mode = "clamp_to_border"
SamplerLUT4_mipmap = "false"
Thanks again for creating this, very useful.
I’m only running into a thing I don’t yet understand.
If I create a custom color profile that is exactly sRGB, apart from red being that bit wider, then when using this color profile in combination with setting sRGB color space I would expect the debug to only flag red as clipping (out of gamut) on the 240p color bar test, but instead it shows green and blue clipping on the last bar (but not red!)
Then when I create a custom color profile that is exactly sRGB, apart from green being a bit wider, then when using this profile in combination with setting sRGB color space I would expect the debug to flag only green as clipping (out of gamut) on the 240p color bar test, but instead it shows red and blue clipping with the last bar (but not green!)
Any idea what might be going on?
I’m using this matrix where primaries and white point are sRGB except for red extended one notch. When using this color profile in combination with color space set to sRGB, the debug feature shows green and blue clipping, instead of red.
0.408979, 0.207636, 0.012584,
0.359859, 0.719717, 0.119953,
0.181618, 0.072647, 0.956521
First is your settings, the second mine. Some games definitely show are stronger black outline than others. I wouldn’t say mine is brighter though but perhaps more contrast.
shaders = "17"
shader0 = "shaders_slang/crt/shaders/guest/advanced/stock.slang"
filter_linear0 = "false"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = ""
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/advanced/stock.slang"
filter_linear1 = "false"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = "StockPass"
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/advanced/afterglow0.slang"
filter_linear2 = "true"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
alias2 = "AfterglowPass"
float_framebuffer2 = "false"
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/advanced/pre-shaders-afterglow.slang"
filter_linear3 = "true"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = "PrePass0"
float_framebuffer3 = "false"
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/advanced/ntsc/ntsc-pass1.slang"
filter_linear4 = "false"
wrap_mode4 = "clamp_to_border"
frame_count_mod4 = "2"
mipmap_input4 = "false"
alias4 = ""
float_framebuffer4 = "true"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "4.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "shaders_slang/crt/shaders/guest/advanced/ntsc/ntsc-pass2.slang"
filter_linear5 = "true"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = ""
float_framebuffer5 = "false"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "0.500000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "shaders_slang/crt/shaders/guest/advanced/custom-fast-sharpen.slang"
filter_linear6 = "true"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = "NtscPass"
float_framebuffer6 = "false"
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/advanced/convert-ntsc.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = ""
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "source"
scale_x7 = "0.500000"
scale_type_y7 = "source"
scale_y7 = "1.000000"
shader8 = "shaders_slang/crt/shaders/guest/advanced/stock.slang"
filter_linear8 = "true"
wrap_mode8 = "clamp_to_border"
mipmap_input8 = "true"
alias8 = "PrePass"
float_framebuffer8 = "false"
srgb_framebuffer8 = "false"
scale_type_x8 = "source"
scale_x8 = "1.000000"
scale_type_y8 = "source"
scale_y8 = "1.000000"
shader9 = "shaders_slang/crt/shaders/guest/advanced/avg-lum-ntsc.slang"
filter_linear9 = "true"
wrap_mode9 = "clamp_to_border"
mipmap_input9 = "true"
alias9 = "AvgLumPass"
float_framebuffer9 = "false"
srgb_framebuffer9 = "false"
scale_type_x9 = "source"
scale_x9 = "1.000000"
scale_type_y9 = "source"
scale_y9 = "1.000000"
shader10 = "shaders_slang/crt/shaders/guest/advanced/linearize-ntsc.slang"
filter_linear10 = "true"
wrap_mode10 = "clamp_to_border"
mipmap_input10 = "false"
alias10 = "LinearizePass"
float_framebuffer10 = "true"
srgb_framebuffer10 = "false"
scale_type_x10 = "source"
scale_x10 = "1.000000"
scale_type_y10 = "source"
scale_y10 = "1.000000"
shader11 = "shaders_slang/crt/shaders/guest/advanced/gaussian_horizontal.slang"
filter_linear11 = "true"
wrap_mode11 = "clamp_to_border"
mipmap_input11 = "false"
alias11 = ""
float_framebuffer11 = "true"
srgb_framebuffer11 = "false"
scale_type_x11 = "absolute"
scale_x11 = "640"
scale_type_y11 = "source"
scale_y11 = "1.000000"
shader12 = "shaders_slang/crt/shaders/guest/advanced/gaussian_vertical.slang"
filter_linear12 = "true"
wrap_mode12 = "clamp_to_border"
mipmap_input12 = "false"
alias12 = "GlowPass"
float_framebuffer12 = "true"
srgb_framebuffer12 = "false"
scale_type_x12 = "absolute"
scale_x12 = "640"
scale_type_y12 = "absolute"
scale_y12 = "480"
shader13 = "shaders_slang/crt/shaders/guest/advanced/bloom_horizontal.slang"
filter_linear13 = "true"
wrap_mode13 = "clamp_to_border"
mipmap_input13 = "false"
alias13 = ""
float_framebuffer13 = "true"
srgb_framebuffer13 = "false"
scale_type_x13 = "absolute"
scale_x13 = "640"
scale_type_y13 = "absolute"
scale_y13 = "480"
shader14 = "shaders_slang/crt/shaders/guest/advanced/bloom_vertical.slang"
filter_linear14 = "true"
wrap_mode14 = "clamp_to_border"
mipmap_input14 = "false"
alias14 = "BloomPass"
float_framebuffer14 = "true"
srgb_framebuffer14 = "false"
scale_type_x14 = "absolute"
scale_x14 = "640"
scale_type_y14 = "absolute"
scale_y14 = "480"
shader15 = "shaders_slang/crt/shaders/guest/advanced/crt-guest-advanced-ntsc.slang"
filter_linear15 = "true"
wrap_mode15 = "clamp_to_border"
mipmap_input15 = "false"
alias15 = ""
float_framebuffer15 = "false"
srgb_framebuffer15 = "false"
scale_type_x15 = "viewport"
scale_x15 = "1.000000"
scale_type_y15 = "viewport"
scale_y15 = "1.000000"
shader16 = "shaders_slang/crt/shaders/guest/advanced/deconvergence.slang"
filter_linear16 = "true"
wrap_mode16 = "clamp_to_border"
mipmap_input16 = "false"
alias16 = ""
float_framebuffer16 = "false"
srgb_framebuffer16 = "false"
scale_type_x16 = "viewport"
scale_x16 = "1.000000"
scale_type_y16 = "viewport"
scale_y16 = "1.000000"
WP = "-100.000000"
quality = "0.000000"
ntsc_sat = "1.200000"
SHARPEN = "1.200000"
CONTR = "0.150000"
blendMode = "2.000000"
prescalex = "4.000000"
glow = "0.020000"
bloom = "0.350000"
halation = "0.050000"
brightboost = "1.999999"
brightboost1 = "1.300000"
beam_min = "1.799999"
beam_max = "1.100000"
scangamma = "1.700001"
shadowMask = "7.000000"
maskstr = "0.200000"
mcut = "1.300000"
mask_gamma = "1.699999"
slotmask = "0.025000"
slotmask1 = "0.500000"
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3;SamplerLUT4"
SamplerLUT1 = "shaders_slang/crt/shaders/guest/advanced/lut/trinitron-lut.png"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT1_mipmap = "false"
SamplerLUT2 = "shaders_slang/crt/shaders/guest/advanced/lut/inv-trinitron-lut.png"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT2_mipmap = "false"
SamplerLUT3 = "shaders_slang/crt/shaders/guest/advanced/lut/nec-lut.png"
SamplerLUT3_linear = "true"
SamplerLUT3_wrap_mode = "clamp_to_border"
SamplerLUT3_mipmap = "false"
SamplerLUT4 = "shaders_slang/crt/shaders/guest/advanced/lut/ntsc-lut.png"
SamplerLUT4_linear = "true"
SamplerLUT4_wrap_mode = "clamp_to_border"
SamplerLUT4_mipmap = "false"
Looks good but the mask is too fine, and too bloomed out compared to the photo. But looks good as far as sharpness and color/brightness goes.
I have to do weird stuff to guest-advanced to get the mask to look like the photo. I altered mask 7 to darken every 4th pixel, you have to replace the pos.x number with a 4 and then the 0.5 with a 0.75, or something. But this looks too fine in some areas. Maybe there should be a variable mask width option (it would require a new mask, though).
You could try the new option Resoultion Scaling, or use Blend Mode 0.0, at least for this game. With some settings, which tend to dissolve dithering, clear thin horizontal black outlines aren’t likely to get a good contrast, because the ntsc code blends it relentlessly with neighbor colors.
With math terms, the answer is rather simple if we look at the matrix and compare it with sRGB. The red color component gets less weights (0.408979 vs.0.412391) if multiplied only with a red color, blue and green get a bit more.
If red gamut is a bit wider, the changed matrix tends to enhance the red color component with mixed colors though.
But it’s more of a question how much clipping and if you get the desired colors in general. I wouldn’t mind very limited clipping too much, since it doesn’t affect very saturated colors, or in a negligible manner. Best answer would be to reduce brightness in the pre-pass a bit if it’s notable.
Regarding this aspect the usage of a lut is more advanced, but with a lut generating program like DisplayCal you cannot miss the concept that color profile conversions are not this trivial and you get to pick a preferred conversion type.