0 votes

Is it possible to create margins around a texture to ensure a sprite always has enough room for an effect like an outline?

I am currently using the outline shader by GDQuest but my assets don't always have the room to display the outline.

shader_type canvas_item;

uniform vec4 line_color : hint_color = vec4(1.0);
uniform float line_thickness : hint_range(0, 10) = 1.0;

const vec2 OFFSETS[8] = {
    vec2(-1, -1), vec2(-1, 0), vec2(-1, 1), vec2(0, -1), vec2(0, 1), 
    vec2(1, -1), vec2(1, 0), vec2(1, 1)
};

void fragment() {
    vec2 size = TEXTURE_PIXEL_SIZE * line_thickness;
    float outline = 0.0;

    for (int i = 0; i < OFFSETS.length(); i++) {
        outline += texture(TEXTURE, UV + size * OFFSETS[i]).a;
    }
    outline = min(outline, 1.0);

    vec4 color = texture(TEXTURE, UV);
    COLOR = mix(color, line_color, outline - color.a);
}
in Engine by (37 points)
edited by

1 Answer

+1 vote
Best answer

Via Mathichai Dechdee on our Facebook group I received this excellent answer with explanations:

shader_type canvas_item;

uniform vec4 line_color: hint_color = vec4(1.0);
uniform float width: hint_range(0,10)  = 2.0;

const vec2 OFFSETS[8] = {
    vec2(-1,-1), vec2(-1,0), vec2(-1,1), vec2(0,-1),
    vec2(0,1), vec2(1,-1), vec2(1,0), vec2(1,1)
};

void vertex(){
    // start with adding margin to the original sprite
    // this will scale up the sprite, will scale down later in fragment()
    VERTEX += (UV * 2.0 - 1.0) * width ;
}

void fragment(){
    // note that TEXTURE_PIXEL_SIZE is actually 1.0/vec2(WIDTH_OF_TEXTURE, HEIGHT_OF_TEXTURE)
    // so 1.0 / TEXTURE_PIXEL_SIZE is vec2(WIDTH, HEIGHT)
    vec2 real_texture_size = 1.0 / TEXTURE_PIXEL_SIZE;

    // This is texture size when add margin equal to 
    // width of the outline*2 (left and right / top and down)
    vec2 added_margin_texture_pixel_size = 1.0 / (real_texture_size + (width*2.0));

    // width in range (0,1), respected to the new texture size
    vec2 width_size = added_margin_texture_pixel_size * width;
    // shift the original uv bottom-right for 'width' unit 
    // Calculate how much bigger is the new size compared to the old one
    vec2 shifted_uv = UV - width_size;
    // Then scale the uv down to that ratio
    vec2 ratio = TEXTURE_PIXEL_SIZE / added_margin_texture_pixel_size;
    vec2 scaled_shifted_uv = shifted_uv * ratio;

    // sample the original texture with new uv to scale it down
    // to the original size
    vec4 color;
    color = texture(TEXTURE, scaled_shifted_uv);
    // This if is to remove artifacts outside the boundary of sprites
    if (scaled_shifted_uv != clamp(scaled_shifted_uv, vec2(0.0), vec2(1.0))) {
        color.a = 0.0;
    }

    float outline = 0.0;
    for (int i=0; i<OFFSETS.length(); i++){
        outline += texture(TEXTURE, scaled_shifted_uv + OFFSETS[i]*width_size).a;
    }
    outline = min(outline, 1.0);

    COLOR = mix(color, line_color, outline-color.a);
}
by (37 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read Frequently asked questions and How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to [email protected] with your username.