Procedural Cyclic Slash

This is a spatial shader that tries to procedurally resemble those nifty textures VFX artists use in their gobsmackingly beautiful WIPs and showcases. You need to apply it to a quad-mesh, and uh yeah you should be good from there! (Just kidding…)

After you apply it you need to set up the uniforms. It might be a bit tricky so I wrote a post on Tumblr that you can refer to (https://www.tumblr.com/alkaliii/768088479365251072/procedural-cyclic-slash?source=share)

If you just need something quick to start from here you go:

Animation.Derive_Progress = -1
Animation.Time_Scale = 0.25
Shape.Rotate_All = 285
Shape.Noise =
– New NoiseTexture2D
– – Width = 512
– – Height = 128
– – Seamless = True
– – Noise =
– – – New FastNoiseLite
– – – – Noise_Type = Cellular
– – – – Fractal.Gain = 4
– – – – Cellular.Distance_Function = Manhattan

Shape.Width_Gradient_Mask =
– New GradientTexture1D
– – Gradient =
– – – New Gradient
– – – – Offsets: 0.2, 0.5, 0.52
– – – – Colors: #FFFFFF, #000000, #FFFFFF

Shape.Length_Gradient_Mask =
– New GradientTexture1D
– – Gradient =
– – – New Gradient
– – – – Offsets: 0.25, 0.4, 0.6, 0.65, 0.7
– – – – Colors: #FFFFFF, #7F7F7F, #000000, #7F7F7F, #FFFFFF

Shape.Highlight =
– New GradientTexture1D
– – Gradient =
– – – New Gradient
– – – – Offsets: 0.5, 0.52, 0.54
– – – – Colors: #000000, #FFFFFF, #000000

Coloring.Color_Lookup =
– New GradientTexture1D
– – Gradient =
– – – New Gradient
– – – – Offsets: 0.0, 0.1, 0.2
– – – – Colors: #BF40BF, #008080, #ADD8E6

This is supposed to be general-purpose in a sense (hence the name procedural). By changing the BASE NOISE used and its dimensions you can get different looks out of this shader. Altering the two mask properties and COLOR LOOKUP can also help create a unique look for whatever you’re using this for. I’m not great at writing shaders so sorry if this is unoptimized or a bit difficult to use.

If you have any questions that I’m able to answer you are probably better off asking me on Discord (@aiwi). I’m on the Godot Engine, Godot Café, and Godot Effects and Shaders servers.

Shader code
shader_type spatial;
render_mode specular_toon,diffuse_toon,cull_disabled;

group_uniforms ANIMATION;
uniform float progress : hint_range(0.0, 1.0, 0.01) = 0.0;
// This value animates this shader when DERIVE_PROGRESS is set to 0.0

uniform float derive_progress : hint_range(-1.0, 1.0, 1.0) = 0.0;
// 0.0 Use PROGRESS (Change value in code or animation player)
// -1.0 Use TIME (Idle Loop)
// 1.0 use LIFETIME (Particle)

uniform float ease_progress : hint_range(-1.0, 1.0, 1.0) = 0.0;
// 0.0 No easing
// -1.0 Ease : Expo In
// 1.0 Ease : Expo Out

uniform float time_scale : hint_range(0.0, 8.0, 0.01) = 1.0;
uniform float anim_rot_amt : hint_range(0.0, 1.0, 0.01) = 1.0;
// ^^^
// The shader is setup to rotate the main image as it progresses, 
// set this to 0 to disable that behaviour
group_uniforms;

group_uniforms SHAPE;
uniform sampler2D base_noise : hint_default_white, filter_linear_mipmap;
// ^^^ 
// This is the basis of the main rough looking shape, It's width should be 
// stretched but that's up to you.

// these should be GradientTexture1Ds
uniform sampler2D width_gradient_mask : hint_default_transparent;
// ^^^ 
// This clips the slashes width, use white for what you want to clip, and 
// black for what you want to keep.
uniform sampler2D length_gradient_mask : hint_default_transparent;
// ^^^ 
// This clips the slashes "length" and controls how it animates... uh try 
// using a gradient that progresses 'white -> black -> white'
uniform sampler2D highlight : source_color,hint_default_transparent;
// ^^^ 
// This will be overlayed ontop of the main shape, it will get masked by 
// LENGTH_GRADIENT_MASK so it animates in sync with the main shape.

uniform float zoom : hint_range(0.0, 2.0, 0.01) = 0.6; // Scales the final image, Inverted (smaller means bigger)
uniform float rotate_all : hint_range(0.0, 360.0, 0.1) = 0.0; // Rotates the final image by x degrees
group_uniforms;

group_uniforms COLORING;
uniform float emission_strength : hint_range(0.0, 1.0, 0.1) = 1.0;
// ^^^
// Makes it glow

uniform float mix_strength : hint_range(0.0, 2.0, 0.01) = 1.0; 
// ^^^
// Controls how COLOR_LOOKUP is applied, less will move it closer to black if
// COLOR_LOOKUP isn't empty

// this should be a GradientTexture1D
uniform sampler2D color_lookup : source_color,hint_default_white;

varying float LIFETIME;
varying float INDEX;

vec2 polar_coordinates(vec2 uv, vec2 center, float zoomm, float repeat) {
	vec2 dir = uv - center;
	float radius = length(dir) * 2.0;
	float angle = atan(dir.y, dir.x) * 1.0/(3.1416 * 2.0);
	return mod(vec2(radius * zoomm, angle * repeat), 1.0);
}

vec2 rotate(vec2 uv, vec2 pivot, float angle) {
	mat2 rotation = mat2(vec2(sin(angle), -cos(angle)),
						vec2(cos(angle), sin(angle)));
	uv -= pivot;
	uv = uv * rotation;
	uv += pivot;
	return uv;
}

// Easing Functions Adapted From https://easings.net/

float easeOutExpo(float x) {
	return 1.0 - pow(2.0, -10.0 * x);
}

float easeInExpo(float x) {
	return pow(2.0, 10.0 * x - 10.0);
}

//Circ
float easeInOut(float x) {
	float result;
	if (x < 0.5) {result = (1.0 - sqrt(1.0 - pow(2.0 * x, 2.0))) / 2.0;}
	else {result =(sqrt(1.0 - pow(-2.0 * x + 2.0, 2.0)) + 1.0) / 2.0;}
	return result;
}

void vertex() {
	LIFETIME = INSTANCE_CUSTOM.y;
	INDEX = float(INSTANCE_ID);
}

float get_progress() {
	float p;
	float final;
	if (derive_progress > 0.0) {p = LIFETIME;}
	else if (derive_progress < 0.0) {p = mod(TIME * time_scale,1.0);}
	// You can change out what p is equal to here to tweak the idle loop behaviour
	// abs(sin(TIME * time_scale)) [Back and Forth]
	// mod(TIME * time_scale,1.0) [Over and Over]
	else {p = progress;}
	
	if (ease_progress > 0.0) {final = easeOutExpo(p);}
	else if (ease_progress < 0.0) {final = easeInExpo(p);}
	else {final = p;}
	
	return final;
}

void fragment() {
	// Get Values
	float p = get_progress();
	vec2 aUV = polar_coordinates(rotate(UV,vec2(0.5),radians(rotate_all)),vec2(0.5),zoom,1.0);
	vec4 b = texture(base_noise,aUV - vec2(0.0,p));
	vec4 wm = texture(width_gradient_mask,aUV);
	vec4 lm = texture(length_gradient_mask,rotate(aUV-vec2(0.0,easeInOut(p * anim_rot_amt)),vec2(0.5),radians(180.0)));
	
	// Combine Them
	vec4 prefinal = (b-wm) - lm;
	vec3 albe = vec3(1.0)*(texture(color_lookup,vec2(clamp(prefinal.r * UV.x,0.0,1.0),0.0)).rgb*mix_strength);
	vec4 high = clamp(texture(highlight,aUV) - (lm),0.0,1.0);
	
	// Apply Them
	ALBEDO = clamp(albe + high.rgb,0.0,1.0);
	EMISSION = clamp(albe + high.rgb,0.0,1.0) * (3.0 * emission_strength);
	
	float start = abs(cos(p * PI));
	float end = abs(cos(p * PI));
	ALPHA *= clamp(smoothstep(start,end,prefinal.r) + smoothstep(clamp(start,0.0,0.2),clamp(end,0.0,0.2),(high.r * 0.2)),0.0,1.0) * COLOR.a;
}
Tags
arc, Slash, Sword Slash
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 Alkaliii

Billboard Sprite3D Hitflash (Godot 4.x)

Related shaders

Procedural Electric Current Shader

Stochastic Procedural Texture Shader

Procedural Wang Tiling Shader

Subscribe
Notify of
guest

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
TTien63
2 days ago

God I love the slashing effect.

GodotRuminant
2 days ago

That is incredibly satisfying to watch