Skip to content

Instantly share code, notes, and snippets.

@bitbrain
Last active November 26, 2025 16:36
Show Gist options
  • Select an option

  • Save bitbrain/103fb35b1fa171c1dd4420a970bc3f37 to your computer and use it in GitHub Desktop.

Select an option

Save bitbrain/103fb35b1fa171c1dd4420a970bc3f37 to your computer and use it in GitHub Desktop.
On-screen debanding post-processing shader for Godot 4.5
shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture;
// How flat before dithering kicks in (try 0.006–0.02)
uniform float gradient_threshold = 0.012;
// Very small (try 0.002–0.004)
uniform float dither_strength = 0.003;
float hash21(vec2 p) {
vec3 p3 = fract(vec3(p.xyx) * 0.1031);
p3 += dot(p3, p3.yzx + 33.33);
return fract((p3.x + p3.y) * p3.z);
}
void fragment() {
vec2 uv = SCREEN_UV;
vec3 col = texture(screen_texture, uv).rgb;
// 1-pixel offsets via built-in
vec2 px = SCREEN_PIXEL_SIZE;
// Sample neighbors (4-tap for a sturdier flatness test)
vec3 c = col;
vec3 r = texture(screen_texture, uv + vec2(+px.x, 0.0)).rgb;
vec3 l = texture(screen_texture, uv + vec2(-px.x, 0.0)).rgb;
vec3 u = texture(screen_texture, uv + vec2(0.0, +px.y)).rgb;
vec3 d = texture(screen_texture, uv + vec2(0.0, -px.y)).rgb;
// Gradient magnitude (sum of differences). Small ⇒ likely banding.
float grad = length(c - r) + length(c - l) + length(c - u) + length(c - d);
// Weight so we only dither in flat areas (smoothstep avoids hard cutoff)
float w = 1.0 - smoothstep(0.0, gradient_threshold, grad);
// Zero-mean procedural noise in [-0.5, +0.5]
float n = hash21(FRAGCOORD.xy + vec2(TIME * 23.17, TIME * 47.23)) - 0.5;
// Apply very small offset, only where flat
col += n * dither_strength * w;
COLOR = vec4(col, 1.0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment