Confusion about floats with shaders

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By 1izNoob

I am currently trying to understand shaders from this tutorial: Outlines for 2D sprites - GDQuest
where an outline for a character is created with this code:

shader_type canvas_item;

uniform vec4 line_color : hint_color = vec4(1);
uniform float line_thickness : hint_range(0, 10) = 1.0;

void fragment() {
    vec2 size = TEXTURE_PIXEL_SIZE * line_thickness;

float outline = texture(TEXTURE, UV + vec2(-size.x, 0)).a;
outline += texture(TEXTURE, UV + vec2(0, size.y)).a;
outline += texture(TEXTURE, UV + vec2(size.x, 0)).a;
outline += texture(TEXTURE, UV + vec2(0, -size.y)).a;
outline += texture(TEXTURE, UV + vec2(-size.x, size.y)).a;
outline += texture(TEXTURE, UV + vec2(size.x, size.y)).a;
outline += texture(TEXTURE, UV + vec2(-size.x, -size.y)).a;
outline += texture(TEXTURE, UV + vec2(size.x, -size.y)).a;
outline = min(outline, 1.0);

vec4 color = texture(TEXTURE, UV);
COLOR = mix(color, line_color, outline - color.a);
} 

I understand all of it except for outline. For example, for

float outline = texture(TEXTURE, UV + vec2(-size.x, 0)).a;

we look at the TEXTURE and shift each pixel a bit to the left bu using UV, and then we take the transparency of that with ‘.a’. All of that makes sense. What I don’t understand is why this is stored in a float? So here would be all my questions:

  1. How could one float be used to represent all the pixels to the left? Wouldn’t a float just a single value like 1.0?
  2. When we add all the other sides, how is that working? Don’t we just add individual floats like 1.0 + -1.0? I am really struggling to grasp how that could represent all the pixels.

(I guess along with that 3. Since there is no print function for shaders, is there any way I could print the values? I tried creating a uniform variable with get_shader_params in GDScript… but that didn’t work)

I am quite sure I have some more fundamental misunderstand how shaders work, would really appreciate someone helping! So many thanks in advance :slight_smile:

About the debugging/printing, what I always did for debugging shaders is to make them return not their original value but instead the value I want to debug.

E.g. if you want to check only the value of “outline”, I’d change the last line to:

COLOR = vec4(outline, outline, outline, 1.0)

That way, you can at least see the value (well, approximately anyway).

TheSHEEEP | 2020-11-25 13:51

:bust_in_silhouette: Reply From: TheSHEEEP

First of all, make sure you realize that this method will work only for 2D sprites - this confused me first when trying to understand what’s going on. Trying to understand this by imagining what would be happening in a 3D scenario won’t work.
You really have to look at and imagine only the 2D area of the sprite.

For a sprite, you have to have an image that consists of the area that is supposed to be displayed (e.g. a character) and the area that is not supposed to be display (e.g. white background).
That background will have full transparency on all pixels - while every pixel of the character will have at least some opacity.

Now, what happens normally is that for each pixel of the sprite on the screen, the correct color value incl. alpha from the sprite’s image at that UV position is used unmodified.

This shader, however, also looks at eight pixels in the image around the UV position at a certain distance and accumulates their alpha value - and only their alpha value, nothing else.
As you said correctly, it’s just one value, not an entire color.
That the tutorial calls that value “outline” is quite misleading, IMO, as it only represents the outline’s inverse strength (the outline is actually stronger the lower the value of “outline”). Likewise, I’d have called the “size” variable “distance” as it is only used for that.

Anyway, what this accumulated alpha value “outline” does is represent how close to the character’s “border” (the area in the image close to the background with full transparency) the current pixel is.
The closer to the border, the more transparent the accumulated alpha value will be - because all those texture lookups around the current pixel will end up with a lot of very small alpha values closer to the border as they go into the background region of the image.

Later on, the “outline” variable is then used together with the normal alpha value at that pixel to determine how much of the original color and how much of the outline color is to be used.

Hey, thank you so much for the long answer, that already made it much clearer!

Although, I am still slightly confused about the use of float:
Let’s say we are only looking at a 2D image with one pixel and nothing around it. Now, when I create outline and assign it the float value of the pixel to the left, this should be 0.0 (hope that is correct).

And now, when I look at each position around the pixel, since all values are empty, I would keep on adding 0.0 to it; and end up with 0.0 overall. What I am fundamentally confused about is how to translate that float value into a position?

(Hope that makes sense)

1izNoob | 2020-11-25 15:23