Please show off what crt shaders can do!

Ah, I was looking at the ‘resolve’ pass from the glow shader. Yeah, I should probably get rid of that conditional and just always do the unjiggered vTexCoord.

1 Like

I’d at least advise testing it before removing it, as it does a nice job at minimizing/removing visible moire. Also if you’re editing that shader would you change the lable for moire test (user parameter) into something like moire toggle idk. It just seems like a debug feature being named test lol.

EDIT: Thanks for the help and explanation!

My new shader preset. Just trying to dial in the sharpness; I think this is about right. Trying to match a 500-600 TVL shadowmask.

As always, I have my LCD backlight cranked to 100% when adding any kind of mask and/or scanlines. The images below will be too dark unless you’re cranking up the backlight and using an LED-lit LCD (really any LCD within the last 5 years will do).

alias0 = ""
alias1 = ""
BRIGHTNESS = "1.000001"
filter_linear0 = "false"
filter_linear1 = "true"
float_framebuffer0 = "false"
float_framebuffer1 = "false"
GAMMA_INPUT = "2.400000"
GAMMA_OUTPUT = "2.200000"
GLOW_DIFFUSION = "0.000000"
GLOW_HALATION = "0.000000"
GLOW_HEIGHT = "0.500000"
GLOW_WIDTH = "0.500000"
LUTHeight = "2.000000"
LUTWidth = "2.000000"
MASK_COLORS = "2.000000"
MASK_SIZE = "1.000000"
MASK_STRENGTH = "0.000000"
mipmap_input0 = "false"
mipmap_input1 = "false"
overlay = "C:\Program Files\RetroArch\shaders\shaders_glsl\reshade\shaders\blendoverlay\aperture_1_2_bgr.png"
overlay_mipmap = "false"
overlay_wrap_mode = "clamp_to_border"
OverlayMix = "0.500000"
parameters = "SHARPNESS_IMAGE;SHARPNESS_EDGES;GLOW_WIDTH;GLOW_HEIGHT;GLOW_HALATION;GLOW_DIFFUSION;MASK_COLORS;MASK_STRENGTH;MASK_SIZE;SCANLINE_SIZE_MIN;SCANLINE_SIZE_MAX;SCANLINE_SHAPE;SCANLINE_OFFSET;GAMMA_INPUT;GAMMA_OUTPUT;BRIGHTNESS;OverlayMix;LUTWidth;LUTHeight"
scale_type_x0 = "source"
scale_type_y0 = "source"
scale_x0 = "5.000000"
scale_y0 = "5.000000"
SCANLINE_OFFSET = "0.000000"
SCANLINE_SHAPE = "1.000000"
SCANLINE_SIZE_MAX = "1.500000"
SCANLINE_SIZE_MIN = "0.500000"
shader0 = "C:\Program Files\RetroArch\shaders\shaders_glsl\crt\shaders\crt-aperture.glsl"
shader1 = "C:\Program Files\RetroArch\shaders\shaders_glsl\reshade\shaders\blendoverlay\blendoverlay.glsl"
shaders = "2"
SHARPNESS_EDGES = "1.000000"
SHARPNESS_IMAGE = "2.000000"
srgb_framebuffer0 = "false"
srgb_framebuffer1 = "false"
textures = "overlay"
wrap_mode0 = "clamp_to_border"
wrap_mode1 = "clamp_to_border"
2 Likes

I think the above preset looks better at even integer scales (4x, 6x etc). Since the LUT is 2x2 pixels and the scanlines are 5 pixels wide, there’s a weird moire-like pattern in the above images. This raises the question: why don’t shadowmask CRTs exhibit this moire-like pattern with scanlines? It seems unlikely that the beam would always perfectly line up with the phosphors in such a way that this wouldn’t happen. Or maybe shadowmask CRTs do show the same moire-like pattern with scanlines and I’ve just never noticed.

edit: it’s not really moire per se; it looks like slightly uneven scanlines due to the fact that the phosphor arrangement in the scanline alternates with each adjacent scanline (this is difficult to explain).

So, the aperture grille pattern has the advantage of looking better at odd integer scales, since the phosphor arrangement is the same with each scanline (again, difficult to explain).

Edit: Okay, this is one of those things that’s just easier to explain with pictures.

The problem is that you have two scanline/phosphor patterns that alternate between each other, which tricks your eyes and gives the illusion of uneven scanlines (at least, it does for me). Does anyone know if shadowmask CRTs did the same thing? If not, why not?

