Energy Beams

A versatile shader to make energy beams or lasers. Plenty of uniforms to play around with. Includes a “Progress” uniform to use if you want to animate the beams on or off (this will smoothly shrink and dissolve the beams which make for a nicer effect).

🎥 Animation below!

Uniforms
Beams – How many beams the energy field should have
Energy – How energetic the beams will seem, how they will travel up and down
Roughness – How compact the noise texture will be
Frequency – Amount of “ripples” in the beams
Speed – Animation speed

Thickness – Thickness of the main beam
Outline Thickness – Thickness of the outline color
Beam Difference – The thickness difference between the main beam and the others, if there is more than one beam. The closer to 1 the smaller the thickness difference.

Glow/Outline Glow – HDR glow. Use together with WorldEnvironment’s Glow feature

Color – Color of beams
Outline Color – Color of outline

Progress – Use this to animate the beam on or off.

Instructions
Simply add the shader code to a ColorRect. If you want to compress the size of the ColorRect use noise_scale to compensate or the noise will be very squeezed.

Glow – By default the shader uses “blend_add” render mode which creates a glow effect. If you instead want to use HDR glow with the WorldEnvironment Glow feature, remove render_mode blend_add on line 11 and use the Glow/Outline Glow parameters to set glow amount. 

Shader code
/*
Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/energy-beams

This shader is under CC0 license. Feel free to use, improve and 
change this shader according to your needs and consider sharing 
the modified result to godotshaders.com.
*/

shader_type canvas_item;
render_mode blend_add; // Remove this if you want to use HDR glow instead (use "Glow" and "Outline Glow" sliders)

uniform int beams = 2; // How many beams the energy field should have

uniform float energy = 3.0; // How much the beams will travel up and down
uniform int roughness : hint_range(1, 10) = 3; // How compact the noise texture will be
uniform int frequency = 10; // Amount of "ripples" in the beams

uniform float speed = 1.0; // Animation speed
uniform float thickness : hint_range(0.0, 0.1) = 0.006; // Thickness of the main beam
uniform float outline_thickness : hint_range(0.0, 0.1) = 0.03; //Thickness of the outline color
uniform float beam_difference : hint_range(0.0, 1.0) = 0.0; // The thickness difference between the main beam and the other, if there are more than one beam. The closer to 1 the smaller the thickness difference.

uniform float glow : hint_range(0.0, 3.0) = 0.0; // Use together with WorldEnvironment's Glow feature
uniform float outline_glow : hint_range(0.0, 3.0) = 0.0;

uniform vec4 color : hint_color = vec4(0.91, 1.0, 1.0, 1.0);
uniform vec4 outline_color : hint_color = vec4(0.5, 1.0, 0.96, 1.0);

uniform float progress : hint_range(0.0, 1.0) = 1.0;

uniform float y_offset : hint_range (-0.5, 0.5) = 0.0; // Position of the beam
uniform float fixed_edge_size : hint_range(0.0, 0.5) = 0.05; // How close to the edge should the beam be still before the animatino starts
uniform vec2 noise_scale = vec2(1.0); // If the object (for example the ColorRect or Sprite node) is compressed use this to compensate for the noise texture being compressed.

float random(vec2 uv) {
    return fract(sin(dot(uv.xy,
        vec2(12.9898,78.233))) *
            43758.5453123);
}

float noise(vec2 uv) {
    vec2 uv_index = floor(uv);
    vec2 uv_fract = fract(uv);

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

    vec2 blur = smoothstep(0.0, 1.0, uv_fract);

    return mix(a, b, blur.x) +
            (c - a) * blur.y * (1.0 - blur.x) +
            (d - b) * blur.x * blur.y;
}

float fbm(vec2 uv, float time) {
    int octaves = roughness;
    float amp = 0.01 * energy * progress;
    float freq = float(frequency);
	float value = 0.0;
	
    for(int i = 0; i < octaves; i++) {
        value += amp * noise(freq * vec2(uv.x, uv.y + time));
        amp *= 0.5;
        freq *= 2.0;
    }
    return value;
}

vec4 difference(vec4 base, vec4 blend){
	return abs(base - blend);
}

