Please show off what crt shaders can do!

This would be a breakthrough for CRT emulation. As of right now, no currently available display is bright enough to do full-strength masks and scanlines AND black frame insertion, so you’re always compromising on one thing or another. If we can fake SDR content as HDR content then we’d have all the brightness we need for all the CRT effects. No one seems to know how to make this work, though. Maybe a bounty is in order…

If you want to maintain CRT-like brightness, you need a minimum of 800 nits if you want BFI @ 120 Hz and full strength mask and scanlines- that would leave you with about 100 nits. If you want to do the slotmask, you need to start with 1600 nits(!), so that might still be out of reach for even the brightest displays currently available. You’d have to reduce the slotmask strength while using BFI and scanlines.

2 Likes

Fragment program for pre-bloom processing

uniform float Exposure;
 uniform sampler2D SrcColor;
 uniform sampler2D SrcHDR;
 
 varying vec2 texCoord;
 
 const vec4 gloomStart = vec4(0.95,0.95,0.95,0.95);
 
 float sqr(float x) { return x*x; }
 vec4 sqr(vec4 x) { return x*x; }
 
 vec4 expand_Hdr(vec4 color)
 {
    return color*(sqr(color.a*2.0)+1.0);
 }
  
 void main(void)
 {
    vec4 color  = texture2D(SrcColor,texCoord);
    gl_FragColor = expand_Hdr(color*Exposure)-gloomStart;
 }

Fragment program for final composition stage

uniform float Exposure;
 uniform sampler2D SrcColor;
 uniform sampler2D SrcHDR1;
 uniform sampler2D SrcHDR2;
 uniform sampler2D SrcHDR3;
 uniform sampler2D SrcHDR4;
 uniform sampler2D Measure;
 
 uniform vec4 MipMix;
 
 float gloomIntensity=1.0;
 
 varying vec2 texCoord;
 
 float sqr(float x) { return x*x; }
 vec4 sqr(vec4 x) { return x*x; }
 
 vec4 expand_Hdr(vec4 color)
 {
    return color*(sqr(color.a*2.0)+1.0);
 }
 
 void main(void)
 {
    vec4 color  = texture2D(SrcColor,texCoord);
    vec4 gloom  = mat4(
       texture2D(SrcHDR1,texCoord),
       texture2D(SrcHDR2,texCoord),
       texture2D(SrcHDR3,texCoord),
       texture2D(SrcHDR4,texCoord) ) * MipMix;
    gl_FragColor = (expand_Hdr(color*Exposure)+gloom*16.0*gloomIntensity)*Exposure;
 }

Vertex/Fragment program for rendered objects

Vertex program

uniform vec3 LightDir;
 uniform vec4 vViewPosition;
 uniform mat4 matViewProjection;
 
 varying vec2 texCoord;
 varying vec3 normal;
 varying vec3 lightDirInTangent;
 varying vec3 viewDirInTangent;
 
 attribute vec3 rm_Tangent;
 attribute vec3 rm_Binormal;
 
 void main(void)
 {
    texCoord = gl_MultiTexCoord0.xy;
    
    mat3 tangentMat = mat3(rm_Tangent,
                           rm_Binormal,
                           gl_Normal);
    lightDirInTangent = normalize(LightDir) * tangentMat;
    viewDirInTangent  = normalize(vViewPosition-gl_Position).xyz * tangentMat;
    
    gl_Position = ftransform();
 }

Fragment program

uniform sampler2D BumpMap;
 uniform sampler2D ObjectMap;
 uniform sampler2D SpecMap;
 
 uniform float Shininess;
 uniform float SpecularIntensity;
 
 varying vec2 texCoord;
 varying vec3 lightDirInTangent;
 varying vec3 viewDirInTangent;
 
 void main(void)
 {
    vec3  n_lightDirInTangent = -normalize(lightDirInTangent);
    vec3  n_viewDirInTangent = normalize(viewDirInTangent);
    vec3  bump     = normalize(texture2D(BumpMap,texCoord).xyz * 2.0 - 1.0);
    float lighting = dot(bump,n_lightDirInTangent);
    float blighting= n_lightDirInTangent.z;
    float specular = dot(-reflect(n_lightDirInTangent,bump),n_viewDirInTangent);
    
    vec4 texColor = texture2D(ObjectMap,texCoord);
    vec4 specColor = texture2D(SpecMap, texCoord); 
    vec4 color = texColor * lighting 
               + float(lighting>0.0)*float(blighting>0.0) 
                 * SpecularIntensity*pow(specular,Shininess)*specColor;
    gl_FragColor = vec4(color.xyz*0.5,0.414);
 }

Fragment programs for first separable convolution stage

Horizontal

