2D shadows shader (v2)

Features:

  • Any X/Y offset
  • Any color
  • debug helper
  • no need to prepare an image
  • 🆕 uses modulate from parent, including alpha for shadow
  • 🆕 shadow blur. Blur goes out of shadow bounds
  • 🆕 new option for handling parents’ rotation

Github: https://github.com/TABmk/godot-2d-shadow-shader

YouTube preview: https://www.youtube.com/watch?v=9rneonV7nQ0

Shader code
// version: 2.0
// repository: https://github.com/TABmk/godot-2d-shadow-shader

shader_type canvas_item;

uniform bool debug = false;
uniform float border_scale = 2.0;
uniform vec2 shadow_offset = vec2(-10.0, -10.0);
uniform vec4 color : source_color;
uniform float blur_amount : hint_range(0.0, 5.0) = 0.0;
uniform float shadow_scale = 1.5;
uniform bool disable_rotating = false;

varying flat vec4 modulate;
varying flat float sprite_rotation;

void vertex() {
    float final_scale = max(border_scale, border_scale * shadow_scale);
    VERTEX.xy *= vec2(final_scale);
    modulate = COLOR;
    sprite_rotation = atan(MODEL_MATRIX[0][1], MODEL_MATRIX[0][0]);
}

vec4 sample_texture_safe(sampler2D tex, vec2 uv) {
    return (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) 
        ? vec4(0.0) 
        : texture(tex, uv);
}

vec4 apply_gaussian_blur(sampler2D tex, vec2 uv, vec2 pixel_size) {
    if (blur_amount <= 0.0) return sample_texture_safe(tex, uv);
    
    vec4 color_blur = vec4(0.0);
    float total_weight = 0.0;
    int kernel_size = int(blur_amount * 3.0);
    
    for (int x = -kernel_size; x <= kernel_size; x++) {
        for (int y = -kernel_size; y <= kernel_size; y++) {
            vec2 blur_offset = vec2(float(x), float(y)) * pixel_size;
            float weight = exp(-0.5 * (float(x * x + y * y)) / (blur_amount * blur_amount));
            color_blur += sample_texture_safe(tex, uv + blur_offset) * weight;
            total_weight += weight;
        }
    }
    
    return total_weight > 0.0 ? color_blur / total_weight : vec4(0.0);
}

vec2 rotate_point(vec2 point, float angle) {
    float s = sin(angle);
    float c = cos(angle);
    return vec2(
        point.x * c - point.y * s,
        point.x * s + point.y * c
    );
}

float calculate_fade(float coord, float scale) {
    if (coord < 0.0) {
        return 1.0 + (coord / (scale - 1.0));
    } else if (coord > 1.0) {
        return 1.0 - ((coord - 1.0) / (scale - 1.0));
    }
    return 1.0;
}

vec4 process_texture(vec2 uv, sampler2D tex, bool is_main, vec2 pixel_size) {
    if (is_main) {
        return sample_texture_safe(tex, uv) * modulate;
    }
    
    vec4 blurred = apply_gaussian_blur(tex, uv, pixel_size);
    float fade_x = calculate_fade(uv.x, shadow_scale);
    float fade_y = calculate_fade(uv.y, shadow_scale);
    float fade = smoothstep(0.0, 1.0, min(fade_x, fade_y));
    
    return vec4(color.rgb, (blurred.a - (1.0 - color.a)) * modulate.a * fade);
}

void fragment() {
    float final_scale = max(border_scale, border_scale * shadow_scale);
    vec2 scaled_uv = UV * final_scale - (0.5 * (final_scale - 1.0));
    
    vec4 main_texture = process_texture(scaled_uv, TEXTURE, true, TEXTURE_PIXEL_SIZE);
    
    vec2 adjusted_offset = disable_rotating ? shadow_offset : rotate_point(shadow_offset, -sprite_rotation);
    vec2 shadow_uv = scaled_uv + adjusted_offset * TEXTURE_PIXEL_SIZE;
    
    vec4 shadow = process_texture(shadow_uv, TEXTURE, false, TEXTURE_PIXEL_SIZE);
    
    vec4 res = mix(shadow, main_texture, main_texture.a);
	vec4 debug_layer = vec4(1.0, 0.0, 0.0, 0.3);
	COLOR = mix(debug ? debug_layer : res, res, res.a);
}
Tags
2d, shadow
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

2D Top-Down Shadows (Tilemap Ready)

Dynamic 2D Lights and Soft Shadows

Stars in Shadows

Subscribe
Notify of
guest

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
zdrmlp
zdrmlp
6 months ago

is there a way to also make the shadow looks soft on the edge or have fade near edges? so it looks smooth?

lukepass86
lukepass86
2 months ago

This is the shader that worked best, thanks! I just had to replace “source_color” with “hint_color” to make it work for Godot 3.5

It would be perfect to have some sort of blur effect for the shadow.

Chris
Chris
2 months ago

Thank you so much for taking the time to make a V2 with blur! =)