Broken glass hole (animated and procedural)

A procedural 2D shader for creating a broken-glass effect, featuring a hole surrounded by detailed cracks. The effect is fully animatable, making it easy to bring shattered glass to life in your projects.

You can find a detailed explanation of the shader on my youtube tutorial. I’ve used some tricks from my tiler shader.

THere is also a sample project on my github.

Hope you’ll like 💔

 

Shader code
shader_type canvas_item;

#define USE_RADIAL 1
#define USE_NOISE 1

uniform int g_random_seed = 0;
uniform vec2 g_grid_size = vec2(7.0, 21.0); // x=cols, y=rows
uniform vec4 g_color_glass : source_color = vec4(0.8, 1.0, 1.0, 0.6); // glass color
uniform vec4 g_color_cracks : source_color = vec4(1.0, 1.0, 1.0, 1.0); // crack color
uniform vec4 g_color_hole : source_color = vec4(1.0, 1.0, 1.0, 0.0); // hole color
uniform float g_line_size : hint_range(0.001, 0.5) = 0.015;

uniform float g_hole_limit = 0.0; // where the hole starts
uniform float g_hole_limit_fade_mult = 0.1; // how fast cracks grow when hole grows

uniform float g_fade_start_min : hint_range(0.1, 0.8) = 0.25;
uniform float g_fade_start_max : hint_range(0.2, 0.9) = 0.60; // should be > _min value
uniform float g_fade_length_min : hint_range(0.0, 0.8) = 0.1;
uniform float g_fade_length_max : hint_range(0.0, 0.9) = 0.2; // should be > _min value

uniform float g_power = 5.0; // how sharp de you want the cracks

#if USE_RADIAL
uniform float g_radial_zoom : hint_range(0.1, 0.7) = 0.69;
uniform float g_radial_repeat = 1.0;
#endif

#if USE_NOISE
uniform sampler2D g_noise_tex : repeat_enable; // use a seamless noise texture with different R and G values (you can use: As Normal Map [x] On)
uniform float g_noise_scale = 1.0;
uniform float g_noise_strength : hint_range(0.000, 0.025, 0.001) = 0.010;
#endif

// returns a pseudo random number 
float random(vec2 p) {
    p = fract(p * vec2(123.34, 456.21));
    p += dot(p, p + vec2(45.32));
    return fract(p.x * p.y);
}

// taken from godotshaders.com :) (but, a bit modified)
#if USE_RADIAL
vec2 polar_coordinates(vec2 uv, vec2 center, float zoom, float repeat, float offset)
{
	vec2 dir = uv - center;
	float radius = length(dir) * 2.0;
	float angle = atan(dir.y, dir.x) * 1.0/TAU;
	return mod(vec2(radius * zoom - offset, angle * repeat), 1.0);
}
#endif

vec4 get_row_info(float seed, vec2 grid_uv, float row_offset) {
	
	// get row indices
	vec2 grid_uv_rows = vec2(0.0, grid_uv.y);
	vec2 row_num = floor(grid_uv_rows); // row indices	
	
	// apply offset
	row_num.y = round(mod(round(row_num.y + row_offset), g_grid_size.y));

	// get row Y position (in cell space)	
	vec2 rnd_pos_y = seed + row_num + vec2(1.0, 1.0);
	float pos_y = random(rnd_pos_y);	

	// get fade start and length
	vec2 rnd_fade_start_rnd = seed + row_num + vec2(2.0, 2.0);
	float fade_start = mix(g_fade_start_min, g_fade_start_max, random(rnd_fade_start_rnd) );	
	vec2 rnd_fade_length_rnd = seed + row_num + vec2(3.0, 3.0);
	float fade_length = mix(g_fade_length_min, g_fade_length_max, random(rnd_fade_length_rnd) );	

	// add hole limit to fade start
	fade_start -= g_hole_limit * g_hole_limit_fade_mult;

	// return row position Y (cell space), row index, fade start and fade length
	return vec4(pos_y, row_num.y, fade_start, fade_length);
	
}

vec2 get_cell_info(float seed, vec2 grid_uv, float row_offset) {
	
	// get cell indices
	vec2 cell_num = floor(grid_uv); // cell indices	

	// apply offset
	cell_num.y = round(mod(round(cell_num.y + row_offset), g_grid_size.y));

	// get row X position (in cell space)
	vec2 rnd_pos_x = seed + cell_num + vec2(4.0, 4.0);
	float pos_x = random(rnd_pos_x);	
	
	// return cell position X, cell col index
	return vec2(pos_x, cell_num.x);
		
}

void fragment() {	
	float seed = float(g_random_seed);
	
	vec2 src_uv = UV;	
	vec2 uv = src_uv;

#if USE_NOISE
	vec4 noise = texture(g_noise_tex, UV * g_noise_scale);
	uv.x += ((noise.r * 2.0) - 1.0) * g_noise_strength;
	uv.y += ((noise.g * 2.0) - 1.0) * g_noise_strength;
#endif
		
#if USE_RADIAL
	uv = polar_coordinates(uv, vec2(0.5), g_radial_zoom, g_radial_repeat, 0.0);
#endif	
	
    vec2 grid_uv = (uv * g_grid_size);
    vec2 local_pos = fract(grid_uv); // position inside the cell	
	
	// get current row info (previous, current, next row)
	vec4 row_prv = get_row_info(seed, grid_uv, -1.0);
	vec4 row_cur = get_row_info(seed, grid_uv, 0.0);
	vec4 row_nxt = get_row_info(seed, grid_uv, 1.0);
	
	// get cell info (previous, current, next row)
	// vec2 cell_prv = get_cell_info(seed, grid_uv, -1.0);
	vec2 cell_cur = get_cell_info(seed, grid_uv, 0.0);
	vec2 cell_nxt = get_cell_info(seed, grid_uv, 1.0);	
	
	// compute row fade values
	float fade_prv = smoothstep(row_prv.z, row_prv.z + row_prv.a, 1.0 - uv.x);	
	float fade_cur = smoothstep(row_cur.z, row_cur.z + row_cur.a, 1.0 - uv.x);
	float fade_nxt = smoothstep(row_nxt.z, row_nxt.z + row_nxt.a, 1.0 - uv.x);	
	
	// we are above or under current row horizontal line?
	float pos_x = 0.5; // cell space
	float pos_y_a = 0.0; // grid space
	float pos_y_b = 0.0; // grid space
	float fade_a = 0.0;
	float fade_b = 0.0;
	float row_dist = 0.0;
	if (local_pos.y < row_cur.x)
	{
		// get X position in cell space
		pos_x = cell_cur.x;
		// get Y positions in grid space
		pos_y_a = row_prv.y + row_prv.x;
		pos_y_b = row_cur.y + row_cur.x;
		if (pos_y_b < pos_y_a) { // a little fix for when the prev row is the last one (repeat-Y)
			pos_y_a = row_cur.y + row_cur.x;
			pos_y_b = row_prv.y + row_prv.x;
		}		
		// get distance to the row
		row_dist = row_cur.x - local_pos.y;
		// get fade values
		fade_a = fade_prv;
		fade_b = fade_cur;		
	}
	else
	{
		// get X position in cell space
		pos_x = cell_nxt.x;
		// get Y positions in grid space
		pos_y_a = row_cur.y + row_cur.x;		
		pos_y_b = row_nxt.y + row_nxt.x;		
		if (pos_y_b < pos_y_a) { // a little fix for when the next row is the first one (repeat-Y)
			pos_y_a = row_nxt.y + row_nxt.x;
			pos_y_b = row_cur.y + row_cur.x;				
		}
		// get distance to the row
		row_dist = (local_pos.y - row_cur.x);
		// get fade values
		fade_a = fade_cur;
		fade_b = fade_nxt;		
	}	
	
	// compute row height
	float row_height = (pos_y_b - pos_y_a);
	// normalize row dist 
	float row_dist_nrm = (row_dist / row_height);
	if (local_pos.y < row_cur.x)
	{
		row_dist_nrm = 1.0 - row_dist_nrm;
	}
	// interpolate fades
	float col_fade = mix(fade_a, fade_b, row_dist_nrm);
	
	// color value (0 = a, 1 = b)
	float color_val = 0.5;
		
	// draw rows
	if ((local_pos.y < row_cur.x - g_line_size) || (local_pos.y > row_cur.x + g_line_size))
	{
		color_val = 0.0;
	} 
	else
	{
		color_val = fade_cur;
	}

	// draw columns
	if ((local_pos.x >= pos_x - g_line_size) && (local_pos.x <= pos_x + g_line_size))
	{
		color_val = col_fade; 
	}
		
	// draw hole (first cells on the left)
	float hole_val = 0.0;
	if (
		((cell_cur.y == floor(g_hole_limit)) && (local_pos.x < pos_x)) 	
		|| 
		(cell_cur.y < g_hole_limit - 1.0)		
	) {		
		hole_val = 1.0;			
	} 
	
	// render final color
	COLOR = mix(g_color_glass, g_color_cracks, pow(color_val, g_power));
	COLOR = mix(COLOR, g_color_hole, hole_val);
}
Live Preview
Tags
animated, broken, glass, hole, Procedural
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 ProfesorShader

Related shaders

guest

0 Comments
Oldest
Newest Most Voted