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;