Improved sprite pixel explosion shader

Ported to Godot 4.7.1 and improved, based on this shader from mopifish

The parameters and the setup are the same as described in the original repo.

A few more parameters were added to support sprite sheets.

NEW PARAMS:

  • sprite_frame – Controls the frame number to use for the pixel explosion (defaults to 0)
  • sprite_hframes – Tells the shader how many horizontal frames are in the sprite sheet (defaults to 1)
  • sprite_vframes – Tells the shader how many vertical frames are in the sprite sheet (defaults to 1)

Actual example of how i’m using it in my game:

func emit_death_particles_at(coords: Vector2, sprite: Sprite2D) -> void:
	var particles: DeathParticle = death_particles.get_particles()
	particles.global_position = coords
	particles.scale = sprite.scale
	var texture: Texture2D = sprite.texture
	particles.amount = texture.get_width() * texture.get_height()
	process_material.set_shader_parameter("sprite", sprite.texture)
	process_material.set_shader_parameter("sprite_frame", sprite.frame)
	process_material.set_shader_parameter("sprite_hframes", sprite.hframes)
	process_material.set_shader_parameter("sprite_vframes", sprite.vframes)
	particles.emit()
Shader code
// Sprite Particle Explosion Shader made in Godot 4.7.1 
// port of PlayWithFurcifers shader https://www.youtube.com/watch?v=D7XSL0zBOwI
// Ported by Mopifish, m o r b o

shader_type particles;
render_mode disable_velocity;


uniform float spread = 180;
uniform float inherit_emitter_velocity_ratio = 0;
uniform float initial_linear_velocity_min = 100;
uniform float initial_linear_velocity_max = 100;
uniform float orbit_velocity_min;
uniform float orbit_velocity_max;
uniform float radial_velocity_min;
uniform float radial_velocity_max;
uniform float linear_accel_min;
uniform float linear_accel_max;
uniform float radial_accel_min;
uniform float radial_accel_max;
uniform float tangent_accel_min;
uniform float tangent_accel_max;
uniform float damping_min;
uniform float damping_max;
uniform float scale_min = 1;
uniform float scale_max = 1;
uniform float lifetime_randomness;
uniform vec3 emission_shape_offset = vec3(0.);
uniform vec3 emission_shape_scale = vec3(1.);
uniform vec3 emission_box_extents = vec3(100, 100, 0.);

uniform sampler2D sprite:repeat_disable;
uniform int sprite_frame = 0;
uniform int sprite_hframes = 1;
uniform int sprite_vframes = 1;


float rand_from_seed(inout uint seed) {
	int k;
	int s = int(seed);
	if (s == 0)
	s = 305420679;
	k = s / 127773;
	s = 16807 * (s - k * 127773) - 2836 * k;
	if (s < 0)
		s += 2147483647;
	seed = uint(s);
	return float(seed % uint(65536)) / 65535.0;
}

float rand_from_seed_m1_p1(inout uint seed) {
	return rand_from_seed(seed) * 2.0 - 1.0;
}

uint hash(uint x) {
	x = ((x >> uint(16)) ^ x) * uint(73244475);
	x = ((x >> uint(16)) ^ x) * uint(73244475);
	x = (x >> uint(16)) ^ x;
	return x;
}

struct DisplayParameters{
	vec3 scale;
	float lifetime;
};

struct DynamicsParameters{
	float initial_velocity_multiplier;
	float radial_velocity;
	float orbit_velocity;
};
struct PhysicalParameters{
	float linear_accel;
	float radial_accel;
	float tangent_accel;
	float damping;
};

void calculate_initial_physical_params(inout PhysicalParameters params, inout uint alt_seed){
	params.linear_accel = mix(linear_accel_min, linear_accel_max, rand_from_seed(alt_seed));
	params.radial_accel = mix(radial_accel_min, radial_accel_max, rand_from_seed(alt_seed));
	params.tangent_accel = mix(tangent_accel_min, tangent_accel_max, rand_from_seed(alt_seed));
	params.damping = mix(damping_min, damping_max, rand_from_seed(alt_seed));
}
void calculate_initial_dynamics_params(inout DynamicsParameters params,inout uint alt_seed){
	// -------------------- DO NOT REORDER OPERATIONS, IT BREAKS VISUAL COMPATIBILITY
	// -------------------- ADD NEW OPERATIONS AT THE BOTTOM
	params.initial_velocity_multiplier = mix(initial_linear_velocity_min, initial_linear_velocity_max,rand_from_seed(alt_seed));
	params.radial_velocity = mix(radial_velocity_min, radial_velocity_max,rand_from_seed(alt_seed));
	params.orbit_velocity = mix(orbit_velocity_min, orbit_velocity_max,rand_from_seed(alt_seed));
}
void calculate_initial_display_params(inout DisplayParameters params,inout uint alt_seed){
	// -------------------- DO NOT REORDER OPERATIONS, IT BREAKS VISUAL COMPATIBILITY
	// -------------------- ADD NEW OPERATIONS AT THE BOTTOM
	float pi = 3.14159;
	float degree_to_rad = pi / 180.0;
	params.scale = vec3(mix(scale_min, scale_max, rand_from_seed(alt_seed)));
	params.scale = sign(params.scale) * max(abs(params.scale), 0.001);
	params.lifetime = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));
}
vec3 calculate_initial_position(inout uint alt_seed) {
    vec2 texture_size = vec2(textureSize(sprite, 0)) / vec2(float(sprite_hframes), float(sprite_vframes));

    vec2 random_position = vec2(
        rand_from_seed(alt_seed),
        rand_from_seed(alt_seed)
    );

    vec2 position = (random_position - vec2(0.5)) * texture_size;

    return vec3(position, 0.0);
}

vec3 get_random_direction_from_spread(inout uint alt_seed, float spread_angle){
	float pi = 3.14159;
	float degree_to_rad = pi / 180.0;
	vec3 velocity = vec3(0.);
	float spread_rad = spread_angle * degree_to_rad;
	float angle1_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad;
	float angle2_rad = rand_from_seed_m1_p1(alt_seed) * spread_rad * (1.0);
	vec3 direction_xz = vec3(sin(angle1_rad), 0.0, cos(angle1_rad));
	vec3 direction_yz = vec3(0.0, sin(angle2_rad), cos(angle2_rad));
	direction_yz.z = direction_yz.z / max(0.0001,sqrt(abs(direction_yz.z))); // better uniform distribution
	vec3 spread_direction = vec3(direction_xz.x * direction_yz.z, direction_yz.y, direction_xz.z * direction_yz.z);
	vec3 direction_nrm = length(vec3(0.0)) > 0.0 ? normalize(vec3(0.0)) : vec3(0.0, 0.0, 1.0);
	// rotate spread to direction
	vec3 binormal = cross(vec3(0.0, 1.0, 0.0), direction_nrm);
	if (length(binormal) < 0.0001) {
		// direction is parallel to Y. Choose Z as the binormal.
		binormal = vec3(0.0, 0.0, 1.0);
	}
	binormal = normalize(binormal);
	vec3 normal = cross(binormal, direction_nrm);
	spread_direction = binormal * spread_direction.x + normal * spread_direction.y + direction_nrm * spread_direction.z;
	return spread_direction;
}

vec3 process_orbit_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform,float delta, float total_lifetime){
	if(abs(param.orbit_velocity) < 0.01 || delta < 0.001){ return vec3(0.0);}

	vec3 displacement = vec3(0.);
	float pi = 3.14159;
	float degree_to_rad = pi / 180.0;
	float orbit_amount = param.orbit_velocity;
	if (orbit_amount != 0.0) {
       vec3 pos = transform[3].xyz;
       vec3 org = emission_transform[3].xyz;
       vec3 diff = pos - org;
	     float ang = orbit_amount * pi * 2.0 * delta;
	     mat2 rot = mat2(vec2(cos(ang), -sin(ang)), vec2(sin(ang), cos(ang)));
	     displacement.xy -= diff.xy;
        displacement.xy += rot * diff.xy;
	}
       return (emission_transform * vec4(displacement/delta, 0.0)).xyz;
}
vec3 process_radial_displacement(DynamicsParameters param, float lifetime, inout uint alt_seed, mat4 transform, mat4 emission_transform, float delta){
	vec3 radial_displacement = vec3(0.0);
	if (delta < 0.001){
		return radial_displacement;
	}
	float radial_displacement_multiplier = 1.0;
	if(length(transform[3].xyz ) > 0.01){
		radial_displacement = normalize(transform[3].xyz) * radial_displacement_multiplier * param.radial_velocity;
	}else{radial_displacement = get_random_direction_from_spread(alt_seed, 360.0)* param.radial_velocity;} 
	if (radial_displacement_multiplier * param.radial_velocity < 0.0){
 // Prevent inwards velocity to flicker once the point is reached.		if (length(radial_displacement) > 0.01){
		radial_displacement = normalize(radial_displacement) * min(abs((radial_displacement_multiplier * param.radial_velocity)), length(transform[3].xyz) / delta);
		}
	
	return radial_displacement;
}

void start() {
	uint base_number = NUMBER;
	uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);
	DisplayParameters params;
	calculate_initial_display_params(params, alt_seed);
	DynamicsParameters dynamic_params;
	calculate_initial_dynamics_params(dynamic_params, alt_seed);
	PhysicalParameters physics_params;
	calculate_initial_physical_params(physics_params, alt_seed);
	if (rand_from_seed(alt_seed) > AMOUNT_RATIO) {
		ACTIVE = false;
	}
	
	float pi = 3.14159;
	float degree_to_rad = pi / 180.0;
	
	if (RESTART_CUSTOM){
		CUSTOM = vec4(0.);
		CUSTOM.w = params.lifetime;
	}
	
	if (RESTART_ROT_SCALE) {
		TRANSFORM[0].xyz = vec3(1.0, 0.0, 0.0);
		TRANSFORM[1].xyz = vec3(0.0, 1.0, 0.0);
		TRANSFORM[2].xyz = vec3(0.0, 0.0, 1.0);
	}

	if (RESTART_POSITION) {
		TRANSFORM[3].xyz = calculate_initial_position(alt_seed);
		TRANSFORM = EMISSION_TRANSFORM * TRANSFORM;
		}
	if (RESTART_VELOCITY) {
		VELOCITY = get_random_direction_from_spread(alt_seed, spread) * dynamic_params.initial_velocity_multiplier;
		}
		
	VELOCITY = (EMISSION_TRANSFORM * vec4(VELOCITY, 0.0)).xyz;
	VELOCITY += EMITTER_VELOCITY * inherit_emitter_velocity_ratio;
;
		VELOCITY.z = 0.;
		TRANSFORM[3].z = 0.;
	
	// do sprite sheet calculations
	vec2 sheet_grid = vec2(float(sprite_hframes), float(sprite_vframes));
	vec2 sheet_size = vec2(textureSize(sprite, 0));
	vec2 frame_size = sheet_size / sheet_grid;
	int frame_x = sprite_frame % sprite_hframes;
	int frame_y = sprite_frame / sprite_hframes;
	
	// Set particle to match sprite pixel color
	vec2 particlePosition = TRANSFORM[3].xy;
	// Position within the selected frame.
	vec2 frame_uv = particlePosition / frame_size + vec2(0.5);
	// Offset into the spritesheet.
	vec2 sheet_uv = (vec2(float(frame_x), float(frame_y)) + frame_uv) / sheet_grid;
	
	vec4 spriteColor = texture(sprite, sheet_uv);
	COLOR = spriteColor;
	// Disable transparent particles
	if (spriteColor.a == 0.0){ ACTIVE = false;}
}

void process() {
	uint base_number = NUMBER;

	uint alt_seed = hash(base_number + uint(1) + RANDOM_SEED);
	DisplayParameters params;
	calculate_initial_display_params(params, alt_seed);
	DynamicsParameters dynamic_params;
	calculate_initial_dynamics_params(dynamic_params, alt_seed);
	PhysicalParameters physics_params;
	calculate_initial_physical_params(physics_params, alt_seed);
	float pi = 3.14159;
	float degree_to_rad = pi / 180.0;

	CUSTOM.y += DELTA / LIFETIME;
	CUSTOM.y = mix(CUSTOM.y, 1.0, INTERPOLATE_TO_END);
	float lifetime_percent = CUSTOM.y/ params.lifetime;
	if (CUSTOM.y > CUSTOM.w) {
		ACTIVE = false;
	}
	
	
	
	// will use this later to calculate final displacement and orient the particle.
	vec3 starting_position = TRANSFORM[3].xyz;
	vec3 controlled_displacement = vec3(0.0);
	
	// calculate all velocity
	controlled_displacement += process_orbit_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA, params.lifetime * LIFETIME);
	controlled_displacement += process_radial_displacement(dynamic_params, lifetime_percent, alt_seed, TRANSFORM, EMISSION_TRANSFORM, DELTA);
	
	vec3 force = vec3(0.0);
	{
		// copied from previous version
		vec3 pos = TRANSFORM[3].xyz;
		// apply linear acceleration
		force += length(VELOCITY) > 0.0 ? normalize(VELOCITY) * physics_params.linear_accel : vec3(0.0);
		// apply radial acceleration
		vec3 org = EMISSION_TRANSFORM[3].xyz;
		vec3 diff = pos - org;
		force += length(diff) > 0.0 ? normalize(diff) * physics_params.radial_accel : vec3(0.0);
		// apply tangential acceleration;
		float tangent_accel_val = physics_params.tangent_accel;
       force += length(diff.yx) > 0.0 ? vec3(normalize(diff.yx * vec2(-1.0, 1.0)), 0.0) * tangent_accel_val : vec3(0.0);
		force += ATTRACTOR_FORCE;

		// apply attractor forces
			force.z = 0.;
		VELOCITY += force * DELTA;
	}
	{
		// copied from previous version
		if (physics_params.damping > 0.0) {
			float v = length(VELOCITY);
			v -= physics_params.damping * DELTA;
			if (v < 0.0) {
				VELOCITY = vec3(0.0);
			} else {
				VELOCITY = normalize(VELOCITY) * v;
			}
		}
		
	}
	
	
	// turbulence before limiting
	vec3 final_velocity = controlled_displacement + VELOCITY;
	
	// limit velocity
		final_velocity.z = 0.;
	TRANSFORM[3].xyz += final_velocity * DELTA;
	
	TRANSFORM[0] = vec4(cos(CUSTOM.x), -sin(CUSTOM.x), 0.0, 0.0);
	TRANSFORM[1] = vec4(sin(CUSTOM.x), cos(CUSTOM.x), 0.0, 0.0);
	TRANSFORM[2] = vec4(0.0, 0.0, 1.0, 0.0);
	TRANSFORM[3].z = 0.0;
	
	// Apply Scale
	TRANSFORM[0].xyz *= sign(params.scale.x) * max(abs(params.scale.x), 0.001);
	TRANSFORM[1].xyz *= sign(params.scale.y) * max(abs(params.scale.y), 0.001);
	TRANSFORM[2].xyz *= sign(params.scale.z) * max(abs(params.scale.z), 0.001);
	
	if (CUSTOM.y > CUSTOM.w) {
		ACTIVE = false;
	}
	
	// Fade out pixels as time progresses
	if (COLOR.a > 0.0){
		COLOR.a -= 1.0/LIFETIME*DELTA;
	}
}
Tags
dissolve, explosion, particles, pixel
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.

Related shaders

guest

0 Comments
Oldest
Newest Most Voted