RP | Resource | Post-Process VHS Effect

Converted this ShaderToy shader to work in legacy UPBGE, RanGE, or UPBGE 0.3+. Enjoy. :slightly_smiling_face:

Legacy UPBGE / RanGE
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;

uniform float Timer; //you need an Timer property in the logic with the name after uniform float (you can change the name) 
float iTime = Timer;

vec2 iResolution = vec2(bgl_RenderedTextureWidth, bgl_RenderedTextureHeight);
vec2 fragCoord = vec2(gl_TexCoord[0].x * bgl_RenderedTextureWidth, gl_TexCoord[0].y * bgl_RenderedTextureHeight);

float noise(vec2 p) {
  float s = texture(bgl_RenderedTexture,vec2(1.,2.*cos(iTime))*iTime*8. + p*1.).x;
  s *= s;
  return s;
}

float onOff(float a, float b, float c) {
  return step(c, sin(iTime + a*cos(iTime*b)));
}

float ramp(float y, float start, float end) {
  float inside = step(start,y) - step(end,y);
  float fact = (y-start)/(end-start)*inside;
  return (1.-fact) * inside;
}

float stripes(vec2 uv) {
  float noi = noise(uv*vec2(0.5,1.) + vec2(1.,3.));
  return ramp(mod(uv.y*4. + iTime/2.+sin(iTime + sin(iTime*0.63)),1.),0.5,0.6)*noi;
}

vec3 getVideo(vec2 uv) {
  vec2 look = uv;
  float window = 1./(1.+20.*(look.y-mod(iTime/4.,1.))*(look.y-mod(iTime/4.,1.)));
  look.x = look.x + sin(look.y*10. + iTime)/50.*onOff(4.,4.,.3)*(1.+cos(iTime*80.))*window;
  float vShift = 0.4*onOff(2.,3.,.9)*(sin(iTime)*sin(iTime*20.) + 
                     (0.5 + 0.1*sin(iTime*200.)*cos(iTime)));
  look.y = mod(look.y + vShift, 1.);
  vec3 video = vec3(texture(bgl_RenderedTexture,look));
  return video;
}

vec2 screenDistort(vec2 uv) {
  uv -= vec2(.5,.5);
  uv = uv*1.2*(1./1.2+2.*uv.x*uv.x*uv.y*uv.y);
  uv += vec2(.5,.5);
  return uv;
}

void main(void) {
  vec2 uv = fragCoord.xy / iResolution.xy;
  uv = screenDistort(uv);
  vec3 video = getVideo(uv);
  float vigAmt = 3.+.3*sin(iTime + 5.*cos(iTime*5.));
  float vignette = (1.-vigAmt*(uv.y-.5)*(uv.y-.5))*(1.-vigAmt*(uv.x-.5)*(uv.x-.5));
  
  video += stripes(uv);
  video += noise(uv*2.)/2.;
  video *= vignette;
  video *= (12.+mod(uv.y*30.+iTime,1.))/13.;
  
  gl_FragColor = vec4(video,1.0);
}
UPBGE 0.3+
uniform sampler2D bgl_RenderedTexture;
uniform float bgl_RenderedTextureWidth;
uniform float bgl_RenderedTextureHeight;

uniform float Timer; //you need an Timer property in the logic with the name after uniform float (you can change the name) 
float iTime = Timer;

in vec4 bgl_TexCoord;
out vec4 fragColor;

vec2 iResolution = vec2(bgl_RenderedTextureWidth, bgl_RenderedTextureHeight);
vec2 fragCoord = vec2(bgl_TexCoord.x * bgl_RenderedTextureWidth, bgl_TexCoord.y * bgl_RenderedTextureHeight);

float noise(vec2 p) {
    float s = texture(bgl_RenderedTexture,vec2(1.,2.*cos(iTime))*iTime*8. + p*1.).x;
    s *= s;
    return s;
}

float onOff(float a, float b, float c) {
    return step(c, sin(iTime + a*cos(iTime*b)));
}

float ramp(float y, float start, float end) {
    float inside = step(start,y) - step(end,y);
    float fact = (y-start)/(end-start)*inside;
    return (1.-fact) * inside;
}

float stripes(vec2 uv) {
    float noi = noise(uv*vec2(0.5,1.) + vec2(1.,3.));
    return ramp(mod(uv.y*4. + iTime/2.+sin(iTime + sin(iTime*0.63)),1.),0.5,0.6)*noi;
}

vec3 getVideo(vec2 uv) {
    vec2 look = uv;
    float window = 1./(1.+20.*(look.y-mod(iTime/4.,1.))*(look.y-mod(iTime/4.,1.)));
    look.x = look.x + sin(look.y*10. + iTime)/50.*onOff(4.,4.,.3)*(1.+cos(iTime*80.))*window;
    float vShift = 0.4*onOff(2.,3.,.9)*(sin(iTime)*sin(iTime*20.) + 
                                         (0.5 + 0.1*sin(iTime*200.)*cos(iTime)));
    look.y = mod(look.y + vShift, 1.);
    vec3 video = vec3(texture(bgl_RenderedTexture,look));
    return video;
}

vec2 screenDistort(vec2 uv) {
    uv -= vec2(.5,.5);
    uv = uv*1.2*(1./1.2+2.*uv.x*uv.x*uv.y*uv.y);
    uv += vec2(.5,.5);
    return uv;
}

void main(void) {
    vec2 uv = fragCoord.xy / iResolution.xy;
    uv = screenDistort(uv);
    vec3 video = getVideo(uv);
    float vigAmt = 3.+.3*sin(iTime + 5.*cos(iTime*5.));
    float vignette = (1.-vigAmt*(uv.y-.5)*(uv.y-.5))*(1.-vigAmt*(uv.x-.5)*(uv.x-.5));
    
    video += stripes(uv);
    video += noise(uv*2.)/2.;
    video *= vignette;
    video *= (12.+mod(uv.y*30.+iTime,1.))/13.;
    
    fragColor = vec4(video,1.0);
}

Download

UPBGE_02_VHS_Shader.blend (93.5 KB)

UPBGE_03_VHS_Shader.blend (819.1 KB)

Credits

Original ShaderToy Script: https://www.shadertoy.com/view/ldjGzV
Thanks to @BluePrintRandom and this helpful thread for helping me learn GLS better: Porting glsl shader to blender? - #8 by TheLumcoin

5 Likes

Hi! I know it’s old topic but how do I port it to newer UPBGE version (0.3)? You use Timer property to animate filter but I tried to run that in 0.3. Guess what happened. Is there equivalent for u_time in GLSL in 0.3?

Hi. I’ve updated my original post with a UPBGE 0.3+ shader + blend. :+1:

Thanks! It really helped!