vec4 bolt(vec2 uv, float time, float i)
{
	// Setup the beam locking to the edges.
	float falloff = smoothstep(0.0, fixed_edge_size, uv.x) * smoothstep(0.0, fixed_edge_size, 1.0 - uv.x);
	
	// Use Fractal Brownian Motion to create a "cloud texture" and use Difference blend mode to make the beam
	vec4 clouds = vec4(fbm((uv + vec2(i) ) * noise_scale, time * speed)) * falloff;
	vec4 diff_clouds = difference(clouds, vec4(uv.y - 0.5 + y_offset + (uv.y * falloff * 0.02 * energy * progress)));
	
	// Create a new noise to mask the beams on low "progress" values. To make a "turn-off" effect more visually interesting.
	vec4 clouds2 = vec4(fbm((uv * 2.0) * noise_scale, time * 1.)) * 5.0;
	diff_clouds += smoothstep(0.0, 0.8, clouds2) * 0.1 * (1.-progress);
	
	// Set thickness of the beams. First beam is the Thickness size and all following beams are sized with beam_difference
	float thickness2 =  1. - ( thickness / (min(i + beam_difference, 1.0) + (1.0-beam_difference))) * progress ;
	vec4 beam = clamp(smoothstep(thickness2, thickness2 + 0.005 * progress, 1.0 - diff_clouds), vec4(0.0), vec4(1.0));
	
	//Set the beam outlines
	vec4 beam_outline;
	float outline = thickness2 - (outline_thickness * progress);
	beam_outline = clamp(smoothstep(outline, outline + 0.04, 1.0 - diff_clouds), 0.0, 1.0);
	beam_outline = clamp(beam_outline - beam, 0.0, 1.0);
	
	// Merge the beam and the outline and return to the fragment function
	return (beam * (color + vec4(glow, glow, glow, 0.))) + (beam_outline * (outline_color + vec4(outline_glow, outline_glow, outline_glow, 0.)));
}

void fragment()
{	
	vec4 beam = vec4(0.0);
	
	for (int i = 0; i < beams; i++){
		beam = max(beam, bolt(UV, TIME, float(i)));
	}
	
	COLOR = beam;
}
Tags
energy, field, laser, toon
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

God rays

2D waterfall

Brick/tiled wall

Related shaders

Simple Energy Shield

Simple Energy Shield

Energy shield with impact effect

Subscribe
Notify of
guest

14 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Apple0726
Apple0726
3 years ago

Hey, cool shader! Though when I try to use it, I see graphical glitches. Is it a GPU problem (I have an integrated GPU) or is it something I can do in Godot?
comment image

Alejandro
Alejandro
3 years ago
Reply to  pend00

Me too, is like is broken in pieces. Using gles2
https://imgur.com/Z4gfuUC

Chris
Chris
3 years ago
Reply to  Alejandro

It looks like an issue with noise texture resolution. You can see full integers in those pieces, this is why interpolation between random values is too stiff. Try to experiment with fbm() and random(), multiply its UV by something higher

mroberti
3 years ago

OMFG. This is EXACTLY WHAT I NEEDED AND WANTED!!! Thanks so much for making this awesome shader!!!

mroberti
3 years ago

Awwwww, I don’t know how to make it appear from one point on-screen to another point on-screen. Like I have my ships flying around, and I was trying to use this shader on a line2d, so I could set the point[0] as the source ship and the point[1] as the target ship. Is there a way to do it with a colored rect?

FreakyFriends
FreakyFriends
2 years ago

Hey Pend00, awesome work on the shader! How would you make the beam get thinner towards one or two of the ends? With the fixed_edge_size we can have the beam be still towards the ends, but I’m trying to make it so the beam also gets thinner. Any ideas?

ksvslk
ksvslk
2 years ago

It is awesome but on Android for example it stops working after 2 minutes. Performance issues?

Calinou
4 months ago
Reply to  ksvslk

You need to decrease Time Rollover Secs in the Project Settings to avoid visible precision issues on mobile platforms, as mobile GPUs use lower shader precision by default. When targeting mobile, try using a value of 32 or lower (floating-point precision is halved at every power of two value).

lyghtkruz
lyghtkruz
2 years ago

For anyone else having the issue with the segments, it looks like the fbm code is changing very harshly like Chris mentioned. I copied the perlin noise code and used that random and noise function instead and it fixed my problem https://godotshaders.com/snippet/2d-noise/

I’m using Godot 4, change hint_color to source_color and try the perlin noise.

Tanders
2 years ago
Reply to  lyghtkruz

Sharing the actual code with perlin (For Godot 4):

/*
Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/energy-beams

This shader is under CC0 license. Feel free to use, improve and 
change this shader according to your needs and consider sharing 
the modified result to godotshaders.com.
*/

shader_type spatial;
render_mode blend_add; // Remove this if you want to use HDR glow instead (use "Glow" and "Outline Glow" sliders)

uniform int beams = 2; // How many beams the energy field should have

uniform float energy = 3.0; // How much the beams will travel up and down
uniform int roughness : hint_range(1, 10) = 3; // How compact the noise texture will be
uniform int frequency = 10; // Amount of "ripples" in the beams

uniform float speed = 1.0; // Animation speed
uniform float thickness : hint_range(0.0, 0.1) = 0.006; // Thickness of the main beam
uniform float outline_thickness : hint_range(0.0, 0.1) = 0.03; //Thickness of the outline color
uniform float beam_difference : hint_range(0.0, 1.0) = 0.0; // The thickness difference between the main beam and the other, if there are more than one beam. The closer to 1 the smaller the thickness difference.

uniform float glow : hint_range(0.0, 3.0) = 0.0; // Use together with WorldEnvironment's Glow feature
uniform float outline_glow : hint_range(0.0, 3.0) = 0.0;

uniform vec4 color : source_color = vec4(0.91, 1.0, 1.0, 1.0);
uniform vec4 outline_color : source_color = vec4(0.5, 1.0, 0.96, 1.0);

uniform float progress : hint_range(0.0, 1.0) = 1.0;

uniform float y_offset : hint_range (-0.5, 0.5) = 0.0; // Position of the beam
uniform float fixed_edge_size : hint_range(0.0, 0.5) = 0.05; // How close to the edge should the beam be still before the animatino starts
uniform vec2 noise_scale = vec2(1.0); // If the object (for example the ColorRect or Sprite node) is compressed use this to compensate for the noise texture being compressed.

float random(vec2 uv) {
   return fract(sin(dot(uv.xy,
       vec2(12.9898,78.233))) *
           43758.5453123);
}

vec2 randomVec2(vec2 uv){
   uv = vec2( dot(uv, vec2(127.1,311.7) ),
              dot(uv, vec2(269.5,183.3) ) );
   return -1.0 + 2.0 * fract(sin(uv) * 43758.5453123);
}

float noise(vec2 uv) {
   vec2 uv_index = floor(uv);
   vec2 uv_fract = fract(uv);
   vec2 blur = smoothstep(0.0, 1.0, uv_fract);
   return mix( mix( dot( randomVec2(uv_index + vec2(0.0,0.0) ), uv_fract - vec2(0.0,0.0) ),
                    dot( randomVec2(uv_index + vec2(1.0,0.0) ), uv_fract - vec2(1.0,0.0) ), blur.x),
               mix( dot( randomVec2(uv_index + vec2(0.0,1.0) ), uv_fract - vec2(0.0,1.0) ),
                    dot( randomVec2(uv_index + vec2(1.0,1.0) ), uv_fract - vec2(1.0,1.0) ), blur.x), blur.y) + 0.5;
}


float fbm(vec2 uv, float time) {
   int octaves = roughness;
   float amp = 0.01 * energy * progress;
   float freq = float(frequency);
   float value = 0.0;

   for(int i = 0; i < octaves; i++) {
       value += amp * noise(freq * vec2(uv.x, uv.y + time));
       amp *= 0.5;
       freq *= 2.0;
   }
   return value;
}

vec4 difference(vec4 base, vec4 blend){
   return abs(base - blend);
}

vec4 bolt(vec2 uv, float time, float i)
{
   // Setup the beam locking to the edges.
   float falloff = smoothstep(0.0, fixed_edge_size, uv.x) * smoothstep(0.0, fixed_edge_size, 1.0 - uv.x);

   // Use Fractal Brownian Motion to create a "cloud texture" and use Difference blend mode to make the beam
   vec4 clouds = vec4(fbm((uv + vec2(i) ) * noise_scale, time * speed)) * falloff;
   vec4 diff_clouds = difference(clouds, vec4(uv.y - 0.5 + y_offset + (uv.y * falloff * 0.02 * energy * progress)));

   // Create a new noise to mask the beams on low "progress" values. To make a "turn-off" effect more visually interesting.
   vec4 clouds2 = vec4(fbm((uv * 2.0) * noise_scale, time * 1.)) * 5.0;
   diff_clouds += smoothstep(0.0, 0.8, clouds2) * 0.1 * (1.-progress);

   // Set thickness of the beams. First beam is the Thickness size and all following beams are sized with beam_difference
   float thickness2 = 1. - ( thickness / (min(i + beam_difference, 1.0) + (1.0-beam_difference))) * progress ;
   vec4 beam = clamp(smoothstep(thickness2, thickness2 + 0.005 * progress, 1.0 - diff_clouds), vec4(0.0), vec4(1.0));

   //Set the beam outlines
   vec4 beam_outline;
   float outline = thickness2 - (outline_thickness * progress);
   beam_outline = clamp(smoothstep(outline, outline + 0.04, 1.0 - diff_clouds), 0.0, 1.0);
   beam_outline = clamp(beam_outline - beam, 0.0, 1.0);

   // Merge the beam and the outline and return to the fragment function
   return (beam * (color + vec4(glow, glow, glow, 0.))) + (beam_outline * (outline_color + vec4(outline_glow, outline_glow, outline_glow, 0.)));
}

void vertex()
{
//   MODELVIEW_MATRIX = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0],CAMERA_MATRIX[1],CAMERA_MATRIX[2],WORLD_MATRIX[3]);
}

void fragment()
{   
   vec4 beam = vec4(0.0);

   for (int i = 0; i < beams; i++){
      beam = max(beam, bolt(UV, TIME, float(i)));
   }

   ALBEDO = beam.rgb;
   ALPHA = beam.a;
}
Tanders
2 years ago
Reply to  Tanders

And this is for using it on a spatial, change

   ALBEDO = beam.rgb;
   ALPHA = beam.a;

to

COLOR = beam;

and the shader type at top

tennokoi
tennokoi
1 year ago

Very cool sir.

glungus
7 months ago

Awesome shader!

For Godot 4, change hint_color to source_color

Last edited 7 months ago by glungus