2nd edit: Is this just all in my head? I need a nap.

Here’s an example of the same preset at 4x:

1 Like

If you use a 2x2 png with blendoverlay like shadowmask effect,you will need to change shader scale 0 5x to 4x (crt-aperture passes ).

ex: 4x

ex: 5x

1 Like

Changing the shader scale from 5x to 4x made the problem(?) even more apparent on my display; I’m using a 1080p display and a custom 6x5 integer scale. 3x shader scale looks even worse.

There are a lot Xs here, so to clarify the above images, the first SMB shot is 6x5 custom integer scale with shader scale 0 at 5x, the second SMB shot is 5x4 custom integer scale. They’re all at 5x shader scale for shader 0.

I think the issue is unavoidable with odd-numbered vertical integer scales. You could use a 5x5 LUT (with a 6x5 integer scale), but then the mask texture wouldn’t look right. Curses!

1 Like

When I went from 1080p to 2160p, I only had to tick the integer scale parameter in retroarch and it made my life easier :pray:. The prices of some good screens are becoming more and more affordable. It may be time to buy uhd screen in 2020 ! :money_mouth_face:

Edit! maybe i found a solution for your preset,wait a minute

Edit 2 : ok change this

with this

scale_type_x0 = “absolute” scale_x0 = “1920” scale_type_y0 = “absolute” scale_y0 = “1080”

4 Likes

Nope, that’s even worse lol.

It’s an inevitable consequence of using an odd-numbered integer scale with the checkerboard pattern. There’s no way to avoid an alternating pattern with the scanlines. To test this, try any odd numbered integer scale with the checkerboard pattern and scanlines and you’ll get the same result: seemingly uneven scanlines.

Thanks for trying though :smiley:

Definitely time to upgrade displays!

This still leaves unsolved the mystery of why I don’t notice the same thing on actual shadowmask CRTs. Maybe I’ve just never looked close enough, or maybe the beam width is always an even number of phosphors wide, but that seems unlikely.

2 Likes

Just been catching up on this thread (since Jan '19) and the work you guys have put in is truly amazing, big stand outs for me is HyperspaceMadness bezel reflection and Guest.r and Venoms shader!!!. On that note will HyperspaceMadness’s bezel effect be able to be added existing shaders as an option to enable and disable?

HyerspaceMadness, any plans to release a 1440p version?

So by the looks of this thread the top shaders are:

  • crt-easymode

  • crt-guest-dr-venom

  • crt-aperture

  • crt-hyllian

  • crt-royale kurozumi

2 Likes

I m loving the composite shader (analog pack) but would it be possible to enable an option where you can change the shadow mask opacity ?

semi-off topic:

Can anyone point me to some high quality macro shots of a 500-600 TVL shadowmask/dotmask CRT, showing the phosphor arrangement and scanlines? Seems like more than 95% of the photos I see are of aperture grille CRTs.

Maybe… Could you possibly link the shader or code-block post it here (copy and paste the shader with the mask with “```” at the top and bottom of it without the " ")?

@BlockABoots HyperspaceMadness’s currently posted shader is built on top of crt-guest(still really nice additional work though), the next version should be a standalone shader (at least from what I understood, please correct me if I’m wrong though).

Although if the shadertoy shader I linked works out for him, we may not need to worry about bezel resolutions (at least from what I was understanding of the code).

I feel like that list of shaders is pretty accurate, :joy:.

I have a couple of macro shots from my NEC XM29 Plus on my blog:

It’s 600-800 TVL, I think.

The shots at the end of this post are all from a 31 khz shadowmask CRT, probably around 800 TVL (1024x768 nominal res):

4 Likes

On this link you will find several high quality images with different types of masks.

Exemple: https://aws1.discourse-cdn.com/standard10/uploads/retrogameboards/original/2X/e/e918fa121e748299fc8981ffa59cd2f2053c6adc.jpeg

6 Likes

@hunterk @ProfessorBraun

Those shots look amazing! However, I’m still not able to see what I’m trying to see.

Going back to this image, notice how the phosphor arrangement alternates between adjacent scanlines, ABABAB. (pretend these are CRT phosphors in a dotmask arrangement)

This (at least for me) gives the appearance of uneven or badly scaled scanlines.

here’s the same thing, but at 4x vertical scale instead of 5x vertical scale. The pattern repeats. AAAAA.

image

Do real dotmask CRTs, when displaying a solid color, ever do what the first image is doing? Or do they always do what the second image is doing? Does the pattern repeat perfectly with a solid color or does it alternate as in the first image? If it always repeats like in the second image, that would explain why I never notice any apparently uneven scanlines on a real dotmask CRT. However, it would also seem odd if the pattern repeats perfectly like in the second image, given that it’s an analogue device.

1 Like

I believe that’s caused by the fact that we simply don’t have the resolution to represent the scanlines plus mask structure properly, at least not at 1080p.

That is, the phosphor structure doesn’t need to line up with the scanlines on a real CRT, since there’s like 10+ phosphor triads per scanline.

3 Likes

I’ve seen some CRTs where it was like 5 triads per scanline (13" Commodore monitors), but what you’re saying makes sense. With an LCD, the pixels representing the beam have to line up perfectly with the pixels representing the phosphor, unless you have super high resolutions and can dedicate a lot of pixels to a single phosphor. With stupidly high resolutions you could have the beam cutting through the middle of a phosphor like you sometimes see on a CRT. I’m guessing 4k probably wouldn’t cut it for this.

This also raises the question of how you would even accomplish this with a shader… my brain is tired. :sweat_smile:

edit: basically, the phosphor/beam alignment on a CRT neither alternates (ABABA) NOR repeats (AAAA) perfectly. Rather, it might gradually change over the whole screen, so you don’t even notice it.

2 Likes

composite.cgp

shaders = "10"
shader0 = "COMPOSITE/ntsc/shaders/ntsc-pass1-composite-2phase.cg"
filter_linear0 = "false"
wrap_mode0 = "clamp_to_border"
frame_count_mod0 = "2"
mipmap_input0 = "false"
alias0 = ""
float_framebuffer0 = "true"
srgb_framebuffer0 = "false"
scale_type_x0 = "absolute"
scale_x0 = "1024"
scale_type_y0 = "source"
scale_y0 = "1.000000"
shader1 = "COMPOSITE/ntsc/shaders/ntsc-pass2-2phase-gamma.cg"
filter_linear1 = "false"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = ""
float_framebuffer1 = "false"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "0.500000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "COMPOSITE/cbod-v1-pass1.cg"
filter_linear2 = "false"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
alias2 = ""
float_framebuffer2 = "false"
srgb_framebuffer2 = "false"
scale_type_x2 = "source"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "COMPOSITE/cbod-v1-pass2.cg"
filter_linear3 = "false"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = ""
float_framebuffer3 = "false"
srgb_framebuffer3 = "false"
shader4 = "COMPOSITE/crt-lottes.cg"
filter_linear4 = "false"
wrap_mode4 = "clamp_to_border"
mipmap_input4 = "false"
alias4 = ""
float_framebuffer4 = "false"
srgb_framebuffer4 = "false"
scale_type_x4 = "absolute"
scale_x4 = "1280"
scale_type_y4 = "source"
scale_y4 = "3.0"
shader5 = "COMPOSITE/sharpen - composite.cg"
wrap_mode5 = "clamp_to_border"
mipmap_input5 = "false"
alias5 = ""
float_framebuffer5 = "false"
srgb_framebuffer5 = "false"
shader6 = "COMPOSITE/oldtvshader-composite.cg"
wrap_mode6 = "clamp_to_border"
mipmap_input6 = "false"
alias6 = ""
float_framebuffer6 = "false"
srgb_framebuffer6 = "false"
shader7 = "COMPOSITE/image-adjustment.cg"
wrap_mode7 = "clamp_to_border"
mipmap_input7 = "false"
alias7 = ""
float_framebuffer7 = "false"
srgb_framebuffer7 = "false"
shader8 = "COMPOSITE/jinc2-sharper.cg"
wrap_mode8 = "clamp_to_border"
mipmap_input8 = "false"
alias8 = ""
float_framebuffer8 = "false"
srgb_framebuffer8 = "false"

shader9 = "COMPOSITE/2xsal.cg"
wrap_mode9 = "clamp_to_border"
mipmap_input9 = "false"
alias9 = ""
float_framebuffer9 = "false"
srgb_framebuffer9 = "false"

parameters = "NTSC_CRT_GAMMA;NTSC_MONITOR_GAMMA;hardScan;hardPix;warpX;warpY;maskDark;maskLight;scaleInLinearGamma;shadowMask;brightboost;target_gamma;monitor_gamma;overscan_percent_x;overscan_percent_y;saturation;contrast;luminance;bright_boost;R;G;B;ZOOM;XPOS;YPOS;V_OSMASK;H_OSMASK;JINC2_WINDOW_SINC;JINC2_SINC;JINC2_AR_STRENGTH"
NTSC_CRT_GAMMA = "2.500000"
NTSC_MONITOR_GAMMA = "2.000000"
hardScan = "-7.500000"
hardPix = "-20.000000"
warpX = "0.031000"
warpY = "0.041000"
maskDark = "1.000000"
maskLight = "0.500000"
scaleInLinearGamma = "1.000000"
shadowMask = "1.000000"
brightboost = "1.050000"
target_gamma = "1.900000"
monitor_gamma = "2.200000"
overscan_percent_x = "0.000000"
overscan_percent_y = "0.000000"
saturation = "1.000000"
contrast = "1.000000"
luminance = "1.100000"
bright_boost = "0.000000"
R = "0.8500000"
G = "0.8500000"
B = "0.8500000"
ZOOM = "1.000000"
XPOS = "0.000000"
YPOS = "0.000000"
V_OSMASK = "0.000000"
H_OSMASK = "0.000000"
JINC2_WINDOW_SINC = "0.390000"
JINC2_SINC = "0.900000"
JINC2_AR_STRENGTH = "0.800000"

crt-lottes.cg

// -- config  -- //
#pragma parameter hardScan "hardScan" -8.0 -20.0 0.0 1.0 // default, minimum, maximum, optional step
#pragma parameter hardPix "hardPix" -3.0 -20.0 0.0 1.0
#pragma parameter warpX "warpX" 0.031 0.0 0.125 0.01
#pragma parameter warpY "warpY" 0.041 0.0 0.125 0.01
#pragma parameter maskDark "maskDark" 0.5 0.0 2.0 0.1
#pragma parameter maskLight "maskLight" 1.5 0.0 2.0 0.1
#pragma parameter scaleInLinearGamma "scaleInLinearGamma" 1.0 0.0 1.0 1.0
#pragma parameter shadowMask "shadowMask" 1.0 0.0 1.0 1.0
#pragma parameter brightboost "brightness" 1.0 0.0 2.0 0.05

#ifdef PARAMETER_UNIFORM // If the shader implementation understands #pragma parameters, this is defined.
uniform float hardScan;
uniform float hardPix;
uniform float warpX;
uniform float warpY;
uniform float maskDark;
uniform float maskLight;
uniform float scaleInLinearGamma;
uniform float shadowMask;
uniform float brightboost;
#else
#define hardScan -8.0
#define hardPix -3.0
#define warpX 0.031
#define warpY 0.041
#define maskDark 0.5
#define maskLight 1.5
#define scaleInLinearGamma 1
#define shadowMask 1
#define brightboost 1

#endif
// ------------- //

void main_vertex
(
   float4 position : POSITION,
   out float4 oPosition : POSITION,
   uniform float4x4 modelViewProj,
   float2 tex : TEXCOORD,
   out float2 oTex : TEXCOORD
)
{
   oPosition = mul(modelViewProj, position);
   oTex = tex;
}

struct input
{
   float2 video_size;
   float2 texture_size;
   float2 output_size;
   float  frame_count;
   float  frame_direction;
   float frame_rotation;
};

input IN_global;
sampler2D s0_global;

#define warp float2(warpX,warpY)


//------------------------------------------------------------------------

// sRGB to Linear.
// Assuing using sRGB typed textures this should not be needed.
float ToLinear1(float c)
{
   if (scaleInLinearGamma==0) return c;
   return(c<=0.04045)?c/12.92:pow((c+0.055)/1.055,2.4);
}
float3 ToLinear(float3 c)
{
   if (scaleInLinearGamma==0) return c;
   return float3(ToLinear1(c.r),ToLinear1(c.g),ToLinear1(c.b));
}

// Linear to sRGB.
// Assuing using sRGB typed textures this should not be needed.
float ToSrgb1(float c)
{
   if (scaleInLinearGamma==0) return c;
   return(c<0.0031308?c*12.92:1.055*pow(c,0.41666)-0.055);
}

float3 ToSrgb(float3 c)
{
   if (scaleInLinearGamma==0) return c;
   return float3(ToSrgb1(c.r),ToSrgb1(c.g),ToSrgb1(c.b));
}

// Nearest emulated sample given floating point position and texel offset.
// Also zero's off screen.
float3 Fetch(float2 pos,float2 off){
  pos=(floor(pos*IN_global.texture_size.xy+off)+float2(0.5,0.5))/IN_global.texture_size.xy;
  return ToLinear(brightboost * tex2D(s0_global,pos.xy).rgb);}

// Distance in emulated pixels to nearest texel.
float2 Dist(float2 pos){pos=pos*IN_global.texture_size.xy;return -((pos-floor(pos))-float2(0.5));}
    
// 1D Gaussian.
float Gaus(float pos,float scale){return exp2(scale*pos*pos);}

// 3-tap Gaussian filter along horz line.
float3 Horz3(float2 pos,float off){
  float3 b=Fetch(pos,float2(-1.0,off));
  float3 c=Fetch(pos,float2( 0.0,off));
  float3 d=Fetch(pos,float2( 1.0,off));
  float dst=Dist(pos).x;
  // Convert distance to weight.
  float scale=hardPix;
  float wb=Gaus(dst-1.0,scale);
  float wc=Gaus(dst+0.0,scale);
  float wd=Gaus(dst+1.0,scale);
  // Return filtered sample.
  return (b*wb+c*wc+d*wd)/(wb+wc+wd);}

// 5-tap Gaussian filter along horz line.
float3 Horz5(float2 pos,float off){
  float3 a=Fetch(pos,float2(-2.0,off));
  float3 b=Fetch(pos,float2(-1.0,off));
  float3 c=Fetch(pos,float2( 0.0,off));
  float3 d=Fetch(pos,float2( 1.0,off));
  float3 e=Fetch(pos,float2( 2.0,off));
  float dst=Dist(pos).x;
  // Convert distance to weight.
  float scale=hardPix;
  float wa=Gaus(dst-2.0,scale);
  float wb=Gaus(dst-1.0,scale);
  float wc=Gaus(dst+0.0,scale);
  float wd=Gaus(dst+1.0,scale);
  float we=Gaus(dst+2.0,scale);
  // Return filtered sample.
  return (a*wa+b*wb+c*wc+d*wd+e*we)/(wa+wb+wc+wd+we);}

// Return scanline weight.
float Scan(float2 pos,float off){
  float dst=Dist(pos).y;
  return Gaus(dst+off,hardScan);}

// Allow nearest three lines to effect pixel.
float3 Tri(float2 pos){
  float3 a=Horz3(pos,-1.0);
  float3 b=Horz5(pos, 0.0);
  float3 c=Horz3(pos, 1.0);
  float wa=Scan(pos,-1.0);
  float wb=Scan(pos, 0.0);
  float wc=Scan(pos, 1.0);
  return a*wa+b*wb+c*wc;}

// Distortion of scanlines, and end of screen alpha.
float2 Warp(float2 pos){
  pos=pos*2.0-1.0;    
  pos*=float2(1.0+(pos.y*pos.y)*warp.x,1.0+(pos.x*pos.x)*warp.y);
  return pos*0.5+0.5;}

// Shadow mask.
float3 Mask(float2 pos){
  pos.x+=pos.y*3.0;
  float3 mask=float3(maskDark,maskDark,maskDark);
  pos.x=fract(pos.x/6.0);
  if(pos.x<0.333)mask.r=maskLight;
  else if(pos.x<0.666)mask.g=maskLight;
  else mask.b=maskLight;
  return mask;}    


float4 main_fragment (float2 tex : TEXCOORD, uniform sampler2D s0 : TEXUNIT0, uniform input IN) : COLOR
{
   float2 pos=Warp(tex.xy*(IN.texture_size.xy/IN.video_size.xy))*(IN.video_size.xy/IN.texture_size.xy);
   s0_global = s0;
   IN_global = IN;
   float3 outColor = Tri(pos);

   if(shadowMask)
      outColor.rgb*=Mask(floor(tex.xy*(IN.texture_size.xy/IN.video_size.xy)*IN.output_size.xy)+float2(0.5,0.5));
	  
   return float4(ToSrgb(outColor.rgb),1.0);

}

Just for fun, Ihere’s an image of a 13" Commodore 1084S monitor. It uses a slotmask and looks like the scanlines (the visible part and the black gap) are 4-5 phosphor triads thick.

So, it’s actually possible to have a fairly realistic result at 1080p and 4x vertical scale if you make a few compromises. Slotmask is out of the question, but the magenta-green checkerboard pattern works nicely for a dotmask effect. At 4x scale you wind up with 4 phosphor triads per scanline.

(increase backlight to 100% and view image at full size for proper viewing)

2 Likes