Stylized VHS & Posterization Post-Processing Shader

Shader code
shader_type canvas_item;

uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

// --- UPDATED AESTHETIC PARAMETERS ---
uniform float color_levels : hint_range(2.0, 128.0) = 14.0;
uniform float chromatic_aberration : hint_range(0.0, 0.2) = 0.008;
uniform float blur_amount : hint_range(0.0, 5.0) = 1.0;
uniform float brightness : hint_range(0.5, 3.0) = 1.2;
uniform float saturation : hint_range(1.0, 5.0) = 1.0;

// --- UPDATED GLITCH PARAMETERS ---
uniform float color_offset_multiplier: hint_range(0.0, 0.2) = 0.004;
uniform float black_offset_multiplier: hint_range(0.0, 0.5) = 0.1;
uniform float color_offset: hint_range(-0.1, 0.1) = 0.005;

// Noise function
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() {
	vec2 uv = SCREEN_UV;
	
	// 1. MOVEMENT LOGIC
	float line_logic = uv.y * 487.0;
	vec2 noise_coord = vec2(floor(line_logic), fract(line_logic));
	
	float x_offset = color_offset + noise(noise_coord, TIME) * color_offset_multiplier;
	float black_offset = noise(noise_coord, TIME + 65.0) * (black_offset_multiplier * 0.01);

	// 2. SAMPLING
	vec3 col;
	col.r = texture(SCREEN_TEXTURE, uv + vec2(chromatic_aberration + black_offset, 0.0)).r;
	col.g = texture(SCREEN_TEXTURE, uv + vec2(black_offset, 0.0)).g;
	col.b = texture(SCREEN_TEXTURE, uv - vec2(chromatic_aberration - black_offset, 0.0)).b;

	// 3. YUV CHROMATIC SHIFT
	vec4 color_shifted = textureLod(SCREEN_TEXTURE, uv + vec2(x_offset, 0.0), blur_amount * 0.25);
	vec3 yuv_base = rgb_to_yuv(col);
	vec3 yuv_chroma = rgb_to_yuv(color_shifted.rgb);
	
	col = yuv_to_rgb(vec3(yuv_base.x, yuv_chroma.yz));

	// 4. POST-PROCESSING EFFECTS
	float luma = dot(col, vec3(0.299, 0.587, 0.114));
	col = mix(vec3(luma), col, saturation);
	col *= brightness;
	
	// Posterization
	col.rgb = floor(col.rgb * color_levels + 0.3) / color_levels;

	// --- VIGNETTE REMOVED ---

	// Final pushed contrast
	col = clamp(col, 0.0, 1.0);
	col = mix(col, col * col * (3.0 - 2.0 * col), 0.6);

	COLOR = vec4(col, 1.0);
}
Live Preview
Tags
horror, VHS
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

guest

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
H1d3o
19 days ago

DOOOOPE ! Thank you <3. We will use it as our in game shader, that was excatly what I was searching for!
Check us for some ideas of the rendering 😉

https://store.steampowered.com/app/3756600/Dungeon_Cleaners/
(demo in january 2026)