Thunder Lightning Strike Shader for Godot

lightning effect vfx in godot, thunder shader

Originally based on this shader for 2D lightning. I added some more controls such as thickness and modified it to add alpha on areas other than lightning.

Breakdown

  1. The vertex shader creates a billboard so the quad always faces the camera.
  2. We first rotate the UVs to the direction where we want to apply lighting to.
  3. Then UVs are distorted by the noise function.
  4. Colors are applied to the portion of UV, and are scaled by the distance factor. thickness basically influences the distance factor of how ‘far’ the lightning color to be applied on distorted UV.

The full code is at the end of the post. Here I’m explaining some important parts of it.

Main part

uv += 2.0 * fbm(uv + TIME * speed, octave_count) - 1.0;: This line distorts the UV coordinates using a Perlin noise (fbm).

float dist = abs(uv.x) / thickness;: This line calculates the distance from the center of the lightning. The abs() of x-axis is taken to ensure that the distance is always positive on both sides of center, so color values can get multiplied to create lightning. The calculated distance is then divided by the thickness parameter to stretch the distance so lightning can get thicker.

vec3 color = effect_color * mix(0.0, 0.05, hash12(vec2(TIME))) / dist;: The effect_color parameter specifies the base color of the lightning. The mix function is used to blend between 0 and 0.05 based on a hash function of the time. This adds a random element to the color at different places. The final color is then divided by the distance calculated earlier.

COLOR.a = mix(0.0, 0.05, hash12(vec2(TIME))) / dist;: The same blending and distance calculation are used as for the color. But when assignwed to ALPHA, the darker part gets transparent and the lightning part gets opaque so only lightning shows.

Finally, I just intensified the ALPHA values using pow() function so it gets more neat.

Other things

fbm() function is just a noise function. We can take a noise texture as uniform instead of calculating it via code, but since the original author already made it, I am too lazy to change what is working.

And the UVs are scaled from 0…1 to -1…1 space in this line: vec2 uv = 2.0 * UV - 1.0;, so now 0.0 represents the center of UV instead of 0.5 being the center of UV.

Full Code


shader_type spatial;

render_mode unshaded;

uniform vec3 effect_color: source_color = vec3(0.2, 0.3, 0.8);
uniform int octave_count: hint_range(1, 20) = 10;
uniform float amp_start = 0.5;
uniform float amp_coeff = 0.5;
uniform float freq_coeff = 2.0;
uniform float speed = 0.5;
uniform vec2 direction = vec2(0.0, 1.0); // New uniform for direction


uniform float thickness = 1.0;
uniform bool billboard = false;

float hash12(vec2 x) {
    return fract(cos(mod(dot(x, vec2(13.9898, 8.141)), 3.14)) * 43758.5453);
}

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

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

float fbm(vec2 uv, int octaves) {
    float value = 0.0;
    float amplitude = amp_start;
    for (int i = 0; i < octaves; i++) {
        value += amplitude * noise(uv);
        uv *= freq_coeff;
        amplitude *= amp_coeff;
    }
    return value;
}

void vertex(){
	if(billboard){
		    mat4 modified_model_view = VIEW_MATRIX * mat4(
        INV_VIEW_MATRIX[0],
        INV_VIEW_MATRIX[1],
        INV_VIEW_MATRIX[2],
        MODEL_MATRIX[3]
    );
    MODELVIEW_MATRIX = modified_model_view;
	}
}

void fragment() {
    vec2 uv = 2.0 * UV - 1.0;

    // Normalize the direction and use it to transform the UV coordinates
    vec2 norm_direction = normalize(direction);
    uv = mat2(
		vec2(norm_direction.x, -norm_direction.y),
		vec2(norm_direction.y, norm_direction.x)
		) * uv;

    uv += 2.0 * fbm(uv + TIME * speed, octave_count) - 1.0;
    
    float dist = abs(uv.x) / thickness;
    vec3 color = effect_color * mix(0.0, 0.05, hash12(vec2(TIME))) / dist;
    ALBEDO = color;
	
	ALPHA = mix(0.0, 0.05, hash12(vec2(TIME))) / dist;
	ALPHA = pow(ALPHA, 4.0);
}

Thank you for reading <3

Leave a Reply

Your email address will not be published. Required fields are marked *