2D Reflective Water

A Shader to create a reflective Water Effect in 2D. Apply it to a ColorRect Node which is the size of the canvas.

 

Shader code
// Kitchen Games / 2D Reflective Water Shader

shader_type canvas_item;

uniform float horizon : hint_range(0.0,1.0); // The height of the horizon
uniform sampler2D noise; // add two noises. Play with these some time to get a decent result
uniform sampler2D noise2;
uniform float wave_frequency : hint_range(0.0, 100.0);
uniform float wave_magnitude : hint_range(0.0, .3);
uniform float tides_magnitude : hint_range(0.0, .3);
uniform float noise_wave : hint_range(0.0, 3.0); // add noisiness to waves
uniform float tides_speed : hint_range(0.0, 20.0);
uniform float wave_speed : hint_range(0.0, 20.0);

uniform float shine_position : hint_range(0.0, 1.0); // configure a sunshine if you want
uniform float shine_itensity : hint_range(0.0, 1.0);
uniform float shine_width : hint_range(0.0, 1.0);
uniform vec4 shine_color : hint_color;

uniform vec4 water_color : hint_color;

void fragment()
{	
	vec2 noise_uv = vec2(SCREEN_UV.x + TIME * 0.025, SCREEN_UV.y);
	float noise_val = texture(noise, noise_uv).r * texture(noise2, SCREEN_UV).r + 0.2;
	
	float wave = horizon;
	wave += sin(UV.x * wave_frequency + TIME* wave_speed) * wave_magnitude;
	wave += sin(TIME * tides_speed) * tides_magnitude;
	wave -= texture(noise2, SCREEN_UV).r * 0.05 * noise_wave;
	
	vec2 offset = vec2(0, (wave + abs(SCREEN_UV.y - wave)) - SCREEN_UV.y);

	offset += noise_val* 0.1 - 0.05;
	vec2 col_uv = SCREEN_UV;
	
	col_uv.y = col_uv.y + (offset.y * step(SCREEN_UV.y, wave));
	
	vec4 col = texture(SCREEN_TEXTURE, col_uv);
	col = mix(col, water_color,  step(SCREEN_UV.y, wave) * 0.5);
	col = mix(col, shine_color, step(SCREEN_UV.y, wave) * (step(noise_val + abs(SCREEN_UV.x - shine_position), shine_width)) * shine_itensity);
	COLOR = col;
}
Tags
Water Reflection Wave
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

2D reflective ice

water waves 2d

Sprite Water Reflection Pixel Art Pokémon Style

Subscribe
Notify of
guest

14 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
jun
jun
2 years ago

but……how can i use it? i add it to a Control Node and Nothing happened

CC Cosmos
2 years ago
Reply to  jun

Add it to a colorRect as the child of a Canvas Layer. (That trick works for basically every Screen Texture/UV based shader like this)

This particular shader changes its perspective drastically based on your camera position. Make sure to tweak it by looking at it in-game to see what it looks like relative to the in-game camera.

JUN
JUN
2 years ago
Reply to  CC Cosmos

Thanks a lot

Mel
Mel
1 year ago
Reply to  JUN

This looks awesome, but as my camera pans around to follow the character (especially upwards) and as doing this the water level changes. Anyway to fix the water level regardless of camera movement?

HobbesHK
1 year ago

I was wondering if anyone had gotten this to work on Godot 4 yet? It looks amazing, but refuses to do anything for me with an error on (at least) line 16 and 18:

“Expected valid type hint after ‘:’.

I also believe SCREEN_TEXTURE has changed into something else? Any help would be much appreciated!!

Gabriele
1 year ago
Reply to  HobbesHK

In GODOT4 SCREEN_TEXTURE was removed- You can substitute it with

uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;

then line 39 became

vec4 col = texture(SCREEN_TEXTURE, col_uv);
Pancake
Pancake
7 months ago

I got it to work in Godot 4 but what ever I do I cant get it to flip horizontially. I have no idea why the waves are breaking from above rather than from below

Greg
Greg
6 months ago
Reply to  Pancake

Yeah, it’s upside down as posted here. Needs to be flipped vertically.

matsakis27
matsakis27
29 days ago
Reply to  Greg

Is there a good way to flip it vertically? Using transform isn’t working too well…

Dooley
6 months ago

How would I stop the water line from following the screen vertically?

Dooley
6 months ago
Reply to  Dooley

Change the screen_uv to simply uv.

matsakis27
matsakis27
29 days ago

I’m trying to come up with a Godot 4.2 version of this that is flipped horizontally so the water is on the bottom of the screen. So far I’ve been able to flip the water texture by swapping UV.y and Wave on line 37 so it reads:

col_uv.y = col_uv.y – (offset.y * step(wave, UV.y));

I’m not able to get the reflection to work though… any ideas as to why?
Any help is much appreciated, I’m new to shaders and trying to learn as much as possible.

matsakis27
matsakis27
28 days ago
Reply to  matsakis27

Okay so I’ve done a bit more research and found that might not be on the correct path with flipping the variables in the step functions… col_uv is the reflection/distortion effect. I’m trying to understand how it’s sampling the screen and placing it somewhere else. I’ll continue to do research but here’s the code for the shader with it flipped on the screen. Although the reflections don’t work… any ideas?

shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
uniform float horizon : hint_range(0.0,1.0); // The height of the horizon
uniform sampler2D noise; // add two noises. Play with these some time to get a decent result
uniform sampler2D noise2;
uniform float wave_frequency : hint_range(0.0, 100.0);
uniform float wave_magnitude : hint_range(0.0, .3);
uniform float tides_magnitude : hint_range(0.0, .3);
uniform float noise_wave : hint_range(0.0, 3.0); // add noisiness to waves
uniform float tides_speed : hint_range(0.0, 20.0);
uniform float wave_speed : hint_range(0.0, 20.0);

uniform float shine_position : hint_range(0.0, 1.0); // configure a sunshine if you want
uniform float shine_itensity : hint_range(0.0, 1.0);
uniform float shine_width : hint_range(0.0, 1.0);
uniform vec4 shine_color : source_color;

uniform vec4 water_color : source_color;

void fragment()
{
vec2 noise_uv = vec2(SCREEN_UV.x + TIME * 0.025, SCREEN_UV.y);
float noise_val = texture(noise, noise_uv).r * texture(noise2, SCREEN_UV).r + 0.2;

float wave = horizon;
wave += sin(UV.x * wave_frequency + TIME* wave_speed) * wave_magnitude;
wave += sin(TIME * tides_speed) * tides_magnitude;
wave -= texture(noise2, SCREEN_UV).r * 0.05 * noise_wave;

vec2 offset = vec2(0, (wave + (SCREEN_UV.y – wave)) – SCREEN_UV.y);
//taking out abs flips what it’s sampling, now I just need it to draw everything on the bottom side
offset += noise_val* 0.1 – 0.05;

vec2 col_uv = SCREEN_UV;//the distortion effect

//wave is the threshold, screenUV.y is what we’re testing
//wave is a float thats being modified by lots of noise and shaping functions
//offset is a vec2 thats y value represents?
col_uv.y = col_uv.y + (SCREEN_UV.y, wave));
//I’ve tried swapping between these two orders of variables in the step functions
//wave, SCREEN_UV.y
//SCREEN_UV.y, wave
vec4 col = texture(screen_texture, col_uv);//applying the distortion effect to the texture
col = mix(col, water_color, step(wave, SCREEN_UV.y) * 0.5);
col = mix(col, shine_color, step(wave, SCREEN_UV.y) * (step(noise_val + abs(SCREEN_UV.x – shine_position), shine_width)) * shine_itensity);
COLOR = col;
}

Eduardo Buenno
Eduardo Buenno
22 days ago

Fixed version for godot 4.2:

shader_type canvas_item;




uniform sampler2D SCREEN_TEXTURE : hint_screen_texture, filter_linear_mipmap;

uniform float horizon : hint_range(0.0,1.0); // The height of the horizon

uniform sampler2D noise; // add two noises. Play with these some time to get a decent result

uniform sampler2D noise2;

uniform float wave_frequency : hint_range(0.0, 100.0);

uniform float wave_magnitude : hint_range(0.0, .3);

uniform float tides_magnitude : hint_range(0.0, .3);

uniform float noise_wave : hint_range(0.0, 3.0); // add noisiness to waves

uniform float tides_speed : hint_range(0.0, 20.0);

uniform float wave_speed : hint_range(0.0, 20.0);




uniform float shine_position : hint_range(0.0, 1.0); // configure a sunshine if you want

uniform float shine_intensity : hint_range(0.0, 1.0);

uniform float shine_width : hint_range(0.0, 1.0);

uniform vec4 shine_color : source_color;




uniform vec4 water_color : source_color;




void fragment() {

  vec2 noise_uv = vec2(SCREEN_UV.x + TIME * 0.025, SCREEN_UV.y);

  float noise_val = texture(noise, noise_uv).r * texture(noise2, SCREEN_UV).r + 0.2;




  float wave = horizon;

  wave += sin(UV.x * wave_frequency + TIME * wave_speed) * wave_magnitude;

  wave += sin(TIME * tides_speed) * tides_magnitude;

  wave -= texture(noise2, SCREEN_UV).r * 0.05 * noise_wave;




  vec2 offset = vec2(0, (wave + (SCREEN_UV.y - wave)) - SCREEN_UV.y);

  offset += noise_val * 0.1 - 0.05;




  vec2 col_uv = SCREEN_UV;

  col_uv.y = col_uv.y + (offset.y * step(wave, SCREEN_UV.y));




  vec4 col = texture(SCREEN_TEXTURE, col_uv);

  col = mix(col, water_color, step(wave, SCREEN_UV.y) * 0.5);

  col = mix(col, shine_color, step(wave, SCREEN_UV.y) * step(noise_val + abs(SCREEN_UV.x - shine_position), shine_width) * shine_intensity);




 // Calculate reflection

  vec2 reflection_uv = vec2(col_uv.x, 2.0 * horizon - col_uv.y); // Reflect vertically around the horizon

  vec4 reflection_color = texture(SCREEN_TEXTURE, reflection_uv);




  // Blend reflection with water color only below horizon

  if (SCREEN_UV.y > horizon) {

    col = mix(col, reflection_color, 0.3); // Adjust blend factor as needed

  }




  COLOR = col;

}