Quick answer: Convert FRAGCOORD to UV via division by VIEWPORT_SIZE. Use SCREEN_PIXEL_SIZE for pixel-grain offsets.

Custom blur shader sampling FRAGCOORD shows seams between adjacent pixels. Half-pixel offset misaligns sampling.

The Fix

// Convert to UV correctly
vec2 uv = FRAGCOORD.xy / VIEWPORT_SIZE;

// Pixel-precise neighbor sample
vec2 px = SCREEN_PIXEL_SIZE;
vec3 n = (
    texture(SCREEN_TEXTURE, uv + vec2( px.x, 0.0)).rgb +
    texture(SCREEN_TEXTURE, uv + vec2(-px.x, 0.0)).rgb +
    texture(SCREEN_TEXTURE, uv + vec2(0.0,  px.y)).rgb +
    texture(SCREEN_TEXTURE, uv + vec2(0.0, -px.y)).rgb
) * 0.25;

VIEWPORT_SIZE division produces 0..1 UV. SCREEN_PIXEL_SIZE provides pixel-step offsets. No half-pixel surprises.

Verifying

Seams gone. 4-tap blur produces smooth output. Sample at FRAGCOORD without conversion: visible grid pattern.

“Divide by VIEWPORT. Pixel size for steps.”

Related Issues

For SCREEN_TEXTURE banding, see SCREEN_TEXTURE. For shader UV flip, see UV flip.

Convert. Pixel size. Smooth.