Cheap way to approximate phosphor glow/bleed?

I’d like to try adding something that approximates phosphor glow/bleed, but most options I’ve seen are pretty GPU-intensive. It’d be cool if there was a way to do this that would work on a 10 year old computer.

Is there a way to blend adjacent pixels so that brighter pixels bleed into darker ones, with the amount of bleed determined by the brightness of the brighter pixel (ie, brighter pixels should bleed more)?

A series of blurs could work. For example, suppose the brightness range is 0-100 (just to simplify things). You could have 5 blurs: 1-20, 21-40, 41-60, 61-80, 81-100. Each blur would blur the pixels by varying amounts, with 81-100 being the most blur.

I think this would have to go after scanlines to look right, is that correct?

I’m might be asking for something that already exists.

Is there any way to approximate phosphor glow/bleed that is less demanding GPU-wise than current methods?

I don’t know of any way besides the classic faux HDR bloom method of blur + blend. There are some speed tricks you can do with mipmaps and textureLod, but in my experience that looks bad as the blur “twinkles” with movement/scrolling and it’s a crappy box blur instead of nice gaussian anyway.

can you elaborate on this? I think I know what you mean, but I’m not 100% sure.

The same way the various glow/halation shaders do it. They do a 2-pass gaussian blur and then merge that blurred image with the CRT/phosphor-ed image using a mix, or an addition or a screen combine, etc.

2 Likes

A 2-pass, at least how I’m doing it, doesn’t work well with variable blur widths. It ends up looking blurred vertically and horizontally, rather than evenly blurring outward. However, unrolling the 2-pass blur into all the required coordinates and texture lookups ends up looking correct but it drastically increases the resource usage, so that’s not viable for your 10-year-old machine.

The cheapest method I’ve found for a bloom that adds more blurry glow to bright areas is by calculating the luminosity of the blurred image and using this to control how much the blur is blended into the image, rather than evenly blending in the image by the same amount. High luminosity areas bleed outward to let the surrounding area glow and fade out the farther it gets from the source, low luminosity areas stay darker.

3 Likes

Is this method used in any existing shaders? A cheap phosphor glow effect could be really useful.

I imagine he’s using it in his new shader.

I know it’s definitely in my Candy Bloom shader. It’s in crt/shaders/torridgristle/ in GLSL and Slang.

It also does some other things like max out HSB brightness of the blur and then blending it in based on luminance before it was brightened.

2 Likes

Candy Bloom is a nice-looking effect, but the shader preset is still a bit too intense for this poor old machine that I was going to use for a MAME cab.

After playing around a bit, I was able to get a reasonably convincing phosphor-like glow by manipulating scanline brightness offset/overdrive effect and scanline alpha/fade. A bit of x-axis blur also works wonders. You can get bright pixels to appear to glow brighter just by manipulating scanline overdrive effect and scanline fade. It’s not quite as dynamic as emulating the individual phosphors, but it looks good enough at normal viewing distances.

Currently I’m using tvout-tweaks-multipass+zfast_crt. I was using interlacing, but the interlacing shader lacks the scanline brightness offset/overdrive effect, which gives the scanlines some faux glow and beam dynamics by blending the top and bottom edges of each scanline.

My current settings:

alias0 = ""
alias1 = ""
alias2 = ""
BLURSCALEX = "0.500000"
BRIGHTBOOST = "1.000000"
CRT_GAMMA = "2.400000"
DOTMASK_STRENGTH = "0.300000"
filter_linear0 = "true"
filter_linear1 = "true"
filter_linear2 = "true"
float_framebuffer0 = "true"
float_framebuffer1 = "false"
float_framebuffer2 = "false"
HILUMSCAN = "8.000000"
LOWLUMSCAN = "10.000000"
MASK_DARK = "0.000000"
MASK_FADE = "0.500000"
maskDark = "1.000000"
maskLight = "1.000000"
mipmap_input0 = "false"
mipmap_input1 = "false"
mipmap_input2 = "false"
parameters = "TVOUT_COMPOSITE_CONNECTION;TVOUT_TV_COLOR_LEVELS;CRT_GAMMA;TVOUT_RESOLUTION;TVOUT_RESOLUTION_Y;TVOUT_RESOLUTION_I;TVOUT_RESOLUTION_Q;BLURSCALEX;LOWLUMSCAN;HILUMSCAN;BRIGHTBOOST;MASK_DARK;MASK_FADE;shadowMask;DOTMASK_STRENGTH;maskDark;maskLight"
shader0 = "D:\retroarch\shaders\shaders_glsl\crt\shaders\tvout-tweaks-multipass\tvout-tweaks-pass-0.glsl"
shader1 = "D:\retroarch\shaders\shaders_glsl\crt\shaders\tvout-tweaks-multipass\tvout-tweaks-pass-1.glsl"
shader2 = "D:\retroarch\shaders\shaders_glsl\crt\shaders\zfast_crt+dotmask.glsl"
shaders = "3"
shadowMask = "2.000000"
srgb_framebuffer0 = "true"
srgb_framebuffer1 = "false"
srgb_framebuffer2 = "false"
TVOUT_COMPOSITE_CONNECTION = "0.000000"
TVOUT_RESOLUTION = "512.000000"
TVOUT_RESOLUTION_I = "83.199997"
TVOUT_RESOLUTION_Q = "25.600000"
TVOUT_RESOLUTION_Y = "256.000000"
TVOUT_TV_COLOR_LEVELS = "0.000000"
wrap_mode0 = "clamp_to_border"
wrap_mode1 = "clamp_to_border"
wrap_mode2 = "clamp_to_border"
1 Like

The only thing I can think of for keeping it really lightweight puts a limit on how far out it blurs due to limiting the number of texture reads, and it’s likely only going to look right when applied to the source resolution and not something that’s been upscaled a lot, or maybe it would look fine with a larger radius I’m not sure. https://pastebin.com/ZYqDrEPY It’s not formatted for RetroArch’s GLSL format but the gist of it should be there. To adjust the radius modify the number that Lum is multiplied by. If you make it spread out too far you’ll need to mix the original Picture vec3.

The way it works is fairly simple. The darker a pixel is, the farther out it samples adjacent pixels for blurring since it’s kind of coded backwards from “light spreads out” as “dark sucks in”. It currently relies on linear interpolation to blend the center pixel in with the adjacent sampled pixels. By moving 50% away from the center pixel at a diagonal you end up with 25% Center, 25% Left, 25% Bottom, 25% Bottom Left pixels. The center pixel gets read at 25% 4 times, the Up/Down/Left/Right pixels get read at 25% 2 times each, and the diagonals each get read at 25% only 1 time. This becomes something like 57% Center, 29% Cardinal, 14% Diagonal I think. It’s not quite gaussian but it’s good enough. Also it’s pretty fast, especially when used on low resolutions like direct emulator output.

1 Like

It seems like it would work on something that’s been upscaled with nearest neighbor integer scaling. Or would upscaling before applying the psuedo-gaussian blur slow it down too much for what we want?

Also, could you use this in addition to something like sharp-bilinear or tvout-tweaks to correct scaling artifacts from non-integer scaling?

Also, how would this look with scanlines? Would this go before or after scanlines?

I’d like to give this a try, but I lack the skillz to convert it to GLSL. @hunterk :smiley:

Oh, here’s a screenshot of it applied to a screenshot at native resolution and then upscaled 2x. https://i.imgur.com/WKmmaiD.png

Here’s the dry image: https://i.imgur.com/iJ9mqvJ.png

It’d be fine with any of those interpolations before it, it’s just that the blurring will look smaller if it’s upscaled before using the effect, but boosting the blur radius to match might be fine.

As for how it’ll look with scanlines, it depends on how you implement it. It’ll smooth out a 2x upscale with simple 0,1,0,1 scanlines so it may not need anything special. https://i.imgur.com/WDjvNkx.png

I’m thinking maybe I can work in nearest neighbor / smootherstep interpolation and prevent the need for additional shaders for performance, but I’d need to change a few core elements.

Edit: Just tried it and I haven’t found anything that looks nice.

1 Like

So could you upscale to 5x nearest neighbor, apply the pseudo-gaussian blur, and adjust the blur radius to 5x normal? Would that work without slowing it down too much?

If that works, then you could take care of scaling artifacts with TVout-tweaks or sharp-bilinear as the first pass, then apply the pseudo-gaussian blur as the second pass and adjust the blur radius accordingly. Then as a third pass you would add scanlines(?).

Here it is in RA’s GLSL:

// version directive if necessary

// good place for credits/license

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    TEX0.xy = TexCoord.xy;
}

#elif defined(FRAGMENT)

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out COMPAT_PRECISION vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
    vec3 Picture = texture(Source,vTexCoord).xyz;
   
    float Lum = ((0.299*Picture.x) + (0.587*Picture.y) + (0.114*Picture.z));
          Lum = 1-Lum;
          Lum = Lum * 0.5;
   
    vec3 PictureBlur = texture(Source,vTexCoord+SourceSize.zw*vec2( Lum, Lum)).xyz;
        PictureBlur += texture(Source,vTexCoord+SourceSize.zw*vec2(-Lum, Lum)).xyz;
        PictureBlur += texture(Source,vTexCoord+SourceSize.zw*vec2( Lum,-Lum)).xyz;
        PictureBlur += texture(Source,vTexCoord+SourceSize.zw*vec2(-Lum,-Lum)).xyz;
        PictureBlur *= 0.25;
   
    FragColor = vec4(PictureBlur,0);
} 
#endif

Looks like this:

Doing it in linear gamma avoids accentuating the dark bits:

// version directive if necessary

// good place for credits/license

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;

vec4 _oPosition1; 
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    TEX0.xy = TexCoord.xy;
}

#elif defined(FRAGMENT)

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out COMPAT_PRECISION vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;

// compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define OutSize vec4(OutputSize, 1.0 / OutputSize)

