Shield with impact waves
Spherical shield with an optional impact generating waves. May be a demanding shader as it does some kind of raymarching for the waves.
Almost everything is a parameter : from the color to the impact location, noise function and wave progression.
Be aware that because of the inexact raymarching function, extreme values can produce artifacts.
**EDIT** : Updated for Godot 4.x (sorry for the delay)
Shader code
shader_type canvas_item;
const float MPI = 1.5707966326;
const int STEPS = 20;
const float LOWER_LIMIT = 0.01;
uniform float zoom_out : hint_range(1.0, 10.0) = 1.0;
uniform float border_decay : hint_range(0.0, 0.99) = 0.6;
uniform vec4 shield_tint : source_color = vec4(0.407843, 0.564706, 0.729412, 0.768627);
uniform vec4 shield_saturation : source_color = vec4(1., 1., 1., .76);
uniform float attack_angle : hint_range(-6.283185, 6.283185) = 0.;
uniform float attack_penetration : hint_range(0., 0.9) = 0.2;
uniform float attack_radius : hint_range(0., 3.) = 0.2;
uniform float attack_amplitude : hint_range(0., 1.) = 0.1;
uniform float wave_speed : hint_range(2., 40.) = 16.;
uniform float wave_num : hint_range(10., 40.) = 17.;
uniform sampler2D noise_texture : repeat_enable;
uniform sampler2D screen_tex : hint_screen_texture, repeat_disable;
uniform float noise_speed : hint_range(1.0, 10.0) = 3.;
uniform float noise_amplitude : hint_range(0.01, 1.) = 0.89;
uniform float noise_deformation : hint_range(1., 300.) = 100.;
float compute_z_radius(vec2 pos, float r) {
vec3 o = vec3(pos, -1.);
return -sqrt(1. - dot(o, o) + (r * r));
}
float compute_front_z(vec2 pos) {
vec3 p = vec3(pos, -1.);
return (-sqrt(2. - dot(p, p)));
}
void fragment() {
// Sphere computation
vec2 current_pos = (UV - 0.5) * (2.0 * zoom_out);
float len = length(current_pos);
vec2 attack_direction = vec2(cos(attack_angle), sin(attack_angle));
vec4 noise_texel = texture(noise_texture, current_pos + TIME * attack_direction * noise_speed);
vec4 noise_amount = (noise_texel * (1. - noise_amplitude)) + noise_amplitude;
float noise_mask = (noise_amount.r + noise_amount.g + noise_amount.b) / 3.0;
float amplitude_decay = (1. + attack_amplitude) * border_decay * noise_mask;
float border_mask = clamp(len - amplitude_decay, 0., 1. - border_decay) / (1. - border_decay);
float mask = clamp(ceil(noise_mask * (1. + attack_amplitude) - len), 0., 1.);
vec4 shield_color = mix(shield_saturation, shield_tint, 1. - border_mask) * mask;
vec2 deformation_mask = (noise_texel.rg - vec2(.5)) * 2. * mask;
// Waves
if(len <= 1. + attack_amplitude) {
vec2 attack_norm = attack_direction * (1. - attack_penetration);
vec3 attack_position = vec3(attack_norm, compute_front_z(attack_norm));
float retained_len = 0.;
float retained_intensity = 0.;
float z_step = compute_z_radius(current_pos, 1. + attack_amplitude);
float hdiff = 1. + attack_amplitude;
float min_diff = hdiff;
int step_id = STEPS;
for(int i = 0; i < STEPS; ++i) {
vec3 current_projection = vec3(current_pos, z_step);
vec3 pos_on_surface = normalize(current_projection);
float att_len = length(attack_position - pos_on_surface);
if(att_len < attack_radius) {
float intensity = (cos(att_len * wave_num - TIME * wave_speed) + 1.)/2. * cos((att_len / attack_radius) * MPI);
hdiff = abs(length(current_projection) - 1. - (intensity * attack_amplitude));
if(hdiff < min_diff) {
retained_intensity = intensity;
retained_len = att_len;
min_diff = hdiff;
if (hdiff < LOWER_LIMIT) {
break;
}
}
float extra = pos_on_surface.z * (1. + (intensity * attack_amplitude));
z_step += (extra - z_step) * (1. - (float(i) / float(STEPS)));
} else {
break;
}
}
if ((hdiff < LOWER_LIMIT) || ((step_id == STEPS) && (min_diff < (1.0 + attack_amplitude)))) {
float attenuation = cos(((1. - (len / attack_radius))) * MPI);
shield_color = mask*mix(shield_color, shield_saturation, retained_intensity);
deformation_mask = mask*mix(current_pos * (1. - retained_intensity), deformation_mask, cos(((1. - (len / attack_radius))) * MPI));
}
}
vec4 screen_color = texture(screen_tex, SCREEN_UV + (noise_deformation * deformation_mask * SCREEN_PIXEL_SIZE));
COLOR = vec4(mix(screen_color.rgb, shield_color.rgb, shield_color.a), 1.0);
}
like it
i really like that shader! Is it possible that someone brings it to work for Godot 4?
i have already replaced hint_color with source_color and added a FastNoiseLite Texture.
Then the shader works partially. The attack impact seems to work, but the noise is not affected by the TIME variable, so it is not moving.
I’m starting to migrate my shaders to Godot 4. I’ll keep you informed. Please keep in mind that i’m doing this on the free-time of my free-time 🙂 But I’ll definitely check that TIME issue.
You don’t have to hurry, I also need godot in my free time, which unfortunately is very limited. In any case, thank you very much in advance for your effort!
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, repeat_disable, filter_nearest;
Add in Godot4
Hi, I tried this, but still the Noise is not moving at all. The Attack impact is working…
I set up the noise texture as a FastNoiseLite…
shader_type canvas_item;
uniform float cotrol_param: hint_range(0.0, 10.0, 0.1);
uniform bool get_hit = false;
uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, repeat_disable, filter_nearest;
const float MPI = 1.5707966326;
const int STEPS = 20;
const float LOWER_LIMIT = 0.01;
uniform float zoom_out : hint_range(1.0, 10.0) = 1.0;
uniform float border_decay : hint_range(0.0, 0.99) = 0.6;
uniform vec4 shield_tint : source_color = vec4(0.407843, 0.564706, 0.729412, 0.768627);
uniform vec4 shield_saturation : source_color = vec4(1., 1., 1., .76);
uniform float attack_angle : hint_range(-6.283185, 6.283185) = 0.;
uniform float attack_penetration : hint_range(0.16, 1) = 0.2;
uniform float attack_radius : hint_range(0., 3.) = 0.2;
uniform float attack_amplitude : hint_range(0., 1.) = 0.1;
uniform float wave_speed : hint_range(1., 40.) = 16.;
uniform float wave_num : hint_range(10., 40.) = 17.;
uniform sampler2D noise_texture;
uniform float noise_speed : hint_range(1.0, 10.0) = 3.;
uniform float noise_amplitude : hint_range(0.01, 1.) = 0.89;
uniform float noise_deformation : hint_range(1., 300.) = 100.;
float compute_z_radius(vec2 pos, float r) {
vec3 o = vec3(pos, -1.);
return -sqrt(1. – dot(o, o) + (r * r));
}
float compute_front_z(vec2 pos) {
vec3 p = vec3(pos, -1.);
return (-sqrt(2. – dot(p, p)));
}
void fragment() {
// Sphere computation
vec2 current_pos = (UV – 0.5) * (2.0 * zoom_out);
float len = length(current_pos);
vec2 attack_direction = vec2(cos(-attack_angle), sin(-attack_angle));
//vec4 noise_texel = texture(noise_texture, current_pos + cotrol_param * attack_direction * noise_speed);
vec4 noise_texel = texture(noise_texture, current_pos + TIME * attack_direction * noise_speed);
vec4 noise_amount = (noise_texel * (1. – noise_amplitude)) + noise_amplitude;
float noise_mask = (noise_amount.r + noise_amount.g + noise_amount.b) / 3.0;
float amplitude_decay = (1. + attack_amplitude) * border_decay * noise_mask;
float border_mask = clamp(len – amplitude_decay, 0., 1. – border_decay) / (1. – border_decay);
float mask = clamp(ceil(noise_mask * (1. + attack_amplitude) – len), 0., 1.);
vec4 shield_color = mix(shield_saturation, shield_tint, 1. – border_mask) * mask;
vec2 deformation_mask = (noise_texel.rg – vec2(.5)) * 2. * mask;
// Waves
if(len <= 1. + attack_amplitude && get_hit) {
vec2 attack_norm = attack_direction * (1. – attack_penetration);
vec3 attack_position = vec3(attack_norm, compute_front_z(attack_norm));
float retained_len = 0.;
float retained_intensity = 0.;
float z_step = compute_z_radius(current_pos, 1. + attack_amplitude);
float hdiff = 1. + attack_amplitude;
float min_diff = hdiff;
int step_id = STEPS;
for(int i = 0; i < STEPS; ++i) {
vec3 current_projection = vec3(current_pos, z_step);
vec3 pos_on_surface = normalize(current_projection);
float att_len = length(attack_position – pos_on_surface);
if(att_len < attack_radius) {
float intensity = (cos(att_len * wave_num – cotrol_param * wave_speed) + 1.)/2. * cos((att_len / attack_radius) * MPI);
//float intensity = (cos(att_len * wave_num – TIME * wave_speed) + 1.)/2. * cos((att_len / attack_radius) * MPI);
hdiff = abs(length(current_projection) – 1. – (intensity * attack_amplitude));
if(hdiff < min_diff) {
retained_intensity = intensity;
retained_len = att_len;
min_diff = hdiff;
if (hdiff < LOWER_LIMIT) {
break;
}
}
float extra = pos_on_surface.z * (1. + (intensity * attack_amplitude));
z_step += (extra – z_step) * (1. – (float(i) / float(STEPS)));
} else {
break;
}
}
if ((hdiff < LOWER_LIMIT) || ((step_id == STEPS) && (min_diff < (1.0 + attack_amplitude)))) {
float attenuation = cos(((1. – (len / attack_radius))) * MPI);
shield_color = mask*mix(shield_color, shield_saturation, retained_intensity);
deformation_mask = mask*mix(current_pos * (1. – retained_intensity), deformation_mask, cos(((1. – (len / attack_radius))) * MPI));
}
TEXTURE;
}
vec4 screen_color = texture(SCREEN_TEXTURE, SCREEN_UV + (noise_deformation * deformation_mask * SCREEN_PIXEL_SIZE));
COLOR = vec4(mix(screen_color.rgb, shield_color.rgb, shield_color.a), 1.0);
}
I found the issue why the TIME variable is not affecting the shader: The noise_texture needs the “repeat_enable” property. Without it, the texture starts to drift immediately.
With that change, the shader works perfect!
Good one ! I should have enclosed the UV with fract. It also works better.
Thanks for the contribution, really appreciated. I’ve been very busy lately :/