Confetti (ColorRect) [Godot 4.5+]

A canvas_item shader that rains colorful confetti pieces from the top of the screen. All 120 particles are simulated entirely on the GPU, so no nodes, no particles system, no GDScript needed at runtime.

Each piece has its own randomized fall speed, sway frequency, sway amplitude, spin direction, size, and aspect ratio, giving the effect a natural and lively feel. A subtle shimmer simulates the pieces flipping as they rotate. The 8-color palette is fully customizable directly in the Inspector.

Simply apply the shader to a full-screen ColorRect and set the resolution uniform to your viewport size via script

 

Shader code
shader_type canvas_item;

// Usage: 
// 1. Append a ColorRect (Full size)
// 2. Add this Shader to the ColorRect


const int PARTICLE_COUNT = 120;

uniform float   speed         : hint_range(20.0, 500.0)  = 120.0;
uniform float   sway_strength : hint_range(0.0,  200.0)  = 60.0;
uniform float   sway_speed    : hint_range(0.1,  5.0)    = 1.4;
uniform float   spin_speed    : hint_range(0.0,  10.0)   = 3.5;
uniform float   piece_size    : hint_range(2.0,  30.0)   = 10.0;
uniform float   aspect_ratio  : hint_range(0.2,  3.0)    = 2.0;
uniform vec2    resolution    = vec2(1920.0, 1080.0);

uniform vec4    col0 : source_color = vec4(1.00, 0.20, 0.20, 1.0);
uniform vec4    col1 : source_color = vec4(1.00, 0.75, 0.10, 1.0);
uniform vec4    col2 : source_color = vec4(0.10, 0.80, 0.30, 1.0);
uniform vec4    col3 : source_color = vec4(0.15, 0.55, 1.00, 1.0);
uniform vec4    col4 : source_color = vec4(0.85, 0.20, 1.00, 1.0);
uniform vec4    col5 : source_color = vec4(1.00, 0.40, 0.70, 1.0);
uniform vec4    col6 : source_color = vec4(0.10, 0.90, 0.95, 1.0);
uniform vec4    col7 : source_color = vec4(1.00, 1.00, 1.00, 1.0);

float rand(float seed) {
    return fract(sin(seed * 127.1 + 311.7) * 43758.5453);
}

vec4 palette_color(float t) {
    int idx = int(t * 8.0) % 8;
    if (idx == 0) return col0;
    if (idx == 1) return col1;
    if (idx == 2) return col2;
    if (idx == 3) return col3;
    if (idx == 4) return col4;
    if (idx == 5) return col5;
    if (idx == 6) return col6;
    return col7;
}

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

void fragment() {
    vec2 px = UV * resolution;
    vec4 out_color = vec4(0.0);

    for (int i = 0; i < PARTICLE_COUNT; i++) {
        float fi = float(i);

        float start_x  = rand(fi * 1.234)  * resolution.x;
        float start_y  = -(rand(fi * 5.678) * resolution.y);
        float t_offset = rand(fi * 9.101)  * (resolution.y + piece_size * 2.0) / speed;

        float my_speed      = speed         * (0.5 + rand(fi * 2.222));
        float my_sway_str   = sway_strength * (0.3 + rand(fi * 3.333) * 1.4);
        float my_sway_spd   = sway_speed    * (0.5 + rand(fi * 4.444) * 1.5);
        float my_sway_phase = rand(fi * 6.666) * TAU;
        float my_spin       = spin_speed    * (0.5 + rand(fi * 7.777) * 1.5)
                              * (rand(fi * 8.888) > 0.5 ? 1.0 : -1.0);
        float my_size       = piece_size    * (0.5 + rand(fi * 0.123) * 1.0);
        float my_ar         = aspect_ratio  * (0.7 + rand(fi * 0.456) * 0.6);

        float t     = mod(TIME + t_offset, (resolution.y + my_size * 2.0) / my_speed);
        float cy    = start_y + t * my_speed;
        float cx    = start_x + my_sway_str * sin(t * my_sway_spd + my_sway_phase);
        float angle = t * my_spin;

        vec2 delta = px - vec2(cx, cy);
        delta = rotate2d(delta, -angle);
        vec2 local = delta / vec2(my_size * my_ar, my_size);

        float inside = step(max(abs(local.x), abs(local.y)), 1.0);

        if (inside > 0.5) {
            float shimmer = 0.5 + 0.5 * sin(t * my_spin * 2.0);
            vec4 base_col = palette_color(rand(fi * 0.321));
            vec4 lit_col  = mix(base_col * 0.5, base_col, shimmer);
            lit_col.a     = 1.0;
            out_color = mix(out_color, lit_col, inside);
        }
    }

    COLOR = out_color;
}
Live Preview
Tags
2d, ColorRect, Confetti
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.

More from RedKnack Interactive

Related shaders

guest

