VHS / Scanline Color Fuzz

This is an essential part of the look of a VHS. You can make this look more like a real NTSC signal by sampling the blurred texture again with an inverted offset into a variable (in this example, yuv3), then replacing the last line with the following:

COLOR.rgb = yuv_to_rgb(vec3(yuv1.x, yuv2.y, yuv3.z));

I did not include this in this version of the shader for performance reasons, as texture samples are very slow. There’s probably a good way to optimize this to make it viable, but I’m not aware of how, as I don’t have much experience making shaders.

 

To use this, create a canvas layer with a colorect set to fill the entire viewport, like any post processing filter.

Also, I highly recommend keeping the settings turned very low, as it can get very ugly very fast. The preview images are set to more extreme values than I think are honestly reasonable for a real game. Especially keep the black offset multiplier low, as this one can quickly make your game incomprehensible if set too high (also, it’s just unrealistic to have it be set high at all).

Shader code
shader_type canvas_item;

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest_mipmap;

group_uniforms scanline_wiggle;
uniform float color_offset_multiplier: hint_range(0.0, 0.1) =  0.004;
uniform float black_offset_multiplier: hint_range(0.0, 0.1) = 0.1;
uniform float color_offset: hint_range(-0.1, 0.1) = 0.005;

group_uniforms blur;
uniform float blur_amount: hint_range(0.0, 3.0, 0.1);

uniform sampler2D colorLookup;


float noise(vec2 uv, float offset) {
	return (fract(sin(dot(uv * offset, vec2(12.9898, 78.233))) * 43758.5453) - 0.5) * 2.0;
}

vec3 rgb_to_yuv(vec3 rgb) {
	const mat3 RGB_TO_YUV = mat3(
		vec3(0.299, 0.587, 0.114),
		vec3(-0.14713, -0.28886, 0.436),
		vec3(.615, -0.51499, -0.10001)
		);
	return rgb * RGB_TO_YUV;
}

vec3 yuv_to_rgb(vec3 yuv) {
	const mat3 YUV_TO_RGB = mat3(
		vec3(1.0, 0.0, 1.13983),
		vec3(1.0, -0.39465, -0.58060),
		vec3(1.0, 2.03211, 0.0)
		);
	return clamp(yuv * YUV_TO_RGB, vec3(0.0), vec3(1.0));
}

void fragment() {

		float x_offset = color_offset + noise(vec2(floor(UV.y * 487.0), fract(UV.y * 487.0)), TIME) * color_offset_multiplier;
		float black_offset = noise(vec2(floor(UV.y * 487.0), fract(UV.y * 487.0)), TIME + 69.420) * (black_offset_multiplier * 0.01);

		COLOR = textureLod(screen_texture, SCREEN_UV + vec2(black_offset,0.0), 0.0);

		vec4 color2 = textureLod(screen_texture, SCREEN_UV + vec2(x_offset,0.0), blur_amount);
		vec3 yuv1 = rgb_to_yuv(COLOR.rgb);
		vec3 yuv2 = rgb_to_yuv(color2.rgb);

		COLOR.rgb = yuv_to_rgb(vec3(yuv1.x, yuv2.yz));
}
Live Preview
Tags
analog, fuzz, NTSC, PAL, tape, vcr, VHS, vintage
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

4.3-scanline

color splash (show only one color)

VHS

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments