Help produce lighting in a arc/circle

Godot Version

4.4.1 mono

Question

So I’m really new to game dev and shaders. I was trying to make a cool energy shield that kind of has an electric crackle. I followed some shader youtube videos and was playing around trying to trying to mix a basic shield with a electric discharge and i managed to poorly combine the effects(see shader code below.)

In my first attempt I know I messed up the mix and step functions and it just overlaps the effect. Which is not exactly what I want. I’m trying to get the electric discharge to circle the shield outline.

So my second attempt I managed to sort of draw a wavy circle and I can make it black or white by inverting the lighting colour and I can tweek the different raduis to get smaller rings or bigger ones or ones that rotate in opposite directions. But I can’t figure out the math for the correct distance calculation for the lightning colour.

In the original shader the ligntnig can be flipped by changing the dist to use uv X or Y but I don’t understand the math enough figure how to get similar values for dist based on a combination of the x and y to make it diagonal let alone how to make it produce the lighting in a arc/circle

Sheild Code
shader_type canvas_item;

uniform float scale = 25.0;
uniform sampler2D SCREEN_TEXURE : hint_screen_texture, filter_nearest;

void vertex() {
	// Called for every vertex the material is visible on.
}

void fragment() {
	float time_factor = TIME*1.1;
	vec2 modified_UV = UV - .5;
	vec2 local_uv = 1.1 * sin(time_factor) * cos(time_factor) * vec2(fract((modified_UV * length(modified_UV)) * scale))-.5;
	
	
	float large_distance = length(modified_UV);
	float grid_distance = length(local_uv);
	float grid_alpha = smoothstep(.1, .85, grid_distance) * smoothstep(.5, .44, large_distance)
		* smoothstep(.2, .5, large_distance);
	
	float uv_height = SCREEN_PIXEL_SIZE.y / TEXTURE_PIXEL_SIZE.y;
	vec2 shield_uv = vec2(SCREEN_UV.x, SCREEN_UV.y + uv_height * UV.y);
	
	vec3 final_colour = mix(vec3(0, 1.0, 1.0), texture(SCREEN_TEXURE, shield_uv).rgb, 0.8);
	float final_alpha = grid_alpha * 2.0;
		
	COLOR = vec4(final_colour, final_alpha);
}

//void light() {
//	// Called for every pixel for every light affecting the CanvasItem.
//	// Uncomment to replace the default light processing function with this one.
//}

Electric arc
shader_type canvas_item;

uniform vec3 lightning_color: source_color= vec3(.2,.3,.8);
uniform int octave_count: hint_range(0, 20) = 10;

uniform float amp_start=0.5;
uniform float amp_coeff=0.5;
uniform float freq_coeff=2.0;
uniform float speed = .5;

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.5453) - 1.0;
}

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

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

void vertex() {
	// Called for every vertex the material is visible on.
}

uniform sampler2D SCREEN_TEXURE : hint_screen_texture, filter_nearest;
void fragment() {
	vec2 uv = 2.0 * UV - 1.0;
	//uv = vec2(circle(uv, 4));
	uv += 2.0 * fbm(uv + TIME * speed, octave_count) - 1.0;
	float dist = abs(uv.x);
	vec3 colour = lightning_color * mix(0.0, 0.05, hash12(vec2(TIME))) / dist;
	//colour = vec3(1.0-colour);
	
	COLOR = vec4(colour, 1.0);
	
}

//void light() {
//	// Called for every pixel for every light affecting the CanvasItem.
//	// Uncomment to replace the default light processing function with this one.
//}

First attempt to combine

``shader_type canvas_item;

uniform float scale = 25.0;
uniform sampler2D SCREEN_TEXURE : hint_screen_texture, filter_nearest;

uniform vec3 lightning_color: source_color= vec3(.2,.3,.8);
uniform int octave_count: hint_range(0, 20) = 10;

uniform float amp_start=0.5;
uniform float amp_coeff=0.5;
uniform float freq_coeff=2.0;
uniform float speed = .5;

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.5453) - 1.0;
}

float noise(vec2 uv)
{
vec2 luv =floor(uv);
vec2 fuv = fract(uv);
vec2 blur = smoothstep(0., 1., fuv);

return mix( mix(dot(hash22(luv + vec2(0.0, 0.0)), fuv - vec2(0.0, 0.0)),
				dot(hash22(luv + vec2(1.0, 0.0)), fuv - vec2(1.0, 0.0)), blur.x),
			mix(dot(hash22(luv + vec2(0.0, 1.0)), fuv - vec2(0.0, 1.0)),
				dot(hash22(luv + vec2(1.0, 1.0)), fuv - vec2(1.0, 1.0)), blur.x), blur.y) + 0.5;

}
//float noise(vec2 uv)
//{
//return sin(uv.x + uv.y);
//}

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

void vertex() {
// Called for every vertex the material is visible on.
}

vec4 lightningColour(vec2 uv)
{
uv = 2.0 * uv -1.0;
uv += 2.0 * fbm(uv + TIME * speed, octave_count) - 1.0;
float dist = abs(uv.x);
vec3 colour = lightning_color * mix(0.0, 0.05, hash12(vec2(TIME))) / dist;
//colour = vec3(1.0-colour);

return vec4(colour, 1.0);

}

void fragment() {
vec4 lightning_mask = lightningColour(UV);

float time_factor = TIME*1.1;
vec2 modified_UV = UV - .5;
vec2 local_uv = 1.1 * sin(time_factor) * cos(time_factor) * vec2(fract((modified_UV * length(modified_UV)) * scale))-.5;


float large_distance = length(modified_UV);
float grid_distance = length(local_uv);
float grid_alpha =  smoothstep(.1, .85, grid_distance) * smoothstep(.5, .44, large_distance)
	* smoothstep(.2, .5, large_distance);
grid_alpha = smoothstep(lightning_mask.r, 1.0, grid_alpha);

float uv_height = SCREEN_PIXEL_SIZE.y / TEXTURE_PIXEL_SIZE.y;
vec2 shield_uv = vec2(SCREEN_UV.x, SCREEN_UV.y + uv_height * UV.y);

vec3 final_colour = mix(vec3(0, 1.0, 1.0), texture(SCREEN_TEXURE, shield_uv).rgb, 0.8);
float final_alpha = grid_alpha * 2.0;

final_colour = step( final_colour, lightning_color.rgb);
	
COLOR = vec4(final_colour, final_alpha);

}

//void light() {
// // Called for every pixel for every light affecting the CanvasItem.
// // Uncomment to replace the default light processing function with this one.
//}
`

Second attempt to combine
shader_type canvas_item;

uniform vec3 lightning_color: source_color= vec3(.2,.3,.8);
uniform int octave_count: hint_range(0, 20) = 10;

uniform float amp_start=0.5;
uniform float amp_coeff=0.5;
uniform float freq_coeff=2.0;
uniform float speed = .5;

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.5453) - 1.0;
}

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

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

void vertex() {
	// Called for every vertex the material is visible on.
}

uniform sampler2D SCREEN_TEXURE : hint_screen_texture, filter_nearest;
void fragment() {
	//vec2 uv = 2.0 * UV - 1.0;
	////uv = vec2(circle(uv, 4));
	//uv += 2.0 * fbm(uv + TIME * speed, octave_count) - 1.0;
	//float dist = abs(uv.y);
	//vec3 colour = lightning_color * mix(0.0, 0.05, hash12(vec2(TIME))) / dist;
	////colour = vec3(1.0-colour);
	//
	//COLOR = vec4(colour, 1.0);
	
	vec2 uv = 2.0 * UV - 1.0;
	//uv = vec2(circle(uv, 4));
	uv += 2.0 * fbm(uv + TIME * speed, octave_count) - 1.0;
	float dist = abs(uv.x);
	//colour = vec3(1.0-colour);
	
	
	//Calculate distance from center (0.5, 0.5)
	float uv_height = SCREEN_PIXEL_SIZE.y / TEXTURE_PIXEL_SIZE.y;
	vec2 shield_uv = vec2(SCREEN_UV.x, SCREEN_UV.y + uv_height * UV.y);
    vec4 orginalColour = texture(TEXTURE, UV);
    dist = length(UV - vec2(0.5, 0.5));
    
    float fbm_val = fbm((UV + TIME) * 20.0 , 10); // Scale UV for more detail
    float fbm_val2 = fbm((UV + TIME) * 20.0 , 10);
    // Modify radius based on fBM
    float dynamic_radius = 0.4 + fbm_val * 0.1; // Adjust range as needed
    float dynamic_radius2 = 0.3 + fbm_val2 * 0.1; // Adjust range as needed
    
	float circle_step = step(dynamic_radius, dist);
	float circle_step2 = step(dynamic_radius2, dist);		
	vec3 colour = lightning_color * mix(0.1, 0.05, hash12(vec2(TIME))) / dist;
	//colour = vec3(1.0-colour);
	COLOR = mix(vec4(colour, 1.0), orginalColour, circle_step);
	COLOR = mix(orginalColour, COLOR, circle_step2);
    //// Color based on dynamic radius
    //if (dist < dynamic_radius && dist > dynamic_radius2) {
        //COLOR = vec4(1.0, 0.0, 0.0, 1.0); // Red inside circle
    //} else {
        //COLOR = vec4(0.0, 0.0, 1.0, 1.0); // Blue outside circle
    //}	
}

//void light() {
//	// Called for every pixel for every light affecting the CanvasItem.
//	// Uncomment to replace the default light processing function with this one.
//}

Godot Shaders is a pretty handy resource for this kind of thing.

You’ve got a modified_UV there that’s a good chunk of what you want. The UV coordinates on the texture are in the range (0.0 .. 1.0) in both U and V.

In case you haven’t run into the idea of UVs before, U is the X coordinate on the texture page, and V is the Y coordinate on the texture page. We give them different letters because when we’re talking about both texture coordinates and geometry coordinates it can get confusing and prolix if everything is X, Y.

Your modified_UV value is subtracting 0.5, which means it’s a range of (-0.5 .. 0.5) in each dimension. You can use that as a “how far am I from the center of the texture?” check, since the center of the texture is at (0.5, 0.5); half the range in each dimension.

1 Like