2D waterfall
Make a waterfall from a Sprite node. Resize the sprite to resize the waterfall. See it in action in the screenshots below.
There are several uniforms that can be set:
- Refraction Map: Noise texture for the refraction effect. Use the built-in SimplexNoise.
- Water Mask: Noise texture for the flow gaps. Use the built-in SimplexNoise.
- Gap Stretch: Squeeze the texture to get the shape of the gaps as you like
- Refraction Stretch: Squeeze the refraction texture to get the effect that you like
- Refraction Strength: How intense you want the refraction effect to be
- Water Tint: The color of the waterfall. Use alpha to lower intensity
- Water Highlight: The color of gap edges. Use alpha to lower intensity
- Speed: The speed of the flow
- Flow Gaps: Size of the gaps
- Highlight width: Size of the gap edges that will be colored with Water Highlight
Instructions
Add a Sprite and set any texture to it (icon.png perhaps). Create the shader with the code below and set a new NoiseTexture with SimplexNoise to both Refraction Map and Water Mask. Check the Seamless parameter.
To make the waterfall behave the way you expect when rescaling and zooming you will have to add a bit of code to the Sprite and connect the item_rect_changed()
signal. Create a new script for the Sprite and add the code below, don’t forget to connect the signal.
tool
extends Sprite
func _process(delta):
get_material().set_shader_param("zoom", get_viewport_transform().y.y)
#Connect the item_rect_changed() signal to this function
func _on_Waterfall_item_rect_changed():
get_material().set_shader_param("scale", scale)
Don’t forget to add the tool
keyword at the top of the script or the changes won’t be seen in the editor.
(The zoom effect is not consistent for all viewport settings when going from editor to the game. Perhaps someone know a better way to fix this)
That’s it! Play around with the different uniform values to get the waterfall look you want.
It’s not working
If it’s not working now, it is most likely because the scale
and zoom
parameters are not getting data from the script. Try reloading the scene and then resizing the sprite.
Shader code
/*
Shader from Godot Shaders - the free shader library.
godotshaders.com/shader/2d-waterfall
Feel free to improve and change this shader according to your needs
and consider sharing the modified result on godotshaders.com.
*/
shader_type canvas_item;
uniform vec2 scale; // Used for sprite script. Don't edit this value in the inspector.
uniform float zoom; // Used for sprite script. Don't edit this value in the inspector.
uniform sampler2D refraction_map;
uniform sampler2D water_mask;
uniform vec2 gap_stretch = vec2(0.8, 0.05);
uniform vec2 refraction_stretch = vec2(2.0, 0.8);
uniform float refraction_strength : hint_range(0.0, 0.1) = 0.02;
uniform vec4 water_tint : hint_color = vec4(0.2, 0.6, 1.0, 0.1);
uniform vec4 water_highlight : hint_color = vec4(1.0, 1.0, 1.0, 0.3);
uniform float speed = 1.0;
uniform float flow_gaps : hint_range(0.0, 1.0) = 0.33;
uniform float highlight_width : hint_range(0.0, 0.3) = 0.02;
void fragment()
{
// Get the two noise textures and make them move on the y axis. The gaps move twice as fast as the refraction, but you can tweak this by changing (speed * 0.5)
vec2 refraction_offset = texture(refraction_map, vec2(UV.x, UV.y + -TIME * speed * 0.5) * scale * refraction_stretch).xy;
vec2 gap_mask = texture(water_mask, vec2(UV.x, UV.y + -TIME * speed) * scale * gap_stretch).xy;
// Set values between -0.5 and 0.5 (instead of 0 and 1). Otherwise the reflection will move whith increased refraction_strength
refraction_offset -= 0.5;
// Get the screen texture and distort it
vec4 refraction = texture(SCREEN_TEXTURE, SCREEN_UV - refraction_offset * refraction_strength * zoom);
// Create holes and apply colors and textures //
vec4 color = vec4(1.0);
// Define what values will be the water highlight color (the gap border)
float inner_edge = flow_gaps + highlight_width;
// See if the pixel is within the edges range and use the water colors alpha to blend between showing color or refraction texture.
if (gap_mask.x < inner_edge)
{
color.rgb = mix(refraction.rgb, water_highlight.rgb, water_highlight.a);
}
else
{
color.rgb = mix(refraction.rgb, water_tint.rgb, water_tint.a);
}
// If the value is below the gap threshhold make it transparent (a gap)
if (gap_mask.x < flow_gaps)
{
color.a = 0.0;
}
// Crate Edge Shape //
// Set the shape for the top and bottom edges. Use water_mask as shape but with other values to flatten it out horizontally.
vec2 water_edge = texture(water_mask, vec2(UV.x, UV.y + -TIME * 0.1) * scale * vec2(0.15, 0.6)).xy;
water_edge -= 0.5;
// Use the same mask as for the gaps for left and right edge.
vec2 vertical_edge_mask = gap_mask - 0.5;
// Apply the new masks to the edges. This will make the wobble effect.
color.a = mix(0.0, color.a, step(UV.x + vertical_edge_mask.x * 0.2, 0.92)); // Right edge
color.a = mix(color.a, 0.0, step(UV.x - vertical_edge_mask.x * 0.2, 0.08)); // Left edge
color.a = mix(0.0, color.a, step(UV.y + water_edge.y * 0.1, 0.95)); //Bottom edge
color.a = mix(color.a, 0.0, step(UV.y - water_edge.y * 0.05, 0.05)); //Top edge
COLOR = color;
}
Thanks for making this! Unfortunately, after about an hour of trying, I was unable to make anything like the demo video. It is not trivial to pick the simplex noise parameters and the other parameters to make the effect work. Consider posting a runnable example, or try creating a new project from scratch to test your instructions and see how easy it is to make it work, then update the instructions.
Hi! Sorry for the unclear instructions. I’ve made some changes to the description. The sprite needs to have a base texture. You can drag any image to the Texture property of the Sprite. It doesn’t matter what it is since the shader will override it anyway. It also seems like the script won’t update even if the ‘tool’ keyword is added. Don’t know why that is. For me, it worked by closing and opening the scene to restart the script, and then rescale the Sprite a little bit to push the ‘scale’ value to the shader. Hope this will solve it for you! Thank you for pointing this out!
This is great! I hijacked it a little bit to get a hologram effect. I changed the end of the shader code to this:
And I set the settings to have a very low gap stretch x and high gap stretch y. Set to a negative speed. On noise, I chose a low period (18.6), high lacunarity (4) and low persistence (0.056). I would be happy to share some screenshots and/or video of the effect!
Nice to hear that you could use the shader and modify it to your needs. I would love to see what you have done with it. I would recommend that you submit your changes as a new shader post, with some images or video of how it looks and works.
I really like the shader! Is there a way to pin the very top edge of the waterfall to make it look like it’s not moving?
Thank you! Yes, comment out or remove line 78, which has a comment saying ‘Top edge’. Those four lines, 74-78, creates the wobbly edges.
Thank you for posting this! After following the instructions, all I see is a transparent blue rectangle with no movement in it.
Do you need to set the simplex parameters, or should they be left as default, to re-create your demo?
Would it be possible to post a working project?
Hi!
If nothing is happening make sure you have added the script to the sprite. Then try closing and opening the scene. It should reload the script. You might have to rescale the sprite a little bit to push the scale values from the script to the shader.
All shader parameter’s default values should give a good starting point so you shouldn’t have to change anything to get it to work. The ‘scale’ and ‘zoom’ parameters have to get data from the script. If nothing works, try setting them to 1 just to see if something happens.
Why it is not working on Android?
Thanks a lot!
This is by far the best 2D waterfall shader I was able to find in the internet.
This Shader looks awesome in my game! thanks
That’s pretty amazing. 🙂
我用ColorRect替代,可以节省纹理。
Script里用rect_scale替代scale。
其他不需要改动,很好的Shader。
I’m trying to make this work in Godot 4.
I had to add this at the top:
I also changed hint_color to source_color.
The problem I am running into is that the noise textures are not animating. Does anyone know what adjustments I would have to make in order to make this work with Godot 4+?
I figured it out.
Needs to be revised to:
So the updated code for Godot 4 looks like this: