diff --git a/vhs/resources/play.png b/vhs/resources/play.png index 3c95ecc..b28bcec 100644 Binary files a/vhs/resources/play.png and b/vhs/resources/play.png differ diff --git a/vhs/resources/rew.png b/vhs/resources/rew.png new file mode 100644 index 0000000..63b24a9 Binary files /dev/null and b/vhs/resources/rew.png differ diff --git a/vhs/shaders/rewind.slang b/vhs/shaders/rewind.slang new file mode 100644 index 0000000..8a53861 --- /dev/null +++ b/vhs/shaders/rewind.slang @@ -0,0 +1,95 @@ +#version 450 + +// VHS Rewind Effect +// adapter from VHS Pause Effect shadertoy by caaaaaaarter +// https://www.shadertoy.com/view/4lB3Dc + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; + int FrameDirection; +} params; + +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; +layout(set = 0, binding = 3) uniform sampler2D rew; + +#define iTime float(params.FrameCount) + +float rand(vec2 co) +{ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +float onOff(float a, float b, float c, float framecount) +{ + return step(c, sin((framecount * 0.001) + a*cos((framecount * 0.001)*b))); +} + +vec2 jumpy(vec2 uv, float framecount) +{ + vec2 look = uv; + float window = 1./(1.+80.*(look.y-mod(framecount/4.,1.))*(look.y-mod(framecount/4.,1.))); + look.x += 0.05 * sin(look.y*10. + framecount)/20.*onOff(4.,4.,.3, framecount)*(0.5+cos(framecount*20.))*window; + float vShift = 0.4*onOff(2.,3.,.9, framecount)*(sin(framecount)*sin(framecount*20.) + + (0.5 + 0.1*sin(framecount*200.)*cos(framecount))); + look.y = mod(look.y - 0.01 * vShift, 1.); + return look; +} + +void main() +{ + vec4 texColor = texture(Source, vTexCoord); + if (float(params.FrameDirection) < 0.0) + { + vec2 uv = vTexCoord.xy; + uv.x -= sin(0.0006 * mod(iTime, 11.0)) * cos(mod(iTime, 17.0) * -uv.y); + texColor = texture(Source, jumpy(uv, iTime)); + vec4 rew_osd = texture(rew, jumpy(vTexCoord, iTime)); + rew_osd.a = ((mod(iTime, 100.0) < 50.0)) ? rew_osd.a : 0.0; + texColor = mix(texColor, rew_osd, rew_osd.a); + // get position to sample + vec2 samplePosition = uv.xy - vec2(0.0, 0.45); + float whiteNoise; + + // Jitter each line left and right + samplePosition.x += (rand(vec2(iTime,vTexCoord.y))+0.5); + // Jitter the whole picture up and down + samplePosition.y = samplePosition.y+(rand(vec2(iTime))-0.5)/32.0; + // Slightly add color noise to each line + texColor = texColor + (vec4(-0.5)+vec4(rand(vec2(vTexCoord.y,iTime)),rand(vec2(vTexCoord.y,iTime+1.0)),rand(vec2(vTexCoord.y,iTime+2.0)),0))*0.1; + + // Either sample the texture, or just make the pixel white (to get the staticy-bit at the bottom) + whiteNoise = rand(vec2(floor(samplePosition.y*160.0),floor(samplePosition.x*cos(iTime)))+vec2(iTime,0.)); + if ((whiteNoise > 11.5-30.0*samplePosition.y || whiteNoise < 1.5-5.0*samplePosition.y) && + (whiteNoise > 11.5-30.0*(samplePosition.y + 0.5) || whiteNoise < 1.5-5.0*(samplePosition.y + 0.4))) { + // Sample the texture. + samplePosition.y = 1.0-samplePosition.y; //Fix for upside-down texture + } else { + // Use white. (I'm adding here so the color noise still applies) + texColor += vec4(0.5 + rand(samplePosition)); + } + } + FragColor = texColor; +} \ No newline at end of file diff --git a/vhs/shaders/vhs.slang b/vhs/shaders/vhs.slang index ae66eec..c5acde5 100644 --- a/vhs/shaders/vhs.slang +++ b/vhs/shaders/vhs.slang @@ -11,6 +11,7 @@ layout(push_constant) uniform Push vec4 OriginalSize; vec4 OutputSize; uint FrameCount; + int FrameDirection; float wiggle; float smear; } params; @@ -42,6 +43,7 @@ void main() layout(location = 0) in vec2 vTexCoord; layout(location = 0) out vec4 FragColor; layout(set = 0, binding = 2) uniform sampler2D Source; +layout(set = 0, binding = 3) uniform sampler2D play; #define iTime mod(float(FrameCount), 7.0) #define iChannel0 Source @@ -164,6 +166,12 @@ void main() final.xyz =Blur(uv,c); float q = rgb2yiq(final.xyz).b; final = vec4(yiq2rgb(vec3(y,i,q))-pow(s+e*2.0,3.0), 1.0); + + vec4 play_osd = texture(play, uv2); + float timer = (params.FrameDirection > 0.5) ? float(FrameCount) : 0.0; + float show_overlay = (mod(timer, 100.0) < 50.0) && (timer != 0.0) && (timer < 500.0) ? play_osd.a : 0.0; + show_overlay = clamp(show_overlay, 0.0, 1.0); + final = mix(final, play_osd, show_overlay); FragColor = final; } diff --git a/vhs/shaders/vhs_mpalko/vhs_mpalko.inc b/vhs/shaders/vhs_mpalko/vhs_mpalko.inc new file mode 100644 index 0000000..2ae31bd --- /dev/null +++ b/vhs/shaders/vhs_mpalko/vhs_mpalko.inc @@ -0,0 +1,78 @@ +#define lerp mix + +#define NTSC 0 +#define PAL 1 + +// Effect params +#define VIDEO_STANDARD PAL + +#if VIDEO_STANDARD == NTSC + const vec2 maxResLuminance = vec2(333.0, 480.0); + const vec2 maxResChroma = vec2(40.0, 480.0); +#elif VIDEO_STANDARD == PAL + const vec2 maxResLuminance = vec2(335.0, 576.0); + const vec2 maxResChroma = vec2(40.0, 240.0); +#endif + +const vec2 blurAmount = vec2(0.2, 0.2); + +// End effect params + +#define VIDEO_TEXTURE iChannel0 + + + +mat3 rgb2yiq = mat3(0.299, 0.596, 0.211, + 0.587, -0.274, -0.523, + 0.114, -0.322, 0.312); + +mat3 yiq2rgb = mat3(1, 1, 1, + 0.956, -0.272, -1.106, + 0.621, -0.647, 1.703); + +// from http://www.java-gaming.org/index.php?topic=35123.0 +vec4 cubic(float v) +{ + vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v; + vec4 s = n * n * n; + float x = s.x; + float y = s.y - 4.0 * s.x; + float z = s.z - 4.0 * s.y + 6.0 * s.x; + float w = 6.0 - x - y - z; + return vec4(x, y, z, w) * (1.0/6.0); +} + +vec4 textureBicubic(sampler2D tex, vec2 texCoords) +{ + + vec2 texSize = vec2(textureSize(tex, 0)); + vec2 invTexSize = vec2(1.0) / texSize; + + texCoords = texCoords * texSize - 0.5; + + + vec2 fxy = fract(texCoords); + texCoords -= fxy; + + vec4 xcubic = cubic(fxy.x); + vec4 ycubic = cubic(fxy.y); + + vec4 c = texCoords.xxyy + vec2 (-0.5, +1.5).xyxy; + + vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw); + vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s; + + offset *= invTexSize.xxyy; + + vec4 sample0 = texture(tex, offset.xz); + vec4 sample1 = texture(tex, offset.yz); + vec4 sample2 = texture(tex, offset.xw); + vec4 sample3 = texture(tex, offset.yw); + + float sx = s.x / (s.x + s.y); + float sy = s.z / (s.z + s.w); + + return mix( + mix(sample3, sample2, sx), mix(sample1, sample0, sx) + , sy); +} \ No newline at end of file diff --git a/vhs/shaders/vhs_mpalko/vhs_mpalko_pass0.slang b/vhs/shaders/vhs_mpalko/vhs_mpalko_pass0.slang new file mode 100644 index 0000000..87d02c0 --- /dev/null +++ b/vhs/shaders/vhs_mpalko/vhs_mpalko_pass0.slang @@ -0,0 +1,112 @@ +#version 450 + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#include "vhs_mpalko.inc" + +#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; + + +//////////// -- SET UP LUTS HERE -- //////////// + +#define iChannel0 Source + +//////////////// -- END PASSES AND LUTS -- //////////////// + +//////////// -- SET UP SHADERTOY VARIABLES -- //////////// + +float iGlobalTime = float(params.FrameCount)*0.025; +float iTime = float(params.FrameCount)*0.025; +vec2 iResolution = params.OutputSize.xy; +#define FragCoord (params.OutputSize.xy * vTexCoord.xy) +int iFrame = int(params.FrameCount); + +//////////// -- END SHADERTOY VARIABLES -- //////////// + +//////////// -- PASTE SHADERTOY HERE -- //////////// + +// Downsample buffer A and convert to YIQ color space + + +vec3 downsampleVideo(vec2 uv, vec2 pixelSize, ivec2 samples) +{ + //return texture(VIDEO_TEXTURE, uv).rgb * rgb2yiq; + + vec2 uvStart = uv - pixelSize / 2.0; + vec2 uvEnd = uv + pixelSize; + + vec3 result = vec3(0.0, 0.0, 0.0); + for (int i_u = 0; i_u < samples.x; i_u++) + { + float u = lerp(uvStart.x, uvEnd.x, float(i_u) / float(samples.x)); + + for (int i_v = 0; i_v < samples.y; i_v++) + { + float v = lerp(uvStart.y, uvEnd.y, float(i_v) / float(samples.y)); + + result += texture(VIDEO_TEXTURE, vec2(u, v)).rgb; + } + } + + return (result / float(samples.x * samples.y)) * rgb2yiq; +} + +vec3 downsampleVideo(vec2 fragCoord, vec2 downsampledRes) +{ + + if (fragCoord.x > downsampledRes.x || fragCoord.y > downsampledRes.y) + { + return vec3(0.0); + } + + vec2 uv = fragCoord / downsampledRes; + vec2 pixelSize = 1.0 / downsampledRes; + ivec2 samples = ivec2(8, 3); + + pixelSize *= 1.0 + blurAmount; // Slight box blur to avoid aliasing + + return downsampleVideo(uv, pixelSize, samples); +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) +{ + vec2 resLuminance = min(maxResLuminance, vec2(iResolution)); + vec2 resChroma = min(maxResChroma, vec2(iResolution)); + + float luminance = downsampleVideo(fragCoord, resLuminance).r; + vec2 chroma = downsampleVideo(fragCoord, resChroma).gb; + + fragColor = vec4(luminance, chroma, 1); +} + +//////////// -- END SHADERTOY -- //////////// + +void main() +{ + mainImage(FragColor, FragCoord.xy); +} \ No newline at end of file diff --git a/vhs/shaders/vhs_mpalko/vhs_mpalko_pass1.slang b/vhs/shaders/vhs_mpalko/vhs_mpalko_pass1.slang new file mode 100644 index 0000000..3469eb9 --- /dev/null +++ b/vhs/shaders/vhs_mpalko/vhs_mpalko_pass1.slang @@ -0,0 +1,78 @@ +#version 450 + +layout(push_constant) uniform Push +{ + vec4 SourceSize; + vec4 OriginalSize; + vec4 OutputSize; + uint FrameCount; +} params; + +layout(std140, set = 0, binding = 0) uniform UBO +{ + mat4 MVP; +} global; + +#include "vhs_mpalko.inc" + +#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; + +//////////// -- SET UP PASSES AND LUTS HERE -- //////////// + +#define iChannel1 Source + +//////////////// -- END PASSES AND LUTS -- //////////////// + +float iGlobalTime = float(params.FrameCount)*0.025; +float iTime = float(params.FrameCount)*0.025; +vec2 iResolution = params.OutputSize.xy; +#define FragCoord (params.OutputSize.xy * vTexCoord.xy) + +//////////// -- PASTE SHADERTOY HERE -- //////////// + +vec2 rotate(vec2 v, float a) +{ + float s = sin(a); + float c = cos(a); + mat2 m = mat2(c, -s, s, c); + return m * v; +} + +void mainImage( out vec4 fragColor, in vec2 fragCoord ) +{ + vec2 uv = fragCoord / iResolution.xy; + + vec2 resLuminance = min(maxResLuminance, vec2(iResolution)); + vec2 resChroma = min(maxResChroma, vec2(iResolution)); + + vec2 uvLuminance = uv * (resLuminance / vec2(iResolution)); + vec2 uvChroma = uv * (resChroma / vec2(iResolution)); + + vec3 result; + float luminance = textureBicubic(iChannel1, uvLuminance).x; + vec2 chroma = textureBicubic(iChannel1, uvChroma).yz; + result = vec3(luminance, chroma) * yiq2rgb; + + fragColor = vec4(result, 1); +} + +//////////// -- END SHADERTOY -- //////////// + +void main() +{ + mainImage(FragColor, FragCoord.xy); +} \ No newline at end of file diff --git a/vhs/vhs.slangp b/vhs/vhs.slangp index 9003756..0f86a6a 100644 --- a/vhs/vhs.slangp +++ b/vhs/vhs.slangp @@ -1,4 +1,11 @@ -shaders = 1 +shaders = 2 shader0 = shaders/vhs.slang -filter_linear0 = true \ No newline at end of file +filter_linear0 = true + +shader1 = shaders/rewind.slang +filter_linear1 = true + +textures = "rew;play" +play = resources/play.png +rew = resources/rew.png \ No newline at end of file diff --git a/vhs/vhs_mpalko.slangp b/vhs/vhs_mpalko.slangp new file mode 100644 index 0000000..f645eb1 --- /dev/null +++ b/vhs/vhs_mpalko.slangp @@ -0,0 +1,11 @@ +shaders = 2 + +shader0 = shaders/vhs_mpalko/vhs_mpalko_pass0.slang +alias0 = Pass1 +float_framebuffer0 = true +filter_linear0 = true +scale_type0 = source +scale0 = 4.0 + +shader1 = shaders/vhs_mpalko/vhs_mpalko_pass1.slang +filter_linear1 = true \ No newline at end of file