All in one outline shader
An outline shader with everyting you should need for almost all situations for canvas items. this shader is a mix of ideas and snippets from other shaders, the base being GD Quest’s library of free shaders and the outline clipping fix from fupi’s Outline that disrespects boundaries
Note: to avoid alot of bugs, the offset uniform does not work well outside the range of the outline thickness, so if you have an outline thickness of 2, the offset only works well within -2 to 2
Features
- Customizable sample counts
- Offset
- Outline Clipping fix (keeps the outline from clipping out into the border by scaling the border itself farther)
- Texture bleeding fix (fixes the strips that appear when outline shaders sample outside the texture boundaries)
Features that differentiate this from other existing outline shaders:
- Square border option
- Aspect ratio control
- Max or Add
– two methods for the opacity of the outline
– Max uses the most opaque as the opacity, good for fully opaque outlines while keeping the outline from aliasing if too many samples is given.
– Add stacks the opacity on top of each other, good for blurry outlines with a very low opacity, as seen in the preset examples image. but shows alot of aliasing if too many samples is used.
Shader code
shader_type canvas_item;
render_mode unshaded;
uniform float thickness : hint_range(0.0, 100.0);
uniform int ring_count : hint_range(1,128) = 16;
uniform float ring_offset : hint_range(0.0, 1.0, 0.01);
uniform vec4 outline_color : source_color;
uniform bool border_clipping_fix = true;
uniform float aspect_ratio : hint_range(0.1, 10.0, 0.01) = 1.0;
uniform bool square_border = false;
uniform vec2 offset;
uniform bool max_or_add = false;
void vertex(){
if (border_clipping_fix){
vec2 o = (UV * 2.0 - 1.0);
VERTEX += o * thickness - offset;
VERTEX.x *= 1.0 + (aspect_ratio-1.0) * (thickness * TEXTURE_PIXEL_SIZE.x) * 2.0;
}
}
vec2 square(float i){ // samples a square pattern
i *= 2.0;
return (vec2(
clamp(abs(mod(i,2.0)-1.0),0.25,0.75),
clamp(abs(mod(i+0.5,2.0)-1.0),0.25,0.75)
)-0.5)*4.0;
}
vec4 tex(sampler2D sampler, vec2 uv){
vec4 clr;
if (uv.x > 0.0 && uv.y > 0.0 && uv.x < 1.0 && uv.y < 1.0){ // bleeding texture sampling fix
clr = texture(sampler, uv);
}else{clr = vec4(0.0);}
return clr;
}
void fragment(){
vec2 o = offset / vec2(textureSize(TEXTURE, 0));
vec2 uv = UV;
uv -= vec2(0.5);
if (border_clipping_fix){
uv.x *= 1.0 + (aspect_ratio-1.0) * thickness * TEXTURE_PIXEL_SIZE.x * 2.0;
uv *= (1.0 + (thickness * TEXTURE_PIXEL_SIZE * 2.0));
uv -= o;
}
uv += vec2(0.5);
vec2 size = vec2(thickness) / vec2(textureSize(TEXTURE, 0));
vec4 sprite_color = tex(TEXTURE, uv);
float alpha = sprite_color.a;
if (square_border){
for(int i=0;i<ring_count;++i){
float r = float(i) / float(ring_count) + ring_offset;
alpha = max(alpha,texture(TEXTURE, uv + o + size * square(r) * vec2(aspect_ratio,1.0)).a * outline_color.a);}// texture sampling fix is disabled because both with and without give the same result
}else{
for(int i=0;i<ring_count;++i){
float r = float(i) * 3.14159 / float(ring_count) * 2.0 + ring_offset;
if (max_or_add){
alpha = alpha+tex(TEXTURE, uv + o + vec2(size.x * sin(r) * aspect_ratio, size.y * cos(r))).a * outline_color.a;
}else{
alpha = max(alpha,tex(TEXTURE, uv + o + vec2(size.x * sin(r) * aspect_ratio, size.y * cos(r))).a * outline_color.a);
}
}
}
vec3 final_color = mix(outline_color.rgb, sprite_color.rgb, sprite_color.a);
COLOR = vec4(final_color, clamp(alpha, 0.0, 1.0));
}





This shader bybass modulate value and I don’t know how to fix, sad
remove it:
render_mode unshaded;
Oh my god this is literally the perfect outline shader i needed. Thanks for making this.
For some reason, this shader (and other similar ones) that use the border clipping method has a bug that cuts off the bottom row of pixel in my texture…
See example here:
Tried Godot 3.5/3.6, same issue on both, is it related to it being part of a sprite sheet?