Issue turning a square sprite into a circle using shaders

I have a 64 x 64 pixel white square that I’m trying to clip with a shader so I have a nice smooth edged circle. It’s a sprite.

I can’t however, get the alpha for work on the edges.

This is what I’m trying to make, minus the red. This was done with a shader.
This is the sample code for this test, but not the code I’m working with:

varying mediump vec2 var_texcoord0;

uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;

void main()
{
	vec4 tex = texture2D(texture_sampler, var_texcoord0);

	vec2 p = var_texcoord0 * 2.0 - 1.0;
	float d = length(p);
	float alpha = 1.0 - smoothstep(0.70, 1.0, d);

	vec3 outside_colour = vec3(1.0, 0.0, 0.0); // red
	vec3 inside_colour = tex.rgb;

	vec3 final_colour = mix(outside_colour, inside_colour, alpha);

	gl_FragColor = vec4(final_colour, tint.a);
}

But my issue is when I try to apply it with alpha, it just renders as a solid white square. (I have blend mode Alpha on the sprite). This is the code I’m working with.

varying mediump vec2 var_texcoord0;

uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;

void main()
{
	vec4 tex = texture2D(texture_sampler, var_texcoord0);

	vec2 p = var_texcoord0 * 2.0 - 1.0;
	float d = length(p);
	float alpha = 1.0 - smoothstep(0.70, 1.0, d);

	gl_FragColor = vec4(tex.rgb, alpha);
}

One other thing, when I run an actual build of the game, these both disappear completely anyway. I can only see them in the editor, so that’s another issue I have after I eventually get this alpha thing fixed…

I did also test directly applying alpha, and it worked… so I’m really confused.

void main()
{
	gl_FragColor = vec4(0.0, 0.0, 1.0, 0.05);
}

My material settings:

My sprite’s atlas settings (its the only image on the atlas and I turned off extrude borders)

By default, the renderer has the pre-multiplied alpha channel enabled. This means you need to multiply the color component by the alpha value.

P.S. I don’t know what your plan is, but most likely, UV-based corner rounding (var_texcoord0) will stop working as soon as you add more sprites to the atlas.

Hi, could explain please why it is this way and what’s the point? I’ve seen that in all the builtins material shaders, and I am doing it in mine, but I dont understand what is the use of multiplying alpha to all color?

See ‘fragment shader’ section, point [4] here:

gl_FragColor = vec4(tex.rgb, alpha); - > gl_FragColor = vec4(tex.rgb*alpha, alpha);

Thank you for the reference. But my point is I understand why we must do this in the shader "
The color value of the tint gets premultiplied with its alpha value since all runtime textures already contain premultiplied alpha
"
But I dont understand why runtime textures are already premultiplied by alpha..?
Why does the runtime do this internally?
That’s just for my deeper understanding of the rendering pipeline.

It’s an old trick and it’s about being able to do filtering and mipmapping of the textures correctly.
Some reading materials:

2 Likes

Thank you! That’s why I love this forum: many people who know things and want to share their knowledge! Je m’endormirais moins bête ce soir

Hi, I kind of highjacked your post sorry, did you manage to fix your problem?
Does premultiplying alpha on the tint color fix it?

In the OP’s case, they are creating a alpha value instead of reading it from the texture, they will need to pre-multiply by the computed alpha AND by the tint’s alpha, so something like this should work

varying mediump vec2 var_texcoord0;
uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;

void main()
{
	vec4 tex = texture2D(texture_sampler, var_texcoord0);

	vec2 p = var_texcoord0 * 2.0 - 1.0;
	float d = length(p);
	float alpha = 1.0 - smoothstep(0.70, 1.0, d);

	gl_FragColor = vec4(tex.rgb*alpha*tint.a, alpha*tint.a);
}

Note, that I am assuming OP wants to use tint.a for an extra overall transparency. And I used the multiplying approach, but you could use other approaches like

gl_FragColor = vec4(tex.rgb*(alpha + tint.a), alpha + tint.a);

Or if you don’t care for tint.a at all (which I believe was OP’s original intent) then just don’t use it.

gl_FragColor = vec4(tex.rgb*alpha, alpha);

Alternatively, you can do it without pre-multiplying but you will have to mix your texture with black like this

varying mediump vec2 var_texcoord0;
uniform lowp sampler2D texture_sampler;
uniform lowp vec4 tint;

void main()
{
	vec4 tex = texture2D(texture_sampler, var_texcoord0);

	vec2 p = var_texcoord0 * 2.0 - 1.0;
	float d = length(p);
	float alpha = 1.0 - smoothstep(0.70, 1.0, d);

	vec3 outside_colour = vec3(0.0, 0.0, 0.0); // black
	vec3 inside_colour = tex.rgb;

	vec3 final_colour = mix(outside_colour, inside_colour, alpha);

	gl_FragColor = vec4(final_colour, alpha); //not tint.a
}

This works because now we are setting the transparency color to zero (black) which means when it blends (add) with the framebuffer color it won’t add anything to the resulting color even though it is not being pre-multiplied.

When I hear

My understanding is that in Defold the OpenGL blending function is set as:

glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glBlendEquation(GL_FUNC_ADD);

Which means the blending equation is:

C_final = C_src + C_dst * (1 - aplha_src)

You can see that C_src (the color you are currently processing in your shader) is not affected by alpha, so transparency effect will work only if you pre-multiply it or if you make C_src black like I did in the alternative solution.