God rays

An animated god ray effect that can be used in many ways. See animation below.

Uniforms

  • Angle – The angle of the rays.
  • Position – Move the rays left and right.
  • Spread – Should the rays be parallel or spread out.
  • Cut off – The width of the effect.
  • Fall off – Fade to transparent towards the bottom.
  • Edge fade – The edges can be sharp or blurry.
  • Speed – Speed of the “shimmer” animation.
  • Ray 1 density – The effect has two layers of rays. This is how compact the back layer is, e.g how close together the gaps are. For best effect have the Ray 1 be wide and Ray 2 more compact.
  • Ray 2 density – This is how compact the top layer is.
  • Ray 2 intensity – Opacity of the top layer.

The rays blend with the background with a ‘Screen’ blending mode but you can change this for a different result. See godotshaders.com/snippet/blending-modes/ to get code to other blending modes.

Instructions
This shader is all code, no noise textures. So just paste the code into a new shader for a Sprite node or a ColorRect. Sprite nodes need a base texture.

Shader code
/*
Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/god-rays

Feel free to use, improve and change this shader according to your needs
and consider sharing the modified result on godotshaders.com.
*/

shader_type canvas_item;

uniform float angle = -0.3;
uniform float position = -0.2;
uniform float spread : hint_range(0.0, 1.0) = 0.5;
uniform float cutoff : hint_range(-1.0, 1.0) = 0.1;
uniform float falloff : hint_range(0.0, 1.0) = 0.2;
uniform float edge_fade : hint_range(0.0, 1.0) = 0.15;

uniform float speed = 1.0;
uniform float ray1_density = 8.0;
uniform float ray2_density = 30.0;
uniform float ray2_intensity : hint_range(0.0, 1.0) = 0.3;

uniform vec4 color : hint_color = vec4(1.0, 0.9, 0.65, 0.8);

uniform bool hdr = false;
uniform float seed = 5.0;

// Random and noise functions from Book of Shader's chapter on Noise.
float random(vec2 _uv) {
    return fract(sin(dot(_uv.xy,
                         vec2(12.9898, 78.233))) *
        43758.5453123);
}

float noise (in vec2 uv) {
    vec2 i = floor(uv);
    vec2 f = fract(uv);

    // Four corners in 2D of a tile
    float a = random(i);
    float b = random(i + vec2(1.0, 0.0));
    float c = random(i + vec2(0.0, 1.0));
    float d = random(i + vec2(1.0, 1.0));


    // Smooth Interpolation

    // Cubic Hermine Curve. Same as SmoothStep()
    vec2 u = f * f * (3.0-2.0 * f);

    // Mix 4 coorners percentages
    return mix(a, b, u.x) +
            (c - a)* u.y * (1.0 - u.x) +
            (d - b) * u.x * u.y;
}

mat2 rotate(float _angle){
    return mat2(vec2(cos(_angle), -sin(_angle)),
                vec2(sin(_angle), cos(_angle)));
}

vec4 screen(vec4 base, vec4 blend){
	return 1.0 - (1.0 - base) * (1.0 - blend);
}

void fragment()
{
	
	// Rotate, skew and move the UVs
	vec2 transformed_uv = ( rotate(angle) * (UV - position) )  / ( (UV.y + spread) - (UV.y * spread) );
	
	// Animate the ray according the the new transformed UVs
	vec2 ray1 = vec2(transformed_uv.x * ray1_density + sin(TIME * 0.1 * speed) * (ray1_density * 0.2) + seed, 1.0);
	vec2 ray2 = vec2(transformed_uv.x * ray2_density + sin(TIME * 0.2 * speed) * (ray1_density * 0.2) + seed, 1.0);
	
	// Cut off the ray's edges
	float cut = step(cutoff, transformed_uv.x) * step(cutoff, 1.0 - transformed_uv.x);
	ray1 *= cut;
	ray2 *= cut;
	
	// Apply the noise pattern (i.e. create the rays)
	float rays;
	
	if (hdr){
		// This is not really HDR, but check this to not clamp the two merged rays making 
		// their values go over 1.0. Can make for some nice effect
		rays = noise(ray1) + (noise(ray2) * ray2_intensity);
	}
	else{
		 rays = clamp(noise(ray1) + (noise(ray2) * ray2_intensity), 0., 1.);
	}
	
	// Fade out edges
	rays *= smoothstep(0.0, falloff, (1.0 - UV.y)); // Bottom
	rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, transformed_uv.x); // Left
	rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, 1.0 - transformed_uv.x); // Right
	
	// Color to the rays
	vec3 shine = vec3(rays) * color.rgb;

	// Try different blending modes for a nicer effect. "Screen" is included in the code,
	// but take a look at https://godotshaders.com/snippet/blending-modes/ for more.
	// With "Screen" blend mode:
	shine = screen(texture(SCREEN_TEXTURE, SCREEN_UV), vec4(color)).rgb;
	
	COLOR = vec4(shine, rays * color.a);
}
Tags
effect, environment, light, shine
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.

More from pend00

2D waterfall

VHS and CRT monitor effect

2D mirror effect

Related shaders

Screen Space God Rays (Godot.4.3)

Subscribe
Notify of
guest

18 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Radastan
Radastan
3 years ago

it’s wonderful!!!!

esa
esa
3 years ago

Beautiful!

agrogers
agrogers
3 years ago

Great shader. Thank you.

Leonid
Leonid
3 years ago

Fabulous effect !! I noticed that when exporting to android, only one ray is visible. Here’s a video

Declivever
Declivever
3 years ago

Hello, used your shader in https://declivever.itch.io/wizard-jump

Jeno
Jeno
1 year ago

is it possible to update this lovely shader to Godot 4?

‘hint’ seems to no longer be supported so the shader doesnt work in Godot 4

Iliya
Iliya
1 year ago
Reply to  Jeno

Add near the top this:
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

Replace hint_color to source_color

and all should work fine

QuestlineDev
1 year ago
Nyaaa
Nyaaa
1 year ago

If someone is using this shader in Godot 4 Vulkan and is getting weird streaks, change the random function to the following:

float random(vec2 _uv) {
	_uv += min(TIME,0.0).
  return fract(sin(dot(_uv.xy.
             vec2(12.9898, 78.233))) *
    43758.5453123).
}

Thanks to https://github.com/godotengine/godot/issues/67150#issuecomment-1453442837

RoseBloom
RoseBloom
1 year ago

thank you very much!!Great shader

George
George
1 year ago

Beautiful. Thank you Thank you Thank you!!!

eymur
eymur
7 months ago

doesnt work on 4.2

Pygnosis
Pygnosis
6 months ago
Reply to  eymur

I use it on 4.2, it just needs some tweaking:

/*
Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/god-rays

Feel free to use, improve and change this shader according to your needs
and consider sharing the modified result on godotshaders.com.
*/

shader_type canvas_item;

uniform float angle = -0.3;
uniform float position = -0.2;
uniform float spread : hint_range(0.0, 1.0) = 0.5;
uniform float cutoff : hint_range(-1.0, 1.0) = 0.1;
uniform float falloff : hint_range(0.0, 1.0) = 0.2;
uniform float edge_fade : hint_range(0.0, 1.0) = 0.15;


uniform float speed = 1.0;
uniform float ray1_density = 8.0;
uniform float ray2_density = 30.0;
uniform float ray2_intensity : hint_range(0.0, 1.0) = 0.3;

uniform vec4 color : source_color = vec4(1.0, 0.9, 0.65, 0.8);

uniform bool hdr = false;
uniform float seed = 5.0;

// Backward compatibility
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

// Random and noise functions from Book of Shader's chapter on Noise.
float random(vec2 _uv) {
   return fract(sin(dot(_uv.xy,
                        vec2(12.9898, 78.233))) *
       43758.5453123);
}

float noise (in vec2 uv) {
   vec2 i = floor(uv);
   vec2 f = fract(uv);

   // Four corners in 2D of a tile
   float a = random(i);
   float b = random(i + vec2(1.0, 0.0));
   float c = random(i + vec2(0.0, 1.0));
   float d = random(i + vec2(1.0, 1.0));


   // Smooth Interpolation

   // Cubic Hermine Curve. Same as SmoothStep()
   vec2 u = f * f * (3.0-2.0 * f);

   // Mix 4 coorners percentages
   return mix(a, b, u.x) +
           (c - a)* u.y * (1.0 - u.x) +
           (d - b) * u.x * u.y;
}

mat2 rotate(float _angle){
   return mat2(vec2(cos(_angle), -sin(_angle)),
               vec2(sin(_angle), cos(_angle)));
}

vec4 screen(vec4 base, vec4 blend){
   return 1.0 - (1.0 - base) * (1.0 - blend);
}

void fragment()
{

   // Rotate, skew and move the UVs
   vec2 transformed_uv = ( rotate(angle) * (UV - position) ) / ( (UV.y + spread) - (UV.y * spread) );

   // Animate the ray according the the new transformed UVs
   vec2 ray1 = vec2(transformed_uv.x * ray1_density + sin(TIME * 0.1 * speed) * (ray1_density * 0.2) + seed, 1.0);
   vec2 ray2 = vec2(transformed_uv.x * ray2_density + sin(TIME * 0.2 * speed) * (ray1_density * 0.2) + seed, 1.0);

   // Cut off the ray's edges
   float cut = step(cutoff, transformed_uv.x) * step(cutoff, 1.0 - transformed_uv.x);
   ray1 *= cut;
   ray2 *= cut;

   // Apply the noise pattern (i.e. create the rays)
   float rays;

   if (hdr){
      // This is not really HDR, but check this to not clamp the two merged rays making
      // their values go over 1.0. Can make for some nice effect
      rays = noise(ray1) + (noise(ray2) * ray2_intensity);
   }
   else{
      rays = clamp(noise(ray1) + (noise(ray2) * ray2_intensity), 0., 1.);
   }

   // Fade out edges
   rays *= smoothstep(0.0, falloff, (1.0 - UV.y)); // Bottom
   rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, transformed_uv.x); // Left
   rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, 1.0 - transformed_uv.x); // Right

   // Color to the rays
   vec3 shine = vec3(rays) * color.rgb;

   // Try different blending modes for a nicer effect. "Screen" is included in the code,
   // but take a look at https://godotshaders.com/snippet/blending-modes/ for more.
   // With "Screen" blend mode:
   shine = screen(texture(SCREEN_TEXTURE, SCREEN_UV), vec4(color)).rgb;

   COLOR = vec4(shine, rays * color.a);
}

Joshua Hansberg
Joshua Hansberg
4 months ago
Reply to  Pygnosis

Ty so much for OP and your revision as well. this is a great shader.

Since visibility.modulate() doesnt work on shaders in Godot 4 anymore, would it be possible to include a parameter in the shader to adjust the alpha channel? Right now you can use something like soft_light or lighten which looks wonderful but a separate possibility to adjust alpha indendently would be great.

RomChatskyu Roman
RomChatskyu Roman
4 months ago

uniform vec4 color : hint_color = vec4(1.0, 0.9, 0.65, 0.8);

expected a valid type after ‘:’

Snailestial
Snailestial
3 months ago

Thank you for sharing this great shader. Here is a couple modifications I made, for anyone else wondering the same things I was.

I was noticing a sharp line in the beams. If you want to remove it, comment out or remove these lines:

float cut = step(cutoff, transformed_uv.x) * step(cutoff, 1.0 - transformed_uv.x);
ray1 *= cut;
ray2 *= cut;

Also I wanted fade out the top of the beams for isometric. You can do this by changing the fade section to:

rays *= smoothstep(0.0, falloff, (1.0 - UV.y)); // Bottom
rays *= smoothstep(0.0, falloff, (UV.y)); // Top
rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, transformed_uv.x); // Left
rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, 1.0 - transformed_uv.x); // Right
silverfr0st
silverfr0st
11 days ago

I combined the 4.x fixes and the random function fix, changed the parameter types to sliders for quick adjustments, added a parameter to quickly select the blending method and changed the function accordingly, and set the angle to degrees instead of radians.

My full code looks like this:

/*
Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/god-rays

Feel free to use, improve and change this shader according to your needs
and consider sharing the modified result on godotshaders.com.
*/

shader_type canvas_item;

uniform int blend_mode : hint_range(1, 13) = 1;

uniform float angle : hint_range(0.0, 360.0) = 30;
uniform float position : hint_range(-1.0, 1.0) = -0.2;
uniform float spread : hint_range(0.0, 1.0) = 0.5;
uniform float cutoff : hint_range(-1.0, 1.0) = 0.1;
uniform float falloff : hint_range(0.0, 1.0) = 0.2;
uniform float edge_fade : hint_range(0.0, 1.0) = 0.15;

uniform float speed : hint_range(0.0, 20.0) = 1.0;
uniform float ray1_density : hint_range(0.0, 20.0) = 8.0;
uniform float ray2_density : hint_range(0.0, 50.0) = 30.0;
uniform float ray2_intensity : hint_range(0.0, 1.0) = 0.3;

uniform vec4 color : source_color = vec4(1.0, 0.9, 0.65, 0.8);

uniform bool hdr = false;
uniform float seed = 5.0;

