Efficient 2D Pixel Outlines

2D pixel outline shader without “if” statements, since branching code is wildly inefficient on GPUs (as I understand it)

Adjustable thickness, supports transparent outline colors

Use the “type” uniform to switch between outline types — 1 for 4-way/”round”, 2 for 8-way/”square” (default), 0 to disable

update, may 25 2025: fixed the mix() factor reaching negative values and counter-blending, expanded “type” to include 0 for easy toggling at runtime

update, jun 13 2025: simplified mix() factor math

https://ko-fi.com/laffcat

Shader code
shader_type canvas_item;

uniform vec4 clr : source_color = vec4(0.0, 0.0, 0.0, 1.0);
uniform int type : hint_range(0, 2) = 2;
uniform float thickness = 1.0;

const vec2[8] DIRECTIONS = {
	vec2(1.0, 0.0),
	vec2(0.0, 1.0),
	vec2(-1.0, 0.0),
	vec2(0.0, -1.0),
	vec2(1.0, 1.0),
	vec2(-1.0, 1.0),
	vec2(-1.0, -1.0),
	vec2(1.0, -1.0)
};

float gtz(float input) { return max(0, sign(input)); }
// returns 1 if input > 0, else 0

float check(sampler2D tex, vec2 from, vec2 size) {
	float result = 0.0;
	for (int i = 0; i < 4 * type; i++) {
		result += texture(tex, from + DIRECTIONS[i] * size * thickness).a;
	}
	return gtz(result);
}

void fragment() {
	COLOR = mix( COLOR, clr, check(TEXTURE, UV, TEXTURE_PIXEL_SIZE) * (1.0 - gtz(COLOR.a)) );
}
Tags
2d, godot 4, optimized, outline, pixel-art, retro
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.

More from laffcat

3-Line Color Array Palettes, with Shifting

3-line Colormap Palletes

Related shaders

Pixel perfect outlines with full control

Animated and Gradient Outlines

Various Canvas Outlines

guest

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Everest
4 months ago

i made an account just to thank you for making this. it solves the issue every similar shader has had for years. the one where pixels without cardinal neighbors would be discolored. it had been following me between engines. it seems my soul can finally rest knowing there exists a solution.

David
David
3 months ago

Thanks for this. I think it’s better than the solution I had here, but I have a question, how would one solve the issue where the the outline would have to be drawn “outside” the UV, for example a case where the image goes until the edge? Would I always have to leave margins on the UVs for that? or something else I’m missing?