I’m trying to make a shader that creates a border around the shaded object, with a specific width in pixels. Here’s my shader right now:
shader_type canvas_item;
uniform float border_width:hint_range(0.0, 64, 1.0) = 0.0;
uniform vec3 border_color:source_color = vec3(0.0);
void vertex()
{
//Called for every vertex the material is visible on.
}
void fragment()
{
//Multiply desired border thickness by the UV size of a single pixel
vec2 border_uv = SCREEN_PIXEL_SIZE * border_width;
//We want to paint a border around the edges
if (UV.x <= border_uv.x
|| UV.y <= border_uv.y
|| 1.0 - UV.x <= border_uv.x
|| 1.0 - UV.y <= border_uv.y)
{
COLOR.rgb = border_color;
}
}
For some reason, this doesn’t quite work. This is what I’m getting in the game:
There are two problems:
The border isn’t the right size; border_width is set to 64, so I’d expect it to be 64 pixels thick, but this is a full screen grab and the borders are far less than 64 pixels in thickness.
The border isn’t the same size on all sides. I’d expect the X and Y borders to match, but instead for example the left border is 13 pixels wide, and the top border is 10 pixels tall.
I figure I’m misunderstanding SCREEN_PIXEL_SIZE here. How should I go about creating a border that’s measured in exact screen pixels?
The value of the built-in SCREEN_PIXEL_SIZE is relative to the size of the screen (i.e. window or viewport). This means that the aspect ratio of SCREEN_PIXEL_SIZE is the same as that of the viewport (e.g. 16:9). Using the UVs of a 2d sprite that does not share the aspect ratio of the screen will result in distortion – in your case, distorted borders.
You can try to use the more specific TEXTURE_PIXEL_SIZE. Although, without having tried it myself, I believe this will prohibit you from scaling the object as that changes the frame of reference which also causes distortion.
I’m not sure what the best approach is for a per-object outline shader. The outlines I’ve seen in other projects and tutorials are mostly produced via a post-processing shader, or they use the fresnel of the mesh which is not applicable here.
There is a technique which involves using the alpha channel of the sprite and a kernel to produce an outline. However, this technique requires that your sprites contain a margin of transparency i.e. the sprite cannot be touching the edge of the image. Should you choose to use this approach, it would require the use of the UV built-in and the SCREEN_PIXEL_SIZE built-in which you’re already using.
(Video tutorial example, with timestamp)
I’ll have to do some research and get back to you.