Frosted Glass Shader (Rounded Rect, Outline, Shadow)

1. Setup

Create a ColorRect or TextureRect node in your scene.

Assign a new ShaderMaterial to the node.

Load this shader file (CircleFrostedGlass.gdshader) into the material.

Important: Ensure the ColorRect is large enough to contain your shape + shadow + outline. The shader draws inside the node’s bounds.

2. Essential Parameters

node_size: This MUST match the size of your node (e.g., (400, 300)). You can set this manually in the inspector or use a script to update it automatically (see below).

shape_type: Choose 0 for Circle or 1 for Rounded Rectangle.

rect_size: The size of the visible glass pane in pixels.

corner_radius_*: Adjust the roundness of each corner independently (in pixels).

Shader code
shader_type canvas_item;

// Screen texture for frosted glass effect
uniform sampler2D screen_texture : hint_screen_texture, filter_linear_mipmap;

// === Shape Settings (Pixel Units) ===
// Shape type: 0=Circle, 1=Rounded Rectangle
uniform int shape_type : hint_range(0, 1) = 1;
// Rectangle size (pixels)
uniform vec2 rect_size = vec2(300.0, 200.0);
// Corner radii for each corner (pixels)
uniform float corner_radius_top_left = 20.0;
uniform float corner_radius_top_right = 20.0;
uniform float corner_radius_bottom_left = 20.0;
uniform float corner_radius_bottom_right = 20.0;
// Circle radius (pixels, used when shape_type=0)
uniform float circle_radius = 100.0;

// === Outline Settings ===
uniform bool outline_enabled = true;
uniform float outline_width = 2.0; // pixels
uniform vec4 outline_color : source_color = vec4(1.0, 1.0, 1.0, 0.8);

// === Shadow Settings ===
uniform bool shadow_enabled = true;
uniform vec4 shadow_color : source_color = vec4(0.0, 0.0, 0.0, 0.5);
uniform vec2 shadow_offset = vec2(10.0, 10.0); // pixel offset
uniform float shadow_blur = 15.0; // shadow blur radius

// === Edge Settings ===
uniform float edge_softness : hint_range(0.0, 5.0) = 1.0; // Edge antialiasing width (pixels)

// === Color Settings ===
uniform vec3 shape_color : source_color = vec3(0.3, 0.5, 0.9);
uniform float color_gradient_y : hint_range(0.0, 3.0) = 1.5;
uniform float color_gradient_x : hint_range(-2.0, 2.0) = -0.8;
uniform float color_multiplier : hint_range(0.1, 2.0) = 0.9;

// === Screen Blur Settings (Frosted Glass) ===
uniform bool enable_screen_blur = true;
uniform float blur_amount : hint_range(0.0, 10.0) = 3.0;
uniform int blur_samples : hint_range(1, 8) = 4;
// Glass opacity/tint strength (smaller value = more transparent/clear, larger value = more solid color)
uniform float glass_opacity : hint_range(0.0, 1.0) = 0.2;

// Node size (used for correct UV calculation, set via script)
uniform vec2 node_size = vec2(400.0, 400.0);

// Rounded Rectangle SDF
float rounded_rect_sdf(vec2 p, vec2 size, vec4 radii) {
    // radii: x=top-left, y=top-right, z=bottom-right, w=bottom-left
    vec2 r;
    if (p.x > 0.0) {
        r = (p.y > 0.0) ? radii.zw : radii.yz;
    } else {
        r = (p.y > 0.0) ? radii.xw : radii.xy;
    }
    float radius = (p.x > 0.0) ? r.x : r.y;
    
    vec2 q = abs(p) - size + radius;
    return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - radius;
}

// Circle SDF
float circle_sdf(vec2 p, float r) {
    return length(p) - r;
}

// Gaussian Blur Sampling
vec3 blur_screen(vec2 screen_uv, vec2 pixel_size) {
    vec3 color = vec3(0.0);
    float total_weight = 0.0;
    
    for (int x = -blur_samples; x <= blur_samples; x++) {
        for (int y = -blur_samples; y <= blur_samples; y++) {
            vec2 offset = vec2(float(x), float(y)) * pixel_size * blur_amount;
            float weight = 1.0 / (1.0 + float(x * x + y * y));
            color += texture(screen_texture, screen_uv + offset).rgb * weight;
            total_weight += weight;
        }
    }
    
    return color / total_weight;
}