uniform sampler2D Src;
 
 varying vec2 texCoord;
 
 const float texDimension = 512.0;
 const float texScaler =  1.0/texDimension;
 const float texOffset = -0.5/texDimension;
 
 void main(void)
 {
    vec4 color = vec4(0.0,0.0,0.0,0.0);
    
    const float gauss0 = 1.0/32.0;
    const float gauss1 = 5.0/32.0;
    const float gauss2 =15.0/32.0;
    const float gauss3 =22.0/32.0;
    const float gauss4 =15.0/32.0;
    const float gauss5 = 5.0/32.0;
    const float gauss6 = 1.0/32.0;   
    
    vec4 gaussFilter[7];
    gaussFilter[0]  = vec4( -3.0*texScaler , 0.0, 0.0, gauss0); 
    gaussFilter[1]  = vec4( -2.0*texScaler , 0.0, 0.0, gauss1); 
    gaussFilter[2]  = vec4( -1.0*texScaler , 0.0, 0.0, gauss2); 
    gaussFilter[3]  = vec4(  0.0*texScaler , 0.0, 0.0, gauss3);
    gaussFilter[4]  = vec4( +1.0*texScaler , 0.0, 0.0, gauss4);
    gaussFilter[5]  = vec4( +2.0*texScaler , 0.0, 0.0, gauss5);
    gaussFilter[6]  = vec4( +3.0*texScaler , 0.0, 0.0, gauss6);
   
    int i;
    for (i=0;i<7;i++)
       color += texture2D(Src, texCoord + gaussFilter[i].xy) * gaussFilter[i].w;
    
    gl_FragColor = color*0.5;
 }

Vertical

uniform sampler2D Src; 
 
 varying vec2 texCoord;
 
 const float texDimension = 512.0;
 const float texScaler =  1.0/texDimension;
 const float texOffset = -0.5/texDimension;
 
 void main(void)
 {
    vec4 color = vec4(0.0,0.0,0.0,0.0);
    
    const float gauss0 = 1.0/32.0;
    const float gauss1 = 5.0/32.0;
    const float gauss2 =15.0/32.0;
    const float gauss3 =22.0/32.0;
    const float gauss4 =15.0/32.0;
    const float gauss5 = 5.0/32.0;
    const float gauss6 = 1.0/32.0;   
   
    vec4 gaussFilter[7];
    gaussFilter[0]  = vec4( -3.0*texScaler , 0.0, 0.0, gauss0).yxzw;
    gaussFilter[1]  = vec4( -2.0*texScaler , 0.0, 0.0, gauss1).yxzw;
    gaussFilter[2]  = vec4( -1.0*texScaler , 0.0, 0.0, gauss2).yxzw;
    gaussFilter[3]  = vec4(  0.0*texScaler , 0.0, 0.0, gauss3).yxzw;
    gaussFilter[4]  = vec4( +1.0*texScaler , 0.0, 0.0, gauss4).yxzw;
    gaussFilter[5]  = vec4( +2.0*texScaler , 0.0, 0.0, gauss5).yxzw;
    gaussFilter[6]  = vec4( +3.0*texScaler , 0.0, 0.0, gauss6).yxzw;
   
    for (int i=0;i<7;i++)
       color += texture2D(Src, texCoord + gaussFilter[i].xy) * gaussFilter[i].w;
    
    gl_FragColor = color*0.5;
 }

Convolution matrix for second separable convolution stage

const float gauss0 = 1.0/32.0;
   const float gauss1 = 5.0/32.0;
   const float gauss2 =15.0/32.0;
   const float gauss3 =22.0/32.0;
   const float gauss4 =15.0/32.0;
   const float gauss5 = 5.0/32.0;
   const float gauss6 = 1.0/32.0;

Convolution matrix for third separable convolution stage

const float gauss0 = 1.0/32.0;
   const float gauss1 = 5.0/32.0;
   const float gauss2 =15.0/32.0;
   const float gauss3 =22.0/32.0;
   const float gauss4 =15.0/32.0;
   const float gauss5 = 5.0/32.0;
   const float gauss6 = 1.0/32.0;

Convolution matrix for fourth separable convolution stage

const float gauss0 = 1.0/18.0;
   const float gauss1 = 2.0/18.0;
   const float gauss2 = 4.0/18.0;
   const float gauss3 = 4.0/18.0;
   const float gauss4 = 4.0/18.0;
   const float gauss5 = 2.0/18.0;
   const float gauss6 = 1.0/18.0;

Should help some coders here (i hope).

1 Like

all completely over my head but looks like you found some great resources there!

The reflection shader is the only reason i’m checking this thread every day, lol.

2 Likes

