Smooth anti-aliased sprite outline

A clean border around the sprite silhouette, anti-aliased at any zoom, with an optional soft falloff.

How to use: put the shader on a Sprite2D (or any CanvasItem). Give the texture a few pixels of transparent padding so the outline has room.

Settings:

  • outline_color, outline_width (0-8, in texture pixels).
  • softness (0-1): 0 gives a hard edge, higher a gentler falloff.

Works with the Compatibility renderer and respects the node modulate.

This shader is part of the free 2D FX Essentials pack (9 sprite shaders with a demo scene): https://dimensionalmios.itch.io/almios-2d-fx-essentials

Shader code
shader_type canvas_item;

// Smooth sprite outline for any Sprite2D or TextureRect. The texture needs a little
// transparent padding for the outline to fit. A full ring of directions (not just
// 8) keeps the outline round and anti-aliased instead of blocky.
group_uniforms Outline;
uniform vec4 outline_color : source_color = vec4(1.0, 1.0, 1.0, 1.0);
uniform float outline_width : hint_range(0.0, 8.0) = 2.0;
uniform float softness : hint_range(0.0, 1.0) = 0.55;

// Past the texture border counts as empty, so on a sprite without padding the
// ring never wraps the opposite edge back into the outline.
float samp_a(sampler2D tex, vec2 uv) {
	if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) { return 0.0; }
	return texture(tex, uv).a;
}

varying vec4 vertex_tint;   // modulate and self_modulate, applied at the end

void vertex() {
	vertex_tint = COLOR;
}

void fragment() {
	vec4 col = texture(TEXTURE, UV);
	vec2 ps = TEXTURE_PIXEL_SIZE * outline_width;
	// coverage over 16 directions at two radii: a round dilation that keeps the
	// sprite's own anti-aliasing. The direction turns by a fixed step (TAU / 16)
	// instead of a sine and cosine per sample.
	const int N = 16;
	const vec2 STEP = vec2(0.92387953, 0.38268343);   // cos and sin of TAU / 16
	vec2 dir = vec2(1.0, 0.0);
	float acc = 0.0;
	for (int i = 0; i < N; i++) {
		acc += samp_a(TEXTURE, UV + dir * ps);
		acc += samp_a(TEXTURE, UV + dir * ps * 0.6);
		dir = vec2(dir.x * STEP.x - dir.y * STEP.y, dir.x * STEP.y + dir.y * STEP.x);
	}
	acc /= float(N * 2);
	float edge = smoothstep(0.0, clamp(softness, 0.02, 1.0), acc);
	float outline = edge * (1.0 - col.a);
	vec3 rgb = mix(outline_color.rgb, col.rgb, col.a);
	COLOR = vec4(rgb, max(col.a, outline * outline_color.a));
	COLOR *= vertex_tint;
}
Live Preview
Tags
2d, border, outline, selection, sprite
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from DimensionAlmios

Related shaders

guest

0 Comments
Oldest
Newest Most Voted