3 Comments
Oldest
Newest Most Voted
vitsoonyoung
vitsoonyoung
5 months ago

This looks nice but my game drops from 600fps to 20fps on default settings

84u71574
84u71574
20 hours ago

hey man there seems to be a little problem with confeti getting stuck on the left os the screen, to be honest i dont know about shaders, but i passed it trough gemini and it gave me this, it seems to have resolve the issue:
shader_type canvas_item;

// Usage: 
// 1. Append a ColorRect (Full size)
// 2. Add this Shader to the ColorRect

const int PARTICLE_COUNT = 120;

uniform float  speed     : hint_range(20.0, 500.0) = 120.0;
uniform float  sway_strength : hint_range(0.0, 200.0) = 60.0;
uniform float  sway_speed  : hint_range(0.1, 5.0)  = 1.4;
uniform float  spin_speed  : hint_range(0.0, 10.0)  = 3.5;
uniform float  piece_size  : hint_range(2.0, 30.0)  = 10.0;
uniform float  aspect_ratio : hint_range(0.2, 3.0)  = 2.0;
uniform vec2  resolution  = vec2(1920.0, 1080.0);

uniform vec4  col0 : source_color = vec4(1.00, 0.20, 0.20, 1.0);
uniform vec4  col1 : source_color = vec4(1.00, 0.75, 0.10, 1.0);
uniform vec4  col2 : source_color = vec4(0.10, 0.80, 0.30, 1.0);
uniform vec4  col3 : source_color = vec4(0.15, 0.55, 1.00, 1.0);
uniform vec4  col4 : source_color = vec4(0.85, 0.20, 1.00, 1.0);
uniform vec4  col5 : source_color = vec4(1.00, 0.40, 0.70, 1.0);
uniform vec4  col6 : source_color = vec4(0.10, 0.90, 0.95, 1.0);
uniform vec4  col7 : source_color = vec4(1.00, 1.00, 1.00, 1.0);

// Trig-free random function to prevent precision loss clumps
float rand(float seed) {
  float x = fract((seed + 314.159) * 0.1031);
  x *= x + 33.33;
  return fract(x * x);
}

vec4 palette_color(float t) {
  int idx = int(t * 8.0) % 8;
  if (idx == 0) return col0;
  if (idx == 1) return col1;
  if (idx == 2) return col2;
  if (idx == 3) return col3;
  if (idx == 4) return col4;
  if (idx == 5) return col5;
  if (idx == 6) return col6;
  return col7;
}

vec2 rotate2d(vec2 v, float angle) {
  float s = sin(angle);
  float c = cos(angle);
  return vec2(c * v.x – s * v.y,
        s * v.x + c * v.y);
}

void fragment() {
  vec2 px = UV * resolution;
  vec4 out_color = vec4(0.0);

  for (int i = 0; i < PARTICLE_COUNT; i++) {
    float fi = float(i);

    float start_x = rand(fi * 1.234) * resolution.x;
     
    // Spawn strictly above the screen; t_offset handles the randomness
    float start_y = -piece_size * 4.0;
    float t_offset = rand(fi * 9.101) * (resolution.y + piece_size * 8.0) / speed;

    float my_speed   = speed     * (0.5 + rand(fi * 2.222));
    float my_sway_str  = sway_strength * (0.3 + rand(fi * 3.333) * 1.4);
    float my_sway_spd  = sway_speed  * (0.5 + rand(fi * 4.444) * 1.5);
    float my_sway_phase = rand(fi * 6.666) * TAU;
    float my_spin    = spin_speed  * (0.5 + rand(fi * 7.777) * 1.5)
               * (rand(fi * 8.888) > 0.5 ? 1.0 : -1.0);
    float my_size    = piece_size  * (0.5 + rand(fi * 0.123) * 1.0);
    float my_ar     = aspect_ratio * (0.7 + rand(fi * 0.456) * 0.6);

    // Modulo based on total travel distance ensuring they reach the bottom
    float travel_dist = resolution.y + piece_size * 8.0;
    float t   = mod(TIME + t_offset, travel_dist / my_speed);
    float cy  = start_y + t * my_speed;
    float cx  = start_x + my_sway_str * sin(t * my_sway_spd + my_sway_phase);
    float angle = t * my_spin;

    vec2 delta = px – vec2(cx, cy);
    delta = rotate2d(delta, -angle);
    vec2 local = delta / vec2(my_size * my_ar, my_size);

    float inside = step(max(abs(local.x), abs(local.y)), 1.0);

    if (inside > 0.5) {
      float shimmer = 0.5 + 0.5 * sin(t * my_spin * 2.0);
      vec4 base_col = palette_color(rand(fi * 0.321));
      vec4 lit_col = mix(base_col * 0.5, base_col, shimmer);
      lit_col.a   = 1.0;
      out_color = mix(out_color, lit_col, inside);
    }
  }

  COLOR = out_color;
}