I just discovered that the white pixel on my LG C9 is a ‘boost’ that can be enabled if you turn on “peak white”. Otherwise it is not used. But that means I still have BGR pixel setup. Which mask should I use? I notice that Mask 0, 7, and 8 look greyscale to me – no RGB like grids while others do. What should I make of this? BTW the fact that peak white is optional explains why it’s been said that LG was able to increase brightness without sacrificing resolution. @guest.r
Mask 7 is BW indeed. Masks 0 and 5 should have even sub-pixel spacing even on a BGR/RBG at 1080p, not minding W. Masks 1-4 should do quite fine. The only RGB specific mask is mask 8. It’s good to know that using mask size of 2.0 breaks sub-pixel distancing basically on all displays, even if a mask complies at size of 1.0.
I was wondering, would it be possible to add an option to rotate the mask you choose 90 degrees to either side? I know that there is TATE mode but that also flips the scanlines as if I have my display set in portrait mode.
I ask because I have a TV that is a Vertical BGR subpixel layout, and because of this it makes it very difficult to find a mask that lines up properly with my TV’s subpixel layout. I thought it would be a good idea after reading up on hunterk’s blog on CRT Shader Masks, and I think it would make it a lot easier to find a mask that looks right on my TV, even if it isn’t accurate to how CRT Masks actually looked.
Since i’m avoiding too many parameters and i’m even keen to remove some, this won’t be included in general i think.
But you can still try with a little fix in crt-guest-dr-venom.slang - line number 740 and some:
float smask = (notate) ? SlotMask(gl_FragCoord.xy * 1.000001, mx) : SlotMask(gl_FragCoord.yx * 1.000001, mx);
cmask*= (notate) ? Mask(gl_FragCoord.xy * 1.000001, mx) : Mask(gl_FragCoord.yx * 1.000001, mx);
You can try switching the swizzle on the gl_FragCoord, which means .xy -> .yx etc.
This will calculate the mask vertically. OTOH Mask 7 is black-white, it should be put to a decent use, if the effect is too slim, increase the mask size to 2.0.
That’s good news about the white subpixel.
Replace the relevant code with this. Another good option that requires no alteration to the code is the guest dr venom slotmask; set CRT mask to -1.00 when using it.
@guest.r is correct that you still get “regular” subpixel spacing using RGB subpixel masks with BGR subpixels, but the active subpixels are all adjacent to each other and this results in a definite loss of image quality and phosphor definition, in my experience.
Mask 0:
// Phosphor.
else if (shadowMask == 0.0)
{
pos.x = fract(pos.x*0.5);
if (pos.x < 0.5) { mask.r = mc; mask.g = 1.0; mask.b = mc; }
else { mask.r = 1.0; mask.g = mc; mask.b = 1.0; }
}
For mask 8:
// 4k mask
else
{
mask = vec3(mc);
pos.x = fract(pos.x * 0.25);
if (pos.x < 0.2) mask.b = 1.0;
else if (pos.x < 0.4) mask.bg = 1.0.xx;
else if (pos.x < 0.7) mask.gr = 1.0.xx;
else mask.r = 1.0;
}
return mask;
}
speaking of masks, changing the black pixel in the red,green,blue,black mask to white keeps the brightness up, though I don’t know how it’ll affect your contrast ramp, @Nesguy
Making it a 2-row staggered pattern:
red, green, blue, white
blue, white, red, green
makes a pretty nice lower-TVL shadowmask alternative to the magenta/green checkerboard (works with a black pixel, too, but it makes it pretty dark).
latter on the left, former on the right:
Ye, I don’t really understand how to set color patterns with the masks myself tbh.
I found a sweet spot I enjoy between PVM like really visible scanlines while keeping adjacent pixels blending, comparable to what crt-geom does.
Clarity is great while still rounding corners to suit the intended drawings (like circle shapes).
shaders = "9"
shader0 = "shaders_slang/misc/ntsc-colors.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 = "crt-guest/shaders/guest/crt-gdv-new/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 = "crt-guest/shaders/guest/crt-gdv-new/afterglow0.slang"
filter_linear2 = "false"
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 = "crt-guest/shaders/guest/crt-gdv-new/pre-shaders-afterglow.slang"
filter_linear3 = "false"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = "PrePass"
float_framebuffer3 = "false"
srgb_framebuffer3 = "false"
scale_type_x3 = "source"
scale_x3 = "1.000000"
scale_type_y3 = "source"
scale_y3 = "1.000000"
shader4 = "crt-guest/shaders/guest/crt-gdv-new/avg-lum.slang"
filter_linear4 = "true"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "true"
alias4 = "AvgLumPass"
float_framebuffer4 = "false"
srgb_framebuffer4 = "false"
scale_type_x4 = "source"
scale_x4 = "1.000000"
scale_type_y4 = "source"
scale_y4 = "1.000000"
shader5 = "crt-guest/shaders/guest/crt-gdv-new/linearize.slang"
filter_linear5 = "true"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = "LinearizePass"
float_framebuffer5 = "true"
srgb_framebuffer5 = "false"
scale_type_x5 = "source"
scale_x5 = "1.000000"
scale_type_y5 = "source"
scale_y5 = "1.000000"
shader6 = "crt-guest/shaders/guest/crt-gdv-new/blur_horiz2.slang"
filter_linear6 = "true"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = ""
float_framebuffer6 = "false"
srgb_framebuffer6 = "false"
scale_type_x6 = "absolute"
scale_x6 = "800"
scale_type_y6 = "source"
scale_y6 = "1.000000"
shader7 = "crt-guest/shaders/guest/crt-gdv-new/blur_vert2.slang"
filter_linear7 = "true"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = "GlowPass"
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
scale_type_x7 = "absolute"
scale_x7 = "800"
scale_type_y7 = "absolute"
scale_y7 = "600"
shader8 = "crt-guest/shaders/guest/crt-gdv-new/crt-guest-dr-venom2.slang"
filter_linear8 = "true"
wrap_mode8 = "clamp_to_border"
mipmap_input8 = "false"
alias8 = ""
float_framebuffer8 = "false"
srgb_framebuffer8 = "false"
scale_type_x8 = "viewport"
scale_x8 = "1.000000"
scale_type_y8 = "viewport"
scale_y8 = "1.000000"
AS = "0.000000"
CP = "-1.000000"
glow = "0.000000"
gamma_c = "1.100000"
brightboost1 = "1.200000"
gsl = "2.000000"
scanline2 = "32.000000"
spike = "0.000000"
h_sharp = "3.600000"
s_sharp = "1.000000"
shadowMask = "-1.000000"
textures = "SamplerLUT1;SamplerLUT2;SamplerLUT3"
SamplerLUT1 = "crt-guest/shaders/guest/lut/sony_trinitron1.png"
SamplerLUT1_linear = "true"
SamplerLUT1_wrap_mode = "clamp_to_border"
SamplerLUT1_mipmap = "false"
SamplerLUT2 = "crt-guest/shaders/guest/lut/sony_trinitron2.png"
SamplerLUT2_linear = "true"
SamplerLUT2_wrap_mode = "clamp_to_border"
SamplerLUT2_mipmap = "false"
SamplerLUT3 = "crt-guest/shaders/guest/lut/other1.png"
SamplerLUT3_linear = "true"
SamplerLUT3_wrap_mode = "clamp_to_border"
SamplerLUT3_mipmap = "false"
Looking at the CRT shots you posted I noticed one of the effects of the shadow mask is that it seems to “dither” the outside of the scanlines. Since all current mask simulation has the large drawback of darkening the image, I was thinking about only simulating that dithering effect on the outside of scanlines.
A naive and simple approach would be to shift Y column up by 1 pixel every other pixel / column . From normal viewing distance this creates the illusion of dithering on the outside of the scanlines, like real mask on CRT does, with the big win that brightness is not affected.
I’m sure better looking implementations of this concept can be thought of, this is purely a paint program mockup to demonstrate the basic idea and effect. Since it uses only 1 pixel shift this is -not- for 4K (it would need bigger size / shift of course ) Watch at true size from normal viewing distance on 1080p / 1440p.
It could be also normal blending instead of dithering, but i get the idea. There are more downsides though related to low-res pixel fetching and attributing it in high res, otherwise i’d use something similar to smooth the scanlines at non-integer scaling etc.
An after-pass could do it, but i’m avoiding it in general. Scanline smoothing for example went very well with extra pass, not minding the speed loss. There is currently no afterpass for scanline smoothing, although it works nice, because the performance degradation.
I’m not sure if normal blending is the same in this situation.
I got the idea by the “produced by” in the CRT shot, see cutout below. If you look at the top row of white pixels of the “produced by”, it’s as if every other pixel / column is shifted “one pixel” up (if we would talk in terms of shader output size) .
This effect makes the scanline very different from high TVL PVM, the scanline is not as flat as BVM, but has “an illusion” bulging pixel pattern to it. That’s where my idea came from . As mentioned I think the big win could be that part of real mask is simulated without any brightness loss.
Shifting Y column up by some amount every other column would produce a similar effect with the shader, but we would need to load up that raw screenshot of turrican in the image viewer to see if applying this effect would produce similar "dithering " pattern on the top pixel line of “produced by” as below from real CRT.
It’s a bit similar to the ‘coarse’ trinitron mask in crt-guest-sm, but, as i said, is easily achievable by an extra pass. The screenshot jaggies though are mask related to deconvergence and unequally fading phosphors, since they are more saturated. I can post the pass you can try it a bit later.
Shader, last pass, Scale1x, Filtering Linear
#version 450
layout(push_constant) uniform Push
{
vec4 SourceSize;
vec4 OriginalSize;
vec4 OutputSize;
uint FrameCount;
float dsize;
} params;
#pragma parameter dsize "Scanline dithering size" 1.0 0.0 2.0 0.10
#define dsize params.dsize // Dithering size
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;
void main()
{
vec2 tex = vTexCoord;
vec2 pos = tex * params.OutputSize.xy;
vec2 dy = dsize*vec2(0.0, params.OutputSize.w);
float mixer = floor(mod(pos.x,2.0));
tex = mix(tex + 0.0*dy, tex - dy, mixer);
FragColor = vec4(texture(Source, tex).rgb, 1.0);
}
Looks very good, similar to my maxDR preset before I broke it. Thanks for sharing your settings!
edit: I think it’s safe to increase bright boost a bit, here it is with 1.80/1.80. I also added grade and set white point to ~7500K
@hunterk are we still waiting on github permissions to be restored?
Also, what’s slang for “white” and “black”? I know how to say red, green and blue, but white and black still elude me. I want to try that mask!
Awesome thanks for making this, it’s quite another thing to be able to test quickly with many more games The idea works okayish I would say, it’s interesting to see it’s not working as well as I expected, as white-black adjacent areas create more visible “sawtooths” patterns then when it’s not black as adjacent color. So overal the sawtooth pattern is not equally divided / visible as a normal mask would. Very nice to have tried this though, thanks as it satisfied the hunger!
There’s one other thing: I’m testing a bit with the “CRT Color Profile” and would be interested where these things clip in sRGB space. I was having this idea that maybe would be possible to have a “test hack” for.
Could it be made such (as temp hack) that when a CRT Color Profile is set and colors clip outside of the sRGB that it is not given the max clipping value but instead turned into (for example) a bright neon green color?
With such a feature we could easily see IF, WHEN and WHERE colors from the CRT Color Profiles clip in the various games. Maybe this could work for the LUTs too?
It’s doable, but not much of it. Three lines must be calculated and three linear inequations solved in the xyY space. It’s not interesting for colorspace transformations the way they work, since the gamut is narrowed. Maybe, when i’m somewhat or very bored.
But gave me a nice idea though…
I think you can sort of get the desired effect with the slot mask in GDV. The sawtooths aren’t quite as pronounced as we’d like them to be, maybe, but I think it looks pretty nice nonetheless.
I’m testing beam non-linearity, with default settings I found a linear response (no beam dynamics at all) and only a 10% loss on luminance output. What presets do you recommend for monitor CRTs and consumer CRTs so I can test further (no glow involved).
Great idea.
Mine are actually all a bit weird right now, as I was recently testing how high I could push brightness before I started to get clipping.
I’m currently working on a pro monitor setup (mask 0) and two consumer-grade setups (mask 8 and GDV slotmask).
They’re all currently too bright, but I think most of the other settings are pretty neutral, scanline shape and bright boost are probably where you want to make changes. Scanline dark @ 1.50 is arbitrary but I find it’s easy to work with; a lower value like 1.30 might be better. Scanline saturation and bright beam are at 1.00 for increased flexibility.
Gamma on my plasma is 2.4 so that’s what I have output gamma set to.
Glow is at 0.01; need to disable that.
Protip: I think if you raise/lower bright boost dark and bright boost bright by the same amount it will keep the gamma curve intact and just raise/lower the whole thing.
Edit: @Tatsuya79’s recent settings also look very good, especially after adding grade and increasing the bright boost a bit (I think 1.80/1.80 is reasonable with mask 0).
I’d recommend @Kurozumi’s excellent preset, but it no longer works with the new GDV.