• Hey! Guest! The 39th GMC Jam will take place between November 26th, 12:00 UTC and November 30th, 12:00 UTC. Why not join in! Click here to find out more!

Android Outline Stroke Shader For Overlapping Sprites

JayR

Member
I have overlapping sprites consisting of Head, 4 Limbs and a Torso that makes up a character. I would like to create a border around this character as a whole and not on its individual sprites as it is overlapping which looks funky.

I used surfaces with the masking technique and it works like a charm. Sadly, to create the surface I had to draw the 6 sprites in 8 directions with offset to create the surface which proves to be extremely slow on my phone.

Decided to go with shaders. I have a shader but it only draws a border on the individual sprite and not as a whole. Can anyone help me on this?

Code:
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

void main() {
    vec4 object_space_pos = vec4(in_Position.x, in_Position.y, in_Position.z, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;

    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
}
Code:
//
// Sprite outline shader
//

varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform float outlineRadius;

void maxAlphaAround(inout float alpha) {
  for(float i = 0.0; i < 5.0; ++i) {
    float xx = floor(sqrt(outlineRadius * outlineRadius - (i * outlineRadius) * (i * outlineRadius)));
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord + vec2(xx, outlineRadius - xx)).a);
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord + vec2(outlineRadius - xx, xx)).a);
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord - vec2(xx, outlineRadius - xx)).a);
    alpha = max(alpha, texture2D(gm_BaseTexture, v_vTexcoord - vec2(outlineRadius - xx, xx)).a);
  }
}

void minAlphaAround(inout float alpha) {
  for(float i = 0.0; i < 5.0; ++i) {
    float xx = floor(sqrt(outlineRadius * outlineRadius - (i * outlineRadius) * (i * outlineRadius)));
    alpha = min(alpha, texture2D(gm_BaseTexture, v_vTexcoord + vec2(xx, outlineRadius - xx)).a);
    alpha = min(alpha, texture2D(gm_BaseTexture, v_vTexcoord + vec2(outlineRadius - xx, xx)).a);
    alpha = min(alpha, texture2D(gm_BaseTexture, v_vTexcoord - vec2(xx, outlineRadius - xx)).a);
    alpha = min(alpha, texture2D(gm_BaseTexture, v_vTexcoord - vec2(outlineRadius - xx, xx)).a);
  }
}

void main() {
  gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
  float alpha = texture2D(gm_BaseTexture, v_vTexcoord).a;
  if(alpha == 0.0) {
    // potential for being a border
    maxAlphaAround(alpha);
    gl_FragColor = vec4(0, 0, 0, alpha);
  } else if(alpha != 1.0) {
    // already kind of transparent
    minAlphaAround(alpha);
    if(alpha == 0.0) {
      // this is near an edge, but was transparent
      gl_FragColor = v_vColour * texture2D(gm_BaseTexture, v_vTexcoord) * texture2D(gm_BaseTexture, v_vTexcoord).aaaa;
      gl_FragColor.a = 1.0;
    }
  }
}
 

Bingdom

Googledom
Couldn't you do something like this?
Code:
shader_set(shdr_outline)
draw_sprite
draw_sprite
draw_sprite
shader_reset()
 

JayR

Member
Couldn't you do something like this?
Code:
shader_set(shdr_outline)
draw_sprite
draw_sprite
draw_sprite
shader_reset()
Yes, I'm already doing that.
Code:
shader_set(sh_outline);
shader_set_uniform_f(shader_get_uniform(sh_outline, "outlineRadius"), 0.0024);
if(hook_ready) {
    draw_sprite_ext(sprite_index, image_index, x, y, 1, 1, 0, image_blend, image_alpha);
    draw_sprite_ext(anim_hook_ready_front_idle_wrist, image_index, x, y, 1, 1, 0, image_blend, image_alpha);
} else {
    draw_sprite_ext(arm_sprites[arm_dir], image_index, x, y, 1, 1, image_angle, image_blend, image_alpha);
}

shader_reset();
 

Bingdom

Googledom
Your shader looks really slow.
Here is a faster one:
Create event
Code:
//Outline shader
sprite_scale = shader_get_uniform(SDR_Outline, "sprite_size");
tex = sprite_get_texture(sprite_index,image_index);
//Texel
tex_h = (1/sprite_height)*image_yscale;
tex_w = (1/sprite_width)*image_xscale;
SDR_Outline
Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec2 sprite_size;

void main()
{
    float alpha = 0.0;
   
    //Add alpha depending on the pixels surround it
    alpha += texture2D( gm_BaseTexture, v_vTexcoord + vec2(-sprite_size.x, 0.0) ).a; //Left
    alpha += texture2D( gm_BaseTexture, v_vTexcoord + vec2(sprite_size.x , 0.0) ).a; //Right
    alpha += texture2D( gm_BaseTexture, v_vTexcoord + vec2(0.0, sprite_size.y) ).a; //Down
    alpha += texture2D( gm_BaseTexture, v_vTexcoord + vec2(0.0, -sprite_size.y) ).a; //Up
   
    //Now draw the results
    gl_FragColor = vec4(0.0,0.0,0.0, alpha);
}
It just requires you to draw the sprite again after the shader.

For the textures, there is a way to pass in textures to the shader using uniforms
Code:
uniform sampler2D head;
Then to read it, I think it would be:
Code:
texture2D( head, v_vtexcoord );
Haven't done shaders in a while, might be a bit off.
 
Last edited:
Top