void main()
{
    vec3 Picture = texture(Source,vTexCoord).xyz;
   
    float Lum = ((0.299*Picture.x) + (0.587*Picture.y) + (0.114*Picture.z));
          Lum = 1-Lum;
          Lum = Lum * 0.5;
   
    vec3 PictureBlur = pow(texture(Source,vTexCoord+SourceSize.zw*vec2( Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,vTexCoord+SourceSize.zw*vec2(-Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,vTexCoord+SourceSize.zw*vec2( Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,vTexCoord+SourceSize.zw*vec2(-Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur *= 0.25;
   
    FragColor = vec4(pow(PictureBlur, vec3(1.0/2.2)),1.0);
} 
#endif

Looks like this:

And that preserves the brightness a bit if you add in scanlines and sharp-interpolation:

//SmuberStep - Like SmoothestStep but even Smoothester
//by torridgristle

#pragma parameter SHARP_BILINEAR_PRE_SCALE "Sharp Bilinear Prescale" 4.0 1.0 10.0 1.0
#pragma parameter AUTO_PRESCALE "Automatic Prescale" 1.0 0.0 1.0 1.0
#pragma parameter amp          "Amplitude"      1.2500  0.000 2.000 0.05
#pragma parameter phase        "Phase"          0.5000  0.000 2.000 0.05
#pragma parameter lines_black  "Lines Blacks"   0.0000  0.000 1.000 0.05
#pragma parameter lines_white  "Lines Whites"   1.0000  0.000 2.000 0.05
 
#define freq             0.500000
#define offset           0.000000
#define pi               3.141592654

#ifndef PARAMETER_UNIFORM
#define amp              1.250000
#define phase            0.500000
#define lines_black      0.000000
#define lines_white      1.000000
#endif

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// vertex compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define outsize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float amp;
uniform COMPAT_PRECISION float phase;
uniform COMPAT_PRECISION float lines_black;
uniform COMPAT_PRECISION float lines_white;
#endif

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    COL0 = COLOR;
    TEX0.xy = TexCoord.xy;
    float omega = 2.0 * pi * freq;              // Angular frequency
    angle = TEX0.y * omega * TextureSize.y + phase;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

// fragment compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define outsize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
// All parameter floats need to have COMPAT_PRECISION in front of them
uniform COMPAT_PRECISION float SHARP_BILINEAR_PRE_SCALE;
uniform COMPAT_PRECISION float AUTO_PRESCALE;
uniform COMPAT_PRECISION float amp;
uniform COMPAT_PRECISION float phase;
uniform COMPAT_PRECISION float lines_black;
uniform COMPAT_PRECISION float lines_white;
#else
#define SHARP_BILINEAR_PRE_SCALE 4.0
#define AUTO_PRESCALE 1.0
#endif

void main()
{
    vec2 SStep = vTexCoord * SourceSize.xy + 0.5;
	vec2 SStepInt = floor(SStep);
	vec2 SStepFra = SStep - SStepInt;

    SStep = ((924.*pow(SStepFra,vec2(13)) - 6006.*pow(SStepFra,vec2(12)) + 16380.*pow(SStepFra,vec2(11)) - 24024.*pow(SStepFra,vec2(10)) + 20020.*pow(SStepFra,vec2(9)) - 9009.*pow(SStepFra,vec2(8)) + 1716.*pow(SStepFra,vec2(7))) + SStepInt - 0.5) * SourceSize.zw;

        vec3 Picture = texture(Source,SStep).xyz;
   
    float Lum = ((0.299*Picture.x) + (0.587*Picture.y) + (0.114*Picture.z));
          Lum = 1-Lum;
          Lum = Lum * 0.5;
   
    vec3 PictureBlur = pow(texture(Source,SStep+SourceSize.zw*vec2( Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2(-Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2( Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2(-Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur *= 0.25;
        float grid;
 
    float lines;
 
    lines = sin(angle);
    lines *= amp;
    lines += offset;
    lines = abs(lines);
    lines *= lines_white - lines_black;
    lines += lines_black;
    PictureBlur *= lines;
   
    FragColor = vec4(pow(PictureBlur, vec3(1.0/2.2)),1.0);
} 
#endif

I’m still getting around 900 fps on my office GPU, so it still seems quite fast.

FAKEDIT: and because I suspect someone will request it anyway, here it is with dotmasks added:

//SmuberStep - Like SmoothestStep but even Smoothester
//by torridgristle

#pragma parameter amp          "Amplitude"      1.2500  0.000 2.000 0.05
#pragma parameter phase        "Phase"          0.5000  0.000 2.000 0.05
#pragma parameter lines_black  "Lines Blacks"   0.0000  0.000 1.000 0.05
#pragma parameter lines_white  "Lines Whites"   1.0000  0.000 2.000 0.05

#pragma parameter shadowMask "Mask Style" 3.0 -1.0 4.0 1.0
#pragma parameter DOTMASK_STRENGTH "CGWG Dot Mask Strength" 0.3 0.0 1.0 0.01
#pragma parameter maskDark "Lottes maskDark" 0.5 0.0 2.0 0.1
#pragma parameter maskLight "Lottes maskLight" 1.5 0.0 2.0 0.1
 
#define freq             0.500000
#define offset           0.000000
#define pi               3.141592654

#ifndef PARAMETER_UNIFORM
#define amp              1.250000
#define phase            0.500000
#define lines_black      0.000000
#define lines_white      1.000000
#endif

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// vertex compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define outsize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float amp;
uniform COMPAT_PRECISION float phase;
uniform COMPAT_PRECISION float lines_black;
uniform COMPAT_PRECISION float lines_white;
#endif

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    COL0 = COLOR;
    TEX0.xy = TexCoord.xy;
    float omega = 2.0 * pi * freq;              // Angular frequency
    angle = TEX0.y * omega * TextureSize.y + phase;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

// fragment compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define outsize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float amp;
uniform COMPAT_PRECISION float phase;
uniform COMPAT_PRECISION float lines_black;
uniform COMPAT_PRECISION float lines_white;
uniform COMPAT_PRECISION float shadowMask;
uniform COMPAT_PRECISION float DOTMASK_STRENGTH;
uniform COMPAT_PRECISION float maskDark;
uniform COMPAT_PRECISION float maskLight;
#else
#define shadowMask 3.0
#define DOTMASK_STRENGTH 0.3
#define maskDark 0.5
#define maskLight 1.5
#endif

#define mod_factor vTexCoord.x * SourceSize.x * outsize.x / SourceSize.x

// Shadow mask.
vec3 Mask(vec2 pos)
{
   vec3 mask = vec3(maskDark, maskDark, maskDark);
   
   // Very compressed TV style shadow mask.
   if (shadowMask == 1.0)
   {
      float line = maskLight;
      float odd  = 0.0;

      if (fract(pos.x/6.0) < 0.5)
         odd = 1.0;
      if (fract((pos.y + odd)/2.0) < 0.5)
         line = maskDark;

      pos.x = fract(pos.x/3.0);
    
      if      (pos.x < 0.333) mask.r = maskLight;
      else if (pos.x < 0.666) mask.g = maskLight;
      else                    mask.b = maskLight;
      mask*=line;  
   } 

   // Aperture-grille.
   else if (shadowMask == 2.0)
   {
      pos.x = fract(pos.x/3.0);

      if      (pos.x < 0.333) mask.r = maskLight;
      else if (pos.x < 0.666) mask.g = maskLight;
      else                    mask.b = maskLight;
   } 

   // Stretched VGA style shadow mask (same as prior shaders).
   else if (shadowMask == 3.0)
   {
      pos.x += pos.y*3.0;
      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;
   }

   // VGA style shadow mask.
   else if (shadowMask == 4.0)
   {
      pos.xy = floor(pos.xy*vec2(1.0, 0.5));
      pos.x += pos.y*3.0;
      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;
}

// torridgristle's shadowmask code
const float Pi = 3.1415926536;

vec3 SinPhosphor(vec3 image)
{
    float MaskR = sin(OutputSize.x*vTexCoord.x*Pi*1.0+Pi*0.00000+vTexCoord.y*OutputSize.y*Pi*0.5)*0.5+0.5;
    float MaskG = sin(OutputSize.x*vTexCoord.x*Pi*1.0+Pi*1.33333+vTexCoord.y*OutputSize.y*Pi*0.5)*0.5+0.5;
    float MaskB = sin(OutputSize.x*vTexCoord.x*Pi*1.0+Pi*0.66667+vTexCoord.y*OutputSize.y*Pi*0.5)*0.5+0.5;

    vec3 Mask = vec3(MaskR,MaskG,MaskB);
    
    Mask = min(Mask*2.0,1.0);
    
    return vec3(Mask * image);
}

void main()
{
    vec2 SStep = vTexCoord * SourceSize.xy + 0.5;
	vec2 SStepInt = floor(SStep);
	vec2 SStepFra = SStep - SStepInt;

    SStep = ((924.*pow(SStepFra,vec2(13)) - 6006.*pow(SStepFra,vec2(12)) + 16380.*pow(SStepFra,vec2(11)) - 24024.*pow(SStepFra,vec2(10)) + 20020.*pow(SStepFra,vec2(9)) - 9009.*pow(SStepFra,vec2(8)) + 1716.*pow(SStepFra,vec2(7))) + SStepInt - 0.5) * SourceSize.zw;

        vec3 Picture = texture(Source,SStep).xyz;
   
    float Lum = ((0.299*Picture.x) + (0.587*Picture.y) + (0.114*Picture.z));
          Lum = 1-Lum;
          Lum = Lum * 0.5;
   
    vec3 PictureBlur = pow(texture(Source,SStep+SourceSize.zw*vec2( Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2(-Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2( Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2(-Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur *= 0.25;
        float grid;
 
    float lines;
 
    lines = sin(angle);
    lines *= amp;
    lines += offset;
    lines = abs(lines);
    lines *= lines_white - lines_black;
    lines += lines_black;
    PictureBlur *= lines;
    
       float mask = 1.0 - DOTMASK_STRENGTH;

   //cgwg's dotmask emulation:
   //Output pixels are alternately tinted green and magenta
   vec3 dotMaskWeights = mix(vec3(1.0, mask, 1.0),
                             vec3(mask, 1.0, mask),
                             floor(mod(mod_factor, 2.0)));
   if (shadowMask > 0.5) 
   {
      PictureBlur *= Mask(floor(1.000001 * gl_FragCoord.xy + vec2(0.5,0.5)));
      FragColor = vec4(pow(PictureBlur, vec3(1.0/2.2)),1.0);

      return;
   }
   else if (shadowMask == 0.)
   {
      PictureBlur = pow(PictureBlur, vec3(1.0/2.2));
      PictureBlur *= dotMaskWeights;
      FragColor = vec4(PictureBlur,1.0);
      return;
   }
   else 
   {
      PictureBlur = pow(PictureBlur, vec3(1.0/2.2));
      PictureBlur = pow(PictureBlur, vec3(1.0/2.2)); //dunno why this needed double delinearization but whatever
      PictureBlur *= SinPhosphor(PictureBlur);
      FragColor = vec4(PictureBlur,1.0);
   }
} 
#endif

I’m getting some funky stuff going on from the sine-abs scanline code at 5x scale. Dunno what that’s all about, but it might require switching to another scanline code.

1 Like

Nice! You’re the best. Seems like this could be very useful!

:sweat_smile:

Thanks!

1 Like

Try this one instead:

 /*
    Scanlines Sine Max
    An ultra light scanline shader
    by RiskyJumps
	license: public domain
*/

#pragma parameter AMPLITUDE  "Scanlines Depth"  1.0000  0.000 4.000 0.05
#pragma parameter LINES_BLACK  "Lines Blacks"   0.9000 -1.000 1.000 0.05
#pragma parameter LINES_WHITE  "Lines Whites"   1.5000  0.000 2.000 0.05
#pragma parameter PHASE        "Phase"          1.0000 -2.000 2.000 0.5

#define freq             1.000000
#define PI               3.141592654

const float omega = 2.0 * PI * freq;        // Angular frequency

#if defined(VERTEX)

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying
#define COMPAT_ATTRIBUTE attribute
#define COMPAT_TEXTURE texture2D
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

vec4 _oPosition1;
uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float PHASE;
#endif

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    TEX0.xy = TexCoord.xy;
    angle = TEX0.y * TextureSize.y * omega - PHASE;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float AMPLITUDE;
uniform COMPAT_PRECISION float LINES_BLACK;
uniform COMPAT_PRECISION float LINES_WHITE;
uniform COMPAT_PRECISION float PHASE;
#else
#define AMPLITUDE        1.000000
#define LINES_BLACK      1.000000
#define LINES_WHITE      1.500000
#define PHASE            0.500000
#endif

void main()
{
    float lines;
    lines = sin(angle);
	lines *= AMPLITUDE;
    lines = clamp(lines, 0.0, 1.0);
    lines *= LINES_WHITE - LINES_BLACK;
    lines += LINES_BLACK;

    vec4 color;
	vec3 source = COMPAT_TEXTURE(Texture, TEX0.xy).rgb;
	source *= lines;
	color.xyz = source;				// Avoid a MOV instruction trick
    FragColor = color;
}
#endif

If were are talking about regular bloom, I have one that is really fast and I have another one in my mind for vectors but I’m really busy right now, I’ll get back on that if you are interested.

3 Likes

I’m definitely interested :smiley:

That fixed it right up.

//SmuberStep - Like SmoothestStep but even Smoothester
//by torridgristle

#pragma parameter AMPLITUDE  "Scanlines Depth"  1.0000  0.000 4.000 0.05
#pragma parameter LINES_BLACK  "Lines Blacks"   0.9000 -1.000 1.000 0.05
#pragma parameter LINES_WHITE  "Lines Whites"   1.5000  0.000 2.000 0.05
#pragma parameter PHASE        "Phase"          1.0000 -2.000 2.000 0.5

#pragma parameter shadowMask "Mask Style" 3.0 -1.0 4.0 1.0
#pragma parameter DOTMASK_STRENGTH "CGWG Dot Mask Strength" 0.3 0.0 1.0 0.01
#pragma parameter maskDark "Lottes maskDark" 0.5 0.0 2.0 0.1
#pragma parameter maskLight "Lottes maskLight" 1.5 0.0 2.0 0.1
 
#define freq             1.000000
#define PI               3.141592654

const float omega = 2.0 * PI * freq;        // Angular frequency

#ifndef PARAMETER_UNIFORM
#define amp              1.250000
#define phase            0.500000
#define lines_black      0.000000
#define lines_white      1.000000
#endif

#if defined(VERTEX)

#if __VERSION__ >= 130
#define COMPAT_VARYING out
#define COMPAT_ATTRIBUTE in
#define COMPAT_TEXTURE texture
#else
#define COMPAT_VARYING varying 
#define COMPAT_ATTRIBUTE attribute 
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

COMPAT_ATTRIBUTE vec4 VertexCoord;
COMPAT_ATTRIBUTE vec4 COLOR;
COMPAT_ATTRIBUTE vec4 TexCoord;
COMPAT_VARYING vec4 COL0;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

uniform mat4 MVPMatrix;
uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;

// vertex compatibility #defines
#define vTexCoord TEX0.xy
#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define outsize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float PHASE;
#endif

void main()
{
    gl_Position = MVPMatrix * VertexCoord;
    COL0 = COLOR;
    TEX0.xy = TexCoord.xy * 1.0001;
    angle = TEX0.y * TextureSize.y * omega - PHASE;
}

#elif defined(FRAGMENT)

#if __VERSION__ >= 130
#define COMPAT_VARYING in
#define COMPAT_TEXTURE texture
out vec4 FragColor;
#else
#define COMPAT_VARYING varying
#define FragColor gl_FragColor
#define COMPAT_TEXTURE texture2D
#endif

#ifdef GL_ES
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
#define COMPAT_PRECISION mediump
#else
#define COMPAT_PRECISION
#endif

uniform COMPAT_PRECISION int FrameDirection;
uniform COMPAT_PRECISION int FrameCount;
uniform COMPAT_PRECISION vec2 OutputSize;
uniform COMPAT_PRECISION vec2 TextureSize;
uniform COMPAT_PRECISION vec2 InputSize;
uniform sampler2D Texture;
COMPAT_VARYING vec4 TEX0;
COMPAT_VARYING float angle;

// fragment compatibility #defines
#define Source Texture
#define vTexCoord TEX0.xy

#define SourceSize vec4(TextureSize, 1.0 / TextureSize) //either TextureSize or InputSize
#define outsize vec4(OutputSize, 1.0 / OutputSize)

#ifdef PARAMETER_UNIFORM
uniform COMPAT_PRECISION float AMPLITUDE;
uniform COMPAT_PRECISION float LINES_BLACK;
uniform COMPAT_PRECISION float LINES_WHITE;
uniform COMPAT_PRECISION float PHASE;
uniform COMPAT_PRECISION float shadowMask;
uniform COMPAT_PRECISION float DOTMASK_STRENGTH;
uniform COMPAT_PRECISION float maskDark;
uniform COMPAT_PRECISION float maskLight;
#else
#define AMPLITUDE        1.000000
#define LINES_BLACK      1.000000
#define LINES_WHITE      1.500000
#define PHASE            0.500000
#define shadowMask 3.0
#define DOTMASK_STRENGTH 0.3
#define maskDark 0.5
#define maskLight 1.5
#endif

#define mod_factor vTexCoord.x * SourceSize.x * outsize.x / SourceSize.x

// Shadow mask.
vec3 Mask(vec2 pos)
{
   vec3 mask = vec3(maskDark, maskDark, maskDark);
   
   // Very compressed TV style shadow mask.
   if (shadowMask == 1.0)
   {
      float line = maskLight;
      float odd  = 0.0;

      if (fract(pos.x/6.0) < 0.5)
         odd = 1.0;
      if (fract((pos.y + odd)/2.0) < 0.5)
         line = maskDark;

      pos.x = fract(pos.x/3.0);
    
      if      (pos.x < 0.333) mask.r = maskLight;
      else if (pos.x < 0.666) mask.g = maskLight;
      else                    mask.b = maskLight;
      mask*=line;  
   } 

   // Aperture-grille.
   else if (shadowMask == 2.0)
   {
      pos.x = fract(pos.x/3.0);

      if      (pos.x < 0.333) mask.r = maskLight;
      else if (pos.x < 0.666) mask.g = maskLight;
      else                    mask.b = maskLight;
   } 

   // Stretched VGA style shadow mask (same as prior shaders).
   else if (shadowMask == 3.0)
   {
      pos.x += pos.y*3.0;
      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;
   }

   // VGA style shadow mask.
   else if (shadowMask == 4.0)
   {
      pos.xy = floor(pos.xy*vec2(1.0, 0.5));
      pos.x += pos.y*3.0;
      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;
}

// torridgristle's shadowmask code
const float Pi = 3.1415926536;

vec3 SinPhosphor(vec3 image)
{
    float MaskR = sin(OutputSize.x*vTexCoord.x*Pi*1.0+Pi*0.00000+vTexCoord.y*OutputSize.y*Pi*0.5)*0.5+0.5;
    float MaskG = sin(OutputSize.x*vTexCoord.x*Pi*1.0+Pi*1.33333+vTexCoord.y*OutputSize.y*Pi*0.5)*0.5+0.5;
    float MaskB = sin(OutputSize.x*vTexCoord.x*Pi*1.0+Pi*0.66667+vTexCoord.y*OutputSize.y*Pi*0.5)*0.5+0.5;

    vec3 Mask = vec3(MaskR,MaskG,MaskB);
    
    Mask = min(Mask*2.0,1.0);
    
    return vec3(Mask * image);
}

void main()
{
    vec2 SStep = vTexCoord * SourceSize.xy + 0.4999;
	vec2 SStepInt = floor(SStep);
	vec2 SStepFra = SStep - SStepInt;

    SStep = ((924.*pow(SStepFra,vec2(13)) - 6006.*pow(SStepFra,vec2(12)) + 16380.*pow(SStepFra,vec2(11)) - 24024.*pow(SStepFra,vec2(10)) + 20020.*pow(SStepFra,vec2(9)) - 9009.*pow(SStepFra,vec2(8)) + 1716.*pow(SStepFra,vec2(7))) + SStepInt - 0.5) * SourceSize.zw;

        vec3 Picture = texture(Source,SStep).xyz;
   
    float Lum = ((0.299*Picture.x) + (0.587*Picture.y) + (0.114*Picture.z));
          Lum = 1-Lum;
          Lum = Lum * 0.5;
   
    vec3 PictureBlur = pow(texture(Source,SStep+SourceSize.zw*vec2( Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2(-Lum, Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2( Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur += pow(texture(Source,SStep+SourceSize.zw*vec2(-Lum,-Lum)).xyz, vec3(2.2));
        PictureBlur *= 0.25;
        float grid;
 
    float lines;
    lines = sin(angle);
	lines *= AMPLITUDE;
    lines = clamp(lines, 0.0, 1.0);
    lines *= LINES_WHITE - LINES_BLACK;
    lines += LINES_BLACK;

	PictureBlur *= lines;
    
       float mask = 1.0 - DOTMASK_STRENGTH;

   //cgwg's dotmask emulation:
   //Output pixels are alternately tinted green and magenta
   vec3 dotMaskWeights = mix(vec3(1.0, mask, 1.0),
                             vec3(mask, 1.0, mask),
                             floor(mod(mod_factor, 2.0)));
   if (shadowMask > 0.5) 
   {
      PictureBlur *= Mask(floor(1.000001 * gl_FragCoord.xy + vec2(0.5,0.5)));
      FragColor = vec4(pow(PictureBlur, vec3(1.0/2.2)),1.0);

      return;
   }
   else if (shadowMask == 0.)
   {
      PictureBlur = pow(PictureBlur, vec3(1.0/2.2));
      PictureBlur *= dotMaskWeights;
      FragColor = vec4(PictureBlur,1.0);
      return;
   }
   else 
   {
      PictureBlur = pow(PictureBlur, vec3(1.0/2.2));
      PictureBlur = pow(PictureBlur, vec3(1.0/2.2)); //dunno why this needed double delinearization but whatever
      PictureBlur *= SinPhosphor(PictureBlur);
      FragColor = vec4(PictureBlur,1.0);
   }
} 
#endif

One thing that might be good to know about is that the PHASE parameter can play an important part to the look and it depends on the scale. At 1x and 2x “0.5” looks the best to me, at 3x and 4x “1.5”, at 5x maybe “2.0” but your milleage may vary. What it does is offsetting the sine pattern just in case it needs alignment due to maybe texCoords inaccuracies, but it can be used to get either the “dynamic” or the “flat” look. You guys try and see.

2 Likes

Ah, yeah, I think 2.0 aligns with the pixels better at 5x.