// Get SDF distance for the shape (Pixel Units)
float get_shape_sdf(vec2 p) {
    float dist;
    if (shape_type == 0) {
        // Circle
        dist = circle_sdf(p, circle_radius);
    } else {
        // Rounded Rectangle
        vec2 half_size = rect_size * 0.5;
        
        // Limit corner radius to half the side length
        float min_dim = min(half_size.x, half_size.y);
        vec4 radii = vec4(
            min(corner_radius_top_left, min_dim),
            min(corner_radius_top_right, min_dim),
            min(corner_radius_bottom_right, min_dim),
            min(corner_radius_bottom_left, min_dim)
        );
        
        dist = rounded_rect_sdf(p, half_size, radii);
    }
    return dist;
}

void fragment() {
    // Convert UV to pixel coordinates, center at (0,0)
    vec2 p = (UV - 0.5) * node_size;
    
    // 1. Draw Shadow (Behind Glass)
    vec4 final_color = vec4(0.0);
    
    if (shadow_enabled) {
        float s_dist = get_shape_sdf(p - shadow_offset);
        // Shadow mask: 1 inside, fades out outwards
        float s_mask = 1.0 - smoothstep(-shadow_blur, 0.0, s_dist); 
        // Mix shadow
        final_color = mix(final_color, shadow_color, s_mask * shadow_color.a);
    }
    
    // 2. Draw Main Glass Shape
    float dist = get_shape_sdf(p);
    // Shape mask (1px antialiasing)
    float shape_alpha = 1.0 - smoothstep(0.0, edge_softness, dist);
    
    // Calculate gradient color (using normalized UV)
    vec2 uv_norm = UV - 0.5;
    // Correct UV aspect ratio for gradient calculation to keep visual consistency
    float aspect = node_size.x / node_size.y;
    uv_norm.x *= aspect;
    
    vec3 dynamic_color = vec3(
        shape_color.r + uv_norm.y * color_gradient_y,
        shape_color.g + uv_norm.x * color_gradient_x,
        shape_color.b
    ) * color_multiplier;
    
    vec3 glass_col = dynamic_color;
    
    // Apply Screen Blur (Frosted Glass Effect)
    if (enable_screen_blur) {
        vec2 screen_uv = SCREEN_UV;
        vec2 pixel_size = SCREEN_PIXEL_SIZE;
        vec3 blurred_bg = blur_screen(screen_uv, pixel_size);
        
        // Mix effect color with blurred background
        glass_col = mix(blurred_bg, glass_col, glass_opacity);
    }
    
    // Mix glass layer onto final color
    final_color = mix(final_color, vec4(glass_col, 1.0), shape_alpha);
    
    // 3. Draw Outline (Above Glass)
    if (outline_enabled) {
        // Outline mask: generated at edge, width is outline_width
        // abs(dist) < width/2 means outline centered on edge
        float outline_mask = 1.0 - smoothstep(outline_width * 0.5 - 0.5, outline_width * 0.5 + 0.5, abs(dist));
        
        // Use Alpha Compositing formula instead of simple mix to prevent gray edges
        vec4 src_color = outline_color;
        float src_alpha = outline_mask * outline_color.a;
        vec4 dst_color = final_color;
        
        // Final Alpha = SrcAlpha + DstAlpha * (1 - SrcAlpha)
        float out_alpha = src_alpha + dst_color.a * (1.0 - src_alpha);
        
        vec3 out_rgb = vec3(0.0);
        if (out_alpha > 0.0) {
            // Final RGB = (SrcRGB * SrcAlpha + DstRGB * DstAlpha * (1 - SrcAlpha)) / FinalAlpha
            out_rgb = (src_color.rgb * src_alpha + dst_color.rgb * dst_color.a * (1.0 - src_alpha)) / out_alpha;
        }
        
        final_color = vec4(out_rgb, out_alpha);
    }
    
    COLOR = final_color;
}
Live Preview
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

Related shaders

guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Maple
18 days ago

Pretty cool effect! love it!

Thanks for sharing this!

Here is a little advice: the node_size is actually able to be calculated inside fragment function like this:

vec2(1.0 / TEXTURE_PIXEL_SIZE.x, 1.0 / TEXTURE_PIXEL_SIZE.y)

btw: setup for corner radius top left seems invalid for some reason.

Last edited 17 days ago by Maple
ssangkall
ssangkall
3 days ago

It looks so nice!