• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Shaders [SOLVED] My first outline shader

F

Feral

Guest
Hey everyone,

This is my very first post here. I'm having some issues with an outline shader i'm trying to use.
I first tried this guy's code:
but it didn't work (no outline at all: just my sprite).

Then, I found this topic: https://forum.yoyogames.com/index.php?threads/solved-problem-with-outline-shader.16721/ and tried to write my own shader by fusing pieces of code from the video and from this topic.

So, here's the beast:

Shader fragment:
Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform vec2 sprite_size;

void main()
{
    vec2 offsetx;
    vec2 offsety;
    offsetx.x = sprite_size.x;
    offsety.y = sprite_size.y;
    
    float alpha = texture2D( gm_BaseTexture, v_vTexcoord ).a;
 
    alpha = max(alpha, texture2D( gm_BaseTexture, v_vTexcoord + offsetx ).a);
    alpha = max(alpha, texture2D( gm_BaseTexture, v_vTexcoord - offsetx ).a);
    alpha = max(alpha, texture2D( gm_BaseTexture, v_vTexcoord + offsety ).a);
    alpha = max(alpha, texture2D( gm_BaseTexture, v_vTexcoord - offsety ).a);

    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.a = alpha;
}
Create event:

Code:
sprite_scale = shader_get_uniform(shd_black_outline, "sprite_size");
tex = sprite_get_texture(sprite_index,image_index);
tex_h = texture_get_texel_height(tex);
tex_w = texture_get_texel_width(tex);
Draw event:

Code:
shader_set(shd_black_outline);
shader_set_uniform_f(sprite_scale, tex_w, tex_h);
draw_self();
shader_reset();
Here's the sprite i'm using:

test.png

And finally the result I get with my shader:

test2.png

I first thought it was due to the collision mask:

test3.png

but switching the Bounding Box to "Full Image" didn't correct the problem.
What did I do wrong?

GM Version: 1.4.1763

Thanks for reading,

Feral
 

Attachments

TrunX

Member
Haven't checked your shader code as I'm not a shader expert, but GameMaker crops your images on export automatically except you deactivate this option in the preferences.
Could be the source of your problem if it has something to do with the sprite size on the texture page.
 
  • Like
Reactions: Xor

Nux

GameMaker Staff
GameMaker Dev.
oh i've never used max before, so i removed what i had previously. sorry
 

Xor

@XorDev
This is due to the texture page. The simplest solution in GM:S 1.x is to tick the "Used for 3D" box, but this only works with sprites that have a power of 2 size (32x32,64x64,128x64,etc). The other option is to use the uvs from sprite_get_uvs(), but this is much more complicated. In order to use the uvs you need to convert them from the range of 0 to 1 into the new minimum and maximum uvs and then pass them in using a uniform.
Hopefully this clears things up.
 
F

Feral

Guest
Hey, thanks for your answers! I really appreciate !

@TrunX I couldn't find this option in the Preferences or in the Global Game Settings
@Xor I just tried your solution and it instantly worked ! Thank you very much :)

Thank you again for your concern !
 
  • Like
Reactions: Xor
F

Feral

Guest
You can find the option under "Global Game Settings -> Texture Groups -> No cropping" but I think you need the Professional Edition for that.
This functionality is indeed limited to the Professional Version. Thanks for the precision!
 
J

JayR

Guest
This is due to the texture page. The simplest solution in GM:S 1.x is to tick the "Used for 3D" box, but this only works with sprites that have a power of 2 size (32x32,64x64,128x64,etc). The other option is to use the uvs from sprite_get_uvs(), but this is much more complicated. In order to use the uvs you need to convert them from the range of 0 to 1 into the new minimum and maximum uvs and then pass them in using a uniform.
Hopefully this clears things up.
Both "No cropping" & "Used for 3D" solutions will create extra texture pages. These are quite resource expensive on the mobile platform. Any other way to solve this? I was thinking if its possible to have a 2px offset between sprites in the texture pages. Not sure how to go about doing this.
 
A

Arconious

Guest
This is due to the texture page. The simplest solution in GM:S 1.x is to tick the "Used for 3D" box, but this only works with sprites that have a power of 2 size (32x32,64x64,128x64,etc). The other option is to use the uvs from sprite_get_uvs(), but this is much more complicated. In order to use the uvs you need to convert them from the range of 0 to 1 into the new minimum and maximum uvs and then pass them in using a uniform.
Hopefully this clears things up.
Sorry to resurrect an old thread, but I'm having a really though time trying to figure out how to implement your suggestion and most of it comes to a lack of understanding of a UV space and what all values I'm even trying to achieve. I'm very new to shaders as is.

I understand how to create a uniform, retrieve the sprite UVs, and transfer them into the Fragment Shader -- but I have no clue where to go from there.

My goal is exactly like the OP here -- to create an outline shader, without needing to sacrifice the use of texture pages. My understanding of the problem is that the texture page as a whole is being considered for the UV space, and I need to do some conversion to get it to only be manipulating/referencing the specific sprite on the texture page... I just can't wrap my head around how to do that and would love any additional pointers in the right direction.

There was another thread with the same idea brought up, but I couldn't glean from the answer given there either how to carry forward.

Thank you!
 

Xor

@XorDev
Sorry to resurrect an old thread, but I'm having a really though time trying to figure out how to implement your suggestion and most of it comes to a lack of understanding of a UV space and what all values I'm even trying to achieve. I'm very new to shaders as is.

I understand how to create a uniform, retrieve the sprite UVs, and transfer them into the Fragment Shader -- but I have no clue where to go from there.
Thank you!
Hello Arconious. The trick is understanding linear interpolation and texture page coordinates. The sprite's texture coordinates are between 0 and 1 (both x and y) when the "Used for 3D" box is ticked, but otherwise it will be 0-1 on the texture page so the sprite may be 0-0.5 for example if it is one quarter of the texture page.

I wrote up some hypothetical code for the fragment shader, but I haven't tested it. If there is an error, try to fix it or let me know. In the shader, I modified the code above to convert the texture page coordinates (ie. 0-0.5) to the sprite texture coordinates (0-1) and back. This is because the offset needs to be added to the sprite coordinates and not the texture page coordinates, but then it needs to be converted back. If you're having trouble understanding, then look at the code below:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform vec4 sprite_uv;   //Values returned from sprite_get_uvs
uniform vec2 sprite_size;//Change to resolution of sprite (ie. 21x26)

vec2 frac(vec2 tc,vec4 uv)//Convert texture coordinates from uv to the range of 0-1.
{
     return tc*(uv.zw-uv.xy)+uv.xy;
}
vec2 page(vec2 tc,vec4 uv))//Convert texture coordinates from the range of 0-1 to uv.
{
     return (tc-uv.xy)/(uv.zw-uv.xy);
}

void main()
{
    vec3 offset = vec3(vec2(1,1)/sprite_size,0);
    float alpha = texture2D( gm_BaseTexture, v_vTexcoord ).a;
 
    alpha = max(alpha, texture2D( gm_BaseTexture, page(frac(v_vTexcoord) + offset.xz)).a);
    alpha = max(alpha, texture2D( gm_BaseTexture, page(frac(v_vTexcoord) - offset.xz)).a);
    alpha = max(alpha, texture2D( gm_BaseTexture, page(frac(v_vTexcoord) + offset.zy)).a);
    alpha = max(alpha, texture2D( gm_BaseTexture, page(frac(v_vTexcoord) - offset.zy)).a);

    gl_FragColor = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );
    gl_FragColor.a = alpha;
}
Just a word of warning; this will not include the empty space of the sprite. This may be a problem when drawing, because you can't apply an outline around the sprite on the outside as will not be applied to the empty space. Give it a try to see what I mean. You can apply the outline shader to a surface, but then you don't have control over the individual sprites (like if you wanted different color outlines). Good luck on your project!
 
Top