// Backward compatibility
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

// Random and noise functions from Book of Shader's chapter on Noise.
float random(vec2 _uv) {
    _uv += min(TIME,0.0);
    return fract(sin(dot(_uv.xy, vec2(12.9898, 78.233))) * 43758.5453123);
}

float noise (in vec2 uv) {
   vec2 i = floor(uv);
   vec2 f = fract(uv);

   // Four corners in 2D of a tile
   float a = random(i);
   float b = random(i + vec2(1.0, 0.0));
   float c = random(i + vec2(0.0, 1.0));
   float d = random(i + vec2(1.0, 1.0));

   // Smooth Interpolation
   // Cubic Hermine Curve. Same as SmoothStep()
   vec2 u = f * f * (3.0-2.0 * f);

   // Mix 4 coorners percentages
   return mix(a, b, u.x) +
           (c - a)* u.y * (1.0 - u.x) +
           (d - b) * u.x * u.y;
}

mat2 rotate(float _angle){
   _angle = radians(_angle);
   return mat2(vec2(cos(_angle), -sin(_angle)),
               vec2(sin(_angle), cos(_angle)));
}

vec4 blend(vec4 base, vec4 blend, int mode) {
    switch (mode) {
        case 1: //Screen
            return 1.0 - (1.0 - base) * (1.0 - blend);
        case 2: //Multiply
            return base * blend;
        case 3: //Darken
            return min(base, blend);
        case 4: //Lighten
            return max(base, blend);
        case 5: //Difference
            return abs(base - blend);
        case 6: //Exclusion
            return base + blend - 2.0 * base * blend;
        case 7: //Overlay
            vec4 limit = step(0.5, base);
            return mix(2.0 * base * blend, 1.0 - 2.0 * (1.0 - base) * (1.0 - blend), limit);
        case 8: //Hard light
            vec4 limit = step(0.5, blend);
            return mix(2.0 * base * blend, 1.0 - 2.0 * (1.0 - base) * (1.0 - blend), limit);
        case 9: //Soft light
            vec4 limit = step(0.5, blend);
            return mix(2.0 * base * blend + base * base * (1.0 - 2.0 * blend), sqrt(base) * (2.0 * blend - 1.0) + (2.0 * base) * (1.0 - blend), limit);
        case 10: //Color dodge
            return base / (1.0 - blend);
        case 11: //Linear dodge
            return base + blend;
        case 12: //Color burn
            return 1.0 - (1.0 - base) / blend;
        case 13: //Linear burn
            return base + blend - 1.0;
    }
}

void fragment()
{
   // Rotate, skew and move the UVs
   vec2 transformed_uv = ( rotate(angle) * (UV - position) ) / ( (UV.y + spread) - (UV.y * spread) );

   // Animate the ray according the the new transformed UVs
   vec2 ray1 = vec2(transformed_uv.x * ray1_density + sin(TIME * 0.1 * speed) * (ray1_density * 0.2) + seed, 1.0);
   vec2 ray2 = vec2(transformed_uv.x * ray2_density + sin(TIME * 0.2 * speed) * (ray1_density * 0.2) + seed, 1.0);

   // Cut off the ray's edges
   float cut = step(cutoff, transformed_uv.x) * step(cutoff, 1.0 - transformed_uv.x);
   ray1 *= cut;
   ray2 *= cut;

   // Apply the noise pattern (i.e. create the rays)
   float rays;

   if (hdr){
      // This is not really HDR, but check this to not clamp the two merged rays making
      // their values go over 1.0. Can make for some nice effect
      rays = noise(ray1) + (noise(ray2) * ray2_intensity);
   }
   else{
      rays = clamp(noise(ray1) + (noise(ray2) * ray2_intensity), 0., 1.);
   }

   // Fade out edges
   rays *= smoothstep(0.0, falloff, (1.0 - UV.y)); // Bottom
   rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, transformed_uv.x); // Left
   rays *= smoothstep(0.0 + cutoff, edge_fade + cutoff, 1.0 - transformed_uv.x); // Right

   // Color to the rays
   vec3 shine = vec3(rays) * color.rgb;

   // Try different blending modes for a nicer effect. "Screen" is included in the code,
   // but take a look at https://godotshaders.com/snippet/blending-modes/ for more.
   // With "Screen" blend mode:
   shine = blend(texture(SCREEN_TEXTURE, SCREEN_UV), vec4(color), blend_mode).rgb;

   COLOR = vec4(shine, rays * color.a);
}