Neon Sprite Shader

hydroxy

Member
I'm looking for some help creating a neon sprite shader. I've got a solid understanding of how to generate neon sprites but I'm pretty much looking to automate the process with a shader and need help with the shader coding.

The basic process is first of all the shader will surround a sprite (A) with a glow of decreasing opacity (B) and then draw a white outline (C) of the sprite to be applied on top of the original. This can be seen in the image below (B+C=D)



I think this could perhaps be automated by getting a shader to do all the work. The shader could find all empty pixels adjacent to the non-empty pixels and give them the color of said adjacent pixels with some decreased opacity also. Repeat this process perhaps about 5-10 times and you will have a nice decreasing glow around the sprite. Finally add a white version of the original sprite on top of the completed image and thats that.

I've mocked up the above examples using the glow function in GMS v1.4. Although I'd imagine a shader based version would have the same effect.

Automating this would save me so much time in my current project and the GameMaker community would have a cool neon shader to make retro games and such with.


What do you think? Can anyone help make something like this? I've tried this introduction to shaders video but still the complexities of that new language make this task too difficult for me right now. Any help at all would be appreciated.

Thanks for reading :)
 
Last edited:

jo-thijs

Member
I can't see the images you posted.
If I understand you correctly, you want to draw a white border of 1 pixel around your sprite and you then want a glow around that in whatever color.

You can't directly apply the approach from the video for this, because the way Shaun gets it to work, is by actually already drawing the border in the ghost image,
but he just he just gives it an alpha value of 0 and lets the shader change that.

Directly applying this in your case means you would have to already draw the white border yourself and draw the color of the glow, and then make all of that invisible.

However, you can generalize his script to something like this:
http://www.mediafire.com/file/75i4bzpi0fp9a8q/GMC_neon_effect.gmz
 

hydroxy

Member
I've updated the image to a better server, so hopefully it is visible now. (EDIT: Here is a direct link)

Hmm the gmz you uploaded is kind of going in the right direction but for the effect I want it needs to create a sprite with an entirely white centre then the glow of the chosen color fades away the further you get from the original image.
 
Last edited:

jo-thijs

Member
I've updated the image to a better server, so hopefully it is visible now. (EDIT: Here is a direct link)

Hmm the gmz you uploaded is kind of going in the right direction but for the effect I want it needs to create a sprite with an entirely white centre then the glow of the chosen color fades away the further you get from the original image.
Now I can see them.

I've tried to rewrite the code to something more flexible.
That process has made me learn that I know little about shaders.

Here's a link to the new version:
http://www.mediafire.com/file/36vqx1m653534kd/GMC_neon_effect_2.gmz

It takes very long to compile, but it runs very well.
I'm not entirely sure why it takes so long to compile,
I only know it has somethingto do with the inner loop having a variable upper bound for cy
and the inner loop containing texture2D.
 

hydroxy

Member
Now I can see them.

I've tried to rewrite the code to something more flexible.
That process has made me learn that I know little about shaders.

Here's a link to the new version:
http://www.mediafire.com/file/36vqx1m653534kd/GMC_neon_effect_2.gmz

It takes very long to compile, but it runs very well.
I'm not entirely sure why it takes so long to compile,
I only know it has somethingto do with the inner loop having a variable upper bound for cy
and the inner loop containing texture2D.
Yeah that is definitely much better, any idea how to make it faster? I am not used to that language at all.

Also I am guessing it is absolutely necessary for the sprite to have a lot of empty space for the glow to be inserted into. Is there a way to remove this necessity so I can keep my collision masks and object coordinates simpler? Maybe a way to resize the canvas in GML?
 

jo-thijs

Member
Yeah that is definitely much better, any idea how to make it faster? I am not used to that language at all.
If radius (the size of the glow) is constant throughout your entire project, then yes, otherwise no.

