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
2 Likes