Stylized Toon Water

This shader uses color banding assigned based on the angle between the normals of the mesh and the camera. Works well for water VFX as well as large bodies of water.

Supports variable flow speeds, custom colors, variable banding bias, and variable brightness for bloom and glow.

Instructions:

Add perlin noise with a low octave value, for the texture and wave noise

Shader code
shader_type spatial;
render_mode cull_disabled, blend_add, unshaded;
uniform float time_speed = 1.0;
//time specifically for the wave noise texture
uniform float surface_speed = 1.0;
uniform float spin = 0.0; //Twisting motion of the water
uniform float brightness = 0.6;
uniform float color_intensity = 0.0;
//Tiling frequency of the noise accross the mesh
uniform float horizontal_frequency = 2.0;
uniform float vertical_frequency = 2.0;
//overall size muliplier
uniform float size = 3.0;
//affects total size
uniform float banding_bias = 0.6;

uniform sampler2D wave_texture;
uniform sampler2D noise_texture;
//wave height, use for ocean waves
uniform float wave_height = 0.5;
//water surface height variation based on the noise texture
uniform float texture_height = 0.5;
//preset band colors
uniform vec4 color1 : source_color = vec4(0.59, 0.761, 1.0, 1.0);
uniform vec4 color2 : source_color = vec4(0.274, 0.474, 0.98, 1.0);
uniform vec4 color3 : source_color = vec4(0.059, 0.389, 0.85, 1.0);
uniform vec4 color4 : source_color = vec4(0.0, 0.267, 1.0, 1.0);

void vertex() {
	float time = -TIME * time_speed;
	VERTEX += NORMAL * wave_height * texture(wave_texture, vec2(UV.x + time * surface_speed, UV.y + time * surface_speed)).r;
	VERTEX += NORMAL * texture_height * texture(noise_texture,vec2(UV.x * horizontal_frequency + spin * (time /2.0), (UV.y * vertical_frequency) + time)).r;;
}

void fragment() {
	float time = -TIME * time_speed;

// Calculate dot product of normals and combine with noise texture value
	float normal_facing = dot(NORMAL, VIEW);
	float noise_value = texture(noise_texture,vec2(UV.x * horizontal_frequency + spin * (time /2.0), (UV.y * vertical_frequency) + time)).r;
	normal_facing += (noise_value -0.5 + size) * 0.3;

	float band = normal_facing * 3.0 * banding_bias;
	vec4 band_color = vec4(0,0,0,0);
	if (band <= 1.5) {
		discard;
	}
	else if(band <= 2.0){
		band_color = mix(color1, color2, -0.01 / (band-2.01)); //Mixes the color bands to make a slight gradient
	}
	else if (band <= 2.5) {
		band_color = mix(color2, color3, -0.01 / (band-2.51));
	}
	else if (band <= 2.9) {
		band_color = mix(color3, color4, -0.01 / (band-2.91));
	}
	else if (band >= 0.0) {
		band_color = color4;
	}
	//Final color adjestment
	ALBEDO = clamp(brightness * (vec3(1.0, 1.0, 1.0) - (band_color.xyz * -color_intensity)) * band_color.xyz, vec3(0.0, 0.0, 0.0), vec3(brightness, brightness, brightness));
}
Tags
bubble, toon, water
The shader code and all code snippets in this post are under MIT license and can be used freely. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Thundergecko8

Stylized Flame

Related shaders

Toon Water Shader

fBm Toon Water

toon Water shader Godot 4.4+

guest

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
zpecc
zpecc
3 months ago

Looks absolutely fantastic. Especially for VFX like balls of water and the like.
That said, I did notice that it can appear very different in brightly lit vs dark environments. If you set it up to look good in a dark place, the same settings can look extremely bright in a bright place, and vice versa. But I guess that’s the nature of blend_add.
Nevertheless, it looks great.