Also I am guessing it is absolutely necessary for the sprite to have a lot of empty space for the glow to be inserted into. Is there a way to remove this necessity so I can keep my collision masks and object coordinates simpler? Maybe a way to resize the canvas in GML?
Your collisions masks and object coordinates shouldn't be suffering due to this.
The collision box is by default matched to the part of your sprite that is not empty.
For object coordinates, that's what origins are for.

Besides that, there may be a way to make it not be a necessity, but I don't directly know how to do that.
 

lolslayer

Member
I've skimmed through that already before you posted a link,
but it's a different effect then originally asked for and it might be better, but it might also be worse.
I was also wondering how the code in "Extracting Bright Color" would best be implemented in GameMaker.
Anyway, it is an interesting technique to use in games.
Just use the way it says how to do it, you can use shaders in GM
 

hydroxy

Member
Isn't this just bloom?

Like in here?
>: D
Hmm, that is not the effect I am looking for. I'm looking for something much more intense than bloom, in every sprite I'm looking for the interior of the sprite to be white, kind of like neon signs, see these for reference:








If radius (the size of the glow) is constant throughout your entire project, then yes, otherwise no.

Your collisions masks and object coordinates shouldn't be suffering due to this.
The collision box is by default matched to the part of your sprite that is not empty.
For object coordinates, that's what origins are for.

Besides that, there may be a way to make it not be a necessity, but I don't directly know how to do that.
Yeah maybe a consistent glow radius wouldn't be a bad idea if it would speed up the code :)

Also, regarding the size, I'm looking to make my sprites as small as possible, for a 16x16 enemy I do not want a 32x32 or 64x64 sprite.
 

jo-thijs

Member
Working with a constant radius, you would change my fragment shader code to something like this:
Code:
//
// Simple passthrough fragment shader
//
#pragma optimize off

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform float pixelW;
uniform float pixelH;
uniform vec4 glowCol;
//uniform float radius;
uniform float factor;

void main()
{
    vec4 fillCol = v_vColour;//vec4(1.0, 1.0, 1.0, 1.0);
    
    gl_FragColor = vec4(fillCol.rgb, fillCol.a * texture2D(gm_BaseTexture, v_vTexcoord).a);
    
    if (gl_FragColor.a > 0.0)
        return;
    
    const float radius = 8.0;
    float m = glowCol.a / factor;
    m *= m;
    for(float cx = 0.0; cx <= radius; cx += 1.0) {
        for(float cy = cx; cx * cx + cy * cy <= radius * radius; cy += 1.0) {
            if (texture2D(gm_BaseTexture, v_vTexcoord + vec2(cx * pixelW, cy * pixelH)).a > 0.0
            || texture2D(gm_BaseTexture, v_vTexcoord + vec2(-cx * pixelW, cy * pixelH)).a > 0.0
            || texture2D(gm_BaseTexture, v_vTexcoord + vec2(cx * pixelW, -cy * pixelH)).a > 0.0
            || texture2D(gm_BaseTexture, v_vTexcoord - vec2(cx * pixelW, cy * pixelH)).a > 0.0
            || texture2D(gm_BaseTexture, v_vTexcoord + vec2(cy * pixelW, cx * pixelH)).a > 0.0
            || texture2D(gm_BaseTexture, v_vTexcoord + vec2(-cy * pixelW, cx * pixelH)).a > 0.0
            || texture2D(gm_BaseTexture, v_vTexcoord + vec2(cy * pixelW, -cx * pixelH)).a > 0.0
            || texture2D(gm_BaseTexture, v_vTexcoord - vec2(cy * pixelW, cx * pixelH)).a > 0.0) {
                m = min(m, cx * cx + cy * cy);
                break;
            }
        }
    }
    
    gl_FragColor = vec4(glowCol.rgb, clamp(glowCol.a - factor * sqrt(m), 0.0, 1.0));
}
I don't know how to solve the sprite issue in a proper way though.
 

lolslayer

Member
Hmm, that is not the effect I am looking for. I'm looking for something much more intense than bloom, in every sprite I'm looking for the interior of the sprite to be white, kind of like neon signs, see these for reference:
Not sure how to get that working, at least not without using 2 different sprites
 
Top