I mainly check it for that, along with to see if hunterk or guest.r is doing something new.

On that note the scanline spike mitigation is pretty nice, and I’m also looking forward to the red (smear, blur thing?) by hunterk.

I do check it for specific users shader chains.

8 posts were split to a new topic: New CRT shader from Guest

Could you please share your shader preset?

I would like to see your preset as well. Those scanlines look neat.

Which one you refer to?

Those from Sonic 1 and Earthworm Jim. I presume they use the same preset.

Ah, yes. It’s the preset i’m using for Sega Genesis/Mega Drive. It’s used in combination with the S-Video filter in the core options of GenesisPlusGX.

shaders = "4"
shader0 = "../../shaders_glsl/misc/image-adjustment.glsl"
wrap_mode0 = "clamp_to_border"
mipmap_input0 = "false"
alias0 = "BloomPass"
float_framebuffer0 = "false"
srgb_framebuffer0 = "true"
shader1 = "../../shaders_glsl/crt/shaders/gtu-v050/pass1.glsl"
wrap_mode1 = "clamp_to_border"
mipmap_input1 = "false"
alias1 = ""
float_framebuffer1 = "true"
srgb_framebuffer1 = "false"
scale_type_x1 = "source"
scale_x1 = "1.000000"
scale_type_y1 = "source"
scale_y1 = "1.000000"
shader2 = "../../shaders_glsl/crt/shaders/gtu-v050/pass2.glsl"
filter_linear2 = "false"
wrap_mode2 = "clamp_to_border"
mipmap_input2 = "false"
alias2 = ""
float_framebuffer2 = "true"
srgb_framebuffer2 = "false"
scale_type_x2 = "viewport"
scale_x2 = "1.000000"
scale_type_y2 = "source"
scale_y2 = "1.000000"
shader3 = "../../shaders_glsl/crt/shaders/crt-geom.glsl"
filter_linear3 = "false"
wrap_mode3 = "clamp_to_border"
mipmap_input3 = "false"
alias3 = ""
float_framebuffer3 = "false"
srgb_framebuffer3 = "false"
scale_type_x3 = "viewport"
scale_x3 = "1.000000"
scale_type_y3 = "viewport"
scale_y3 = "1.000000"
parameters = "ia_target_gamma;ia_monitor_gamma;ia_overscan_percent_x;ia_overscan_percent_y;ia_saturation;ia_contrast;ia_luminance;ia_black_level;ia_bright_boost;ia_R;ia_G;ia_B;ia_ZOOM;ia_XPOS;ia_YPOS;ia_TOPMASK;ia_BOTMASK;ia_LMASK;ia_RMASK;ia_GRAIN_STR;ia_SHARPEN;ia_FLIP_HORZ;ia_FLIP_VERT;compositeConnection;signalResolution;signalResolutionI;signalResolutionQ;CRTgamma;monitorgamma;d;CURVATURE;R;cornersize;cornersmooth;x_tilt;y_tilt;overscan_x;overscan_y;DOTMASK;SHARPER;scanline_weight;lum"
ia_target_gamma = "2.200000"
ia_monitor_gamma = "2.200000"
ia_overscan_percent_x = "0.000000"
ia_overscan_percent_y = "0.000000"
ia_saturation = "1.000000"
ia_contrast = "1.000000"
ia_luminance = "1.100000"
ia_black_level = "0.010000"
ia_bright_boost = "0.050000"
ia_R = "1.000000"
ia_G = "1.000000"
ia_B = "1.000000"
ia_ZOOM = "1.000000"
ia_XPOS = "0.000000"
ia_YPOS = "0.000000"
ia_TOPMASK = "0.000000"
ia_BOTMASK = "0.000000"
ia_LMASK = "0.000000"
ia_RMASK = "0.000000"
ia_GRAIN_STR = "0.000000"
ia_SHARPEN = "0.000000"
ia_FLIP_HORZ = "0.000000"
ia_FLIP_VERT = "0.000000"
compositeConnection = "0.000000"
signalResolution = "256.000000"
signalResolutionI = "83.000000"
signalResolutionQ = "25.000000"
CRTgamma = "2.400000"
monitorgamma = "2.300000"
d = "1.600000"
CURVATURE = "1.000000"
R = "2.400000"
cornersize = "0.025000"
cornersmooth = "1000.000000"
x_tilt = "0.000000"
y_tilt = "0.000000"
overscan_x = "100.000000"
overscan_y = "100.000000"
DOTMASK = "0.300000"
SHARPER = "1.000000"
scanline_weight = "0.350000"
lum = "0.000000"

First two passes from gtuv50 shader and fakelottes as third pass. Signal Resolution set to 160 for blend the dithering.

PSX game Legacy Of Kain: Soul Reaver.

2 Likes

Here’s a new version of the Bezel Reflection shader

I’ve separated out the scaling into an include file hsm-mega-screen-scale.h which defines user parameters and can be used by the shaders in the chain which need to match scale including:

  • CRT Shader
  • Integer Scale
  • Bezel Overlay Scale
  • Scaling of Reflection

Some of the stuff the screen scale does:

  • Aspect Ratio
  • Integer Scale or non integer scale
  • Vertical Scanlines
  • Horizontal Integer scale for vertical scanlines
  • Crop Overscan to allow you to crop and properly affect the integer scale
  • Integer scale multiple offset to make the screen smaller or bigger if needed
  • Small scaling offset so it can be adjusted just a small amount on top of the integer scale

There are two presets included, one for horizontal or vertical. The bezel is oversized so that it will work at multiple aspect ratios and resolutions

Here’s a link to the package https://1drv.ms/u/s!AlJgyN_LYasynL5AnfjEGQakBCUgew?e=9md61z

To use this you must to set your video settings aspect ratio to your monitor resolution aspect ratio and integer scale to off, since the shader handles the aspect ratio and integer scale.

This is slower than the last version because it is done as additional passes on top of the CRT shader. The reason it was done this way was so that it can be integrated more easily on top of other CRT shaders.

The version of Guest’s crt shader here is a copy from last month with alterations to use the shared scaling method, screen distortion & overscan crop. Thanks Guest for this awesome Shader!

Here’s a breakdown of the passes

And some snapshots of different games

In terms of what’s left to do, there are a few things: *The shader probably needs some optimization, it’s slower than the old version

  • Auto generation of the bezel. @Syh’s suggestion with the shadertoy example (https://www.shadertoy.com/view/MljXDG) are a great idea and I’ll probably try this out. This is because getting the inner edges of the bezel to correctly match the screen curvature when creating the bezel graphics is one of the more frustrating parts of the process
  • Add static noise on top of the reflection for surface texture reflectance variation which help with the realism you can see a shadertoy of something like this here https://www.shadertoy.com/view/lt2SDK
  • Improve the reflection approaching the corners because right now there is a point where it cuts off
  • Add the tight reflections in the corners to be driven by the bezel reflection
  • Need separate reflection brightness control over for the top, bottom and sides

Let me know what you think about it and if you have any problems on any hardware especially non NVidia hardware because that’s what I’m testing on.

I also wanted to add a big thanks to @hunterk who helped my with his expertise along the way!

11 Likes

I’ve been checking this thread out everyday waiting for this update. Amazing work. I love that the border scales regardless of aspect ratio.

Short video showing the new version off along with my terrible Super Hang-On skills.

4 Likes

Just tried a few games wit HyperspaceMadness’s bezel and looks really good, just a few things ive notice…

  • Is it possible to remove the screen reflection in the top left and right?

  • Can the ‘sliver/white’ baked in reflection in the corners be toned down or remove as now we have simulated reflections it kind of looks out of place

  • Hopefully the reflection effect can be pushed right to the corners as it does look odd that it fades away when getting close to the corners

Overall a great version 1.0 release!!!

Hi @Arviel thanks for checking it out!

@BlockABoots for your questions,

I was thinking the same thing about getting rid of the reflection on the screen in the top left & right, so next time I update it I’ll remove that

I’ll look into toning down the corner highlights. Ideally these will be replaced by auto-generated ones from the reflection.

For the fade near the corners I’m definitely going to address this as it annoys me as well.

1 Like

Ive noticed that dark colours like blues and purples dont seem to cast a reflection at all, look at the TMNT intro the dark purple doesnt seem to be reflected at all and then on Afterburner on the night stage the dark blue on the horizon doesnt seem to cast a reflection either. I feel even though these are dark colours they would still cast a reflection as the tube is still pushing out light even through dark colours

3 Likes

At least for the TMNT screen, I’m pretty sure I can make out the purple reflection it just blends really well into the bezel (probably because of the darkness of the color or something, some one with a better understanding of color theory then myself would probably know why).

Maybe we need some kind of saturation/brightness boost for the bezel reflection glow.

Hey @BlockABoots & @Syh, there are some parameters you can play with in the settings for brightness, gamma, contrast & saturation. If you set all these parameters to 1.0 you will get a reflection which has the same brightness of the screen.

It’s possible that the default for contrast is too high, or the way I’m doing contrast is making coolers mid range brightness colors too dark.

The contrast & Gamma are there mostly because in a number of reference images of CRTs it seems the brighter colours show up more.

2 Likes

@HyperspaceMadness Since the shader has its own built-in integer scaling, should the RA integer scaling be turned off?