Sprite Outline via Shader - showcase & looking for improvements

Hey guys,

I am looking into outlining sprites using a shader - I am not an expert on shaders at all.
I looked around a bit (googlefu) but did not find any already existing solution.

This is what I got so far:

solid color:
mouseover1

alpha color:
mouseover2

slightly more complex shape:
mouseover3

Here is the shader code:

class OutlinePipeline extends Phaser.Renderer.WebGL.Pipelines.TextureTintPipeline {
    constructor(game: Phaser.Game) {
        let config = {game: game,
            renderer: game.renderer,
            fragShader: `
            precision mediump float;
            uniform sampler2D uMainSampler;
            varying vec2 outTexCoord;
            void main(void) {
                vec4 color = texture2D(uMainSampler, outTexCoord);
                vec4 colorU = texture2D(uMainSampler, vec2(outTexCoord.x, outTexCoord.y - 0.001));
                vec4 colorD = texture2D(uMainSampler, vec2(outTexCoord.x, outTexCoord.y + 0.001));
                vec4 colorL = texture2D(uMainSampler, vec2(outTexCoord.x + 0.001, outTexCoord.y));
                vec4 colorR = texture2D(uMainSampler, vec2(outTexCoord.x - 0.001, outTexCoord.y));
                
                gl_FragColor = color;
                
                if (color.a == 0.0 && (colorU.a != 0.0 || colorD.a != 0.0 || colorL.a != 0.0 || colorR.a != 0.0)  ) {
                    gl_FragColor = vec4(1.0, 0.0, 0.0, .2);
                }
            }`};
        super(config);
    }

}

To get this to run:

in create:

(<Phaser.Renderer.WebGL.WebGLRenderer>this.game.renderer).addPipeline('outline', new OutlinePipeline(this.game));

And to activate / deactivate it on a sprite:

this.sprite.setPipeline("outline");

this.sprite.resetPipeline();

And here are the problems I am looking for input on :slight_smile:

  1. “0.001” is a magic value that works for texture lookup on 1024 x 1024 textures.
    Found it just by trying - there should be a way to get the texture size and calculate this value.

  2. The color & alpha are hard coded into the shader - I guess I should have a look on how the tint pipline get’s the values into the shader :slight_smile:

  3. When looking up the pixels, at the border we look up pixels of other sprites - for that reason there needs to be padding (at least 1 pixel) on the sprite sheet, I don’t know if this could be restricted to only lookup within the sprite itself.

  4. For this to work a 1 pixel transparent border around the picture as part of the sprite is needed. (though I think this is ok and kind of expected)

  5. With some tinkering I guess with this method a glow is possible (lookup further out, different alphas)

Feedback or tips on any of the points is very welcome.

8 Likes

q1:
#extension GL_OES_standard_derivatives : enable
then
replace your magic 0.001 with dFdx(outTexCoord) dFdy(outTexCoord)
BTW:
gl_FragColor = color.a * colurU.a * colorD.a * ..... colorR.a == 0. ? color : vec4(1., 0., 0., .2);
ternery condition is faster then IF.

q2:
suggest that use 3.17.0, there is native Shader gameobject to use, so no need to deal with the renderer.

q3:
texturepacker provides such functionality for you, why not use it? shader is still a resource we need to save… :slight_smile: if you mean your texture is an atlas, then
outTexCoord *= frameSize / resolution
outTexCoord += framePosition / resolution

q4:
of course, OpenGL even renders gaps if you donot pad some transparent pixels between image blocks.

q5:
another topic though…

3 Likes

Thank you for the Feedack @jcyuan!

Can you point me to an example or a short code snipped or documentation how to use a native shader object in 3.17.0?

that’s quite easy:

// in preload method
this.load.glsl("myGlsl", "external_glsl_file.glsl");

// then in create method
const obj = this.add.shader("myGlsl");
obj.setSampler2D(...);  // or shortcut methods: setChannel0 / setChannel1 ... setChannel3
obj.setUniform(...);

// and then just treat this obj as an image to use, even add it into a container...

and, in your glsl, there are built-in uniforms:

uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
uniform float sampleRate;
uniform vec4 date;
uniform vec2 mouse;
uniform float time;
uniform vec2 resolution;

docs ref:
https://photonstorm.github.io/phaser3-docs/Phaser.GameObjects.Shader.html

2 Likes

interesting … shader objects native, i blink and it gets 20 more options. Not up to me to tell people how to do what as for one im just a rank amateur hobbyist. Ever since ive seen the shadertoy website coming up on pouet.net glsl has been winking but far from there. In this case, phaser being pretty hungry already i think id just add an extra frame to the spritesheet which (i think) would in the end take up less resources than a ‘subroutine’ (pardon my archaic) calculating one.

ofcourse i do agree : doing it with a shader is lot more pretty (code-wise) but the client/player/user in the end will notice nothing but maybe the amount of cpu used on the device it runs on … mighty interesting though …

Hey @jppresents,

Could you actually finish this as shader?

And if it is could you be sooo kind and share the glsl? :smiley:

If not is there some source that i can learn about this fragShader ?

Thanks

This is what I ended up using, be aware that this was for phaser 3.16.2 and used some magic numbers as you can see. I think shader pipelines have changed since then.

1 Like

I have an outline effect shader game object

This plugin can’t work with p3.50 yet.

2 Likes

That`s what i have OutlinePipeline (forked) - CodeSandbox
something like violet square on black background…

Are there any updates?
Want to make it work with version >= 3.5

Now I have error:
.WebGL-0x7fd99580aa00]GL ERROR :GL_INVALID_OPERATION : glUniform1iv: count > 1 for non-array

Here is another outline post fx shader plugin, worked for >= phaser3.50.

Demo

  • Pointer-over and pointer-out any sprite
3 Likes

Nice work! Clap 100x times!

It seems like the hardcoded color values could be replaced with uniforms, similar to how tint pipelines pass colors into shaders. That way, the outline color and alpha could be controlled at runtime without modifying the shader each time.