Dynamic Animated Liquid Texture (Blood, Paint, Water and other Fluids)

Warning, this is not an spatial shader. It’s a new type of shaders in Godot 4.7 (texture_blit shaders). You’ll need Godot 4.7 dev1 at least to run this shader.

To do the effect, you’ll need 3 DrawableTexture2D, and 2 shaders. Two of the DrawableTexture2D are for ping-ponging the liquid amount and how dry it is (something like a “wet map”). The other one is an albedo texture generated from that special “wet map” textures. 

For more details, please take a look to the sample/demo project (see below) on my github, or watch my youtube video (its in spanish).

Shader code
// https://www.youtube.com/@ProfesorShader
// https://github.com/profesorshader

// This effect requires two texture_blit shaders, one for drawing the splat and the other for animating the liquid

// "physics" shader

shader_type texture_blit;
render_mode blend_mix;

uniform vec3 blood_color : source_color = vec3(0.8, 0.0, 0.0);
uniform vec3 background_color : source_color = vec3(0.0, 0.0, 0.0);

uniform float flow_velocity = 0.5;
uniform float velocity_keep = 0.998;
uniform sampler2D wet_map : hint_blit_source0;
uniform sampler2D height_map; // try with godot's default simple noise
uniform float height_mult = 20.0; // this value scales noise texture
uniform float mask_min = 0.0;
uniform float mask_max = 0.75;

uniform float texel_size = 0.002; // 1.0 / 512

void blit() {
	// compute UVs
	vec2 up_l = vec2(UV.x - texel_size, UV.y - texel_size);	
	vec2 up_c = vec2(UV.x, UV.y - texel_size);
	vec2 up_r = vec2(UV.x + texel_size, UV.y - texel_size);	
	vec2 down_l = vec2(UV.x - texel_size, UV.y + texel_size);
	vec2 down_c = vec2(UV.x, UV.y + texel_size);
	vec2 down_r = vec2(UV.x + texel_size, UV.y + texel_size);
	
	// read bloods
	vec2 blood_ul = texture(wet_map, up_l).rg;	
	vec2 blood_uc = texture(wet_map, up_c).rg;
	vec2 blood_ur = texture(wet_map, up_r).rg;	
	vec2 blood_c = texture(wet_map, UV).rg;
	//vec2 blood_dl = texture(wet_map, down_l).rg;
	//vec2 blood_dc = texture(wet_map, down_c).rg;
	//vec2 blood_dr = texture(wet_map, down_r).rg;
	
	// read heights
	float height_ul = texture(height_map, up_l).r * height_mult;
	float height_uc = texture(height_map, up_c).r * height_mult;
	float height_ur = texture(height_map, up_r).r * height_mult;			
	float height_c = texture(height_map, UV).r * height_mult;
	float height_dl = texture(height_map, down_l).r * height_mult;
	float height_dc = texture(height_map, down_c).r * height_mult;
	float height_dr = texture(height_map, down_r).r * height_mult;	
	
	// how many liquid and velocity was lost (down)
		
	// how many liquid was lost? splope = 0 -> max liquid lost
	float slope_dl = (1.0 - abs(height_dl - height_c)) * 0.25;
	float slope_dc = (1.0 - abs(height_dc - height_c)) * 0.5;
	float slope_dr = (1.0 - abs(height_dr - height_c)) * 0.25;
	float flow_lost = blood_c.r * (slope_dl + slope_dc + slope_dr ) * flow_velocity * blood_c.g;
	
	// how many velocity was lost? 
	float velocity_lost = blood_c.g * (slope_dl + slope_dc + slope_dr ) * flow_velocity;

	// how many liquid and velocity was gain (from above)
		
	// how many liquid was gain? splope = 0 -> max liquid gain
	float slope_ul = (1.0 - abs(height_ul - height_c)) * 0.25;
	float slope_uc = (1.0 - abs(height_uc - height_c)) * 0.5;
	float slope_ur = (1.0 - abs(height_ur - height_c)) * 0.25;
	float flow_gain = 
		blood_ul.r * slope_ul * flow_velocity * blood_ul.g +
		blood_uc.r * slope_uc * flow_velocity * blood_uc.g +
		blood_ur.r * slope_ur * flow_velocity * blood_ur.g ;

	// how many velocity was gain? 
	float velocity_gain = 
		blood_ul.g * slope_ul * flow_velocity +
		blood_uc.g * slope_uc * flow_velocity +
		blood_ur.g * slope_ur * flow_velocity;

	// compute final liquid amount
	float final_flow = min(1.0, max(0.0, blood_c.x - flow_lost) + flow_gain);
	
	// compute final velocity 
	float final_velocity = min(1.0, max(0.0, blood_c.y - velocity_lost) + velocity_gain) * velocity_keep;
		
	// write to wet_map B
	COLOR1 = vec4(final_flow, final_velocity, 0.0, 1.0);	

	// generate color/albedo map from wet map
	float mask = smoothstep(mask_min, mask_max, COLOR1.x);
	vec3 albedo_color = mix(background_color, blood_color, mask);		
	COLOR0 = vec4(albedo_color, 1.0);	
} 

// splat draw shader

shader_type texture_blit;
render_mode blend_mix; 

uniform sampler2D splat_tex : hint_blit_source0;

void blit() {
	// read from texture
	float wet_amount = texture(splat_tex, UV).a;	
	// write to wet_map 
	COLOR0 = vec4(
		wet_amount,  // liquid amount
		wet_amount,  // liquid speed
		0.0, 
		wet_amount);
} 
Live Preview
Tags
blood, drawabletexture2d, Fluid, liquid, pingpong, water
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
Inline Feedbacks
View all comments