2D Godot Shader Language, how do I create an inner drop shadow?

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

In 2D Godot shaders, I would like to create a drop shadow that only shows up inside a sprite. For example, if I had a donut like shape, I would like the shadow to only appear in the hole.

:bust_in_silhouette: Reply From: epark009

Here’s the answer I came up with, for anyone else struggling with this. The following shader can be applied to any binary image consisting of white and transparent, where the white area is where you want the shadow to appear and the transparent area is outside:

shader_type canvas_item;

uniform float radius = 5.0;
uniform float x = 0.0;
uniform float y = 0.0;

void fragment(){
	vec2 px = TEXTURE_PIXEL_SIZE;
	float total_alpha = 0.0;

	for(float h = (radius - 1.0) * -1.0; h < radius; h++){
		for(float v = (radius - 1.0) * -1.0; v < radius; v++){
			if(length(vec2(h, v)) > radius) continue;
			total_alpha += texture(TEXTURE, UV + px * vec2(h - x, v - y)).a;
		}
	}
	float alpha_ratio = total_alpha / (radius * radius * 4.0);
	COLOR.a = clamp(1.0 - alpha_ratio, 0.0, 1.0);
}

The idea here is for every pixel on the image, the more transparency there is within the specified radius of the given pixel, the darker that pixel should get. This should darken the regions near the edges of the binary image and the transparent area outside of the white area and lighten everything else. The x and y values will offset the shadow, and radius will make it blurrier or sharper. Simply modulate the binary image to get the shadow color that you want. Be sure to leave at least a 1 pixel margin around the edges of the binary image, so none of the white pixels touches the edges of the image (if white pixels touch the edges, this shader ignores that region and makes a shadowless area where there shouldn’t be one).

Unfortunately, there currently isn’t a way for the shader itself to get light position information in fragment(), so you’ll need to use GDScript and vector math to position the shadow. Simply adjust the x and y values to move the shadow around.