Procedural Pixelated Sea Shader

Perfect for side-scroller or parallax backdrops! Assumes that you have a global uniform named `WORLD_SUN_POSITION` for the sun’s reflection across the water, that contains a vector normalized between 0-1 in terms of both its x and y components.

Here is a material with some colours and noise settings preset. Please be sure to modify it to contain the path of the shader!

“`
[gd_resource type=”ShaderMaterial” load_steps=7 format=3 uid=”uid://3yylpnpe1olk”]

[ext_resource type=”Shader” uid=”uid://4vldjefc7u33″ path=”INSERT SHADER PATH HERE” id=”1_odgra”]

[sub_resource type=”FastNoiseLite” id=”FastNoiseLite_gwerp”]
noise_type = 2
frequency = 0.04
fractal_type = 0

[sub_resource type=”NoiseTexture2D” id=”NoiseTexture2D_a3l0h”]
width = 1024
height = 1024
seamless = true
noise = SubResource(“FastNoiseLite_gwerp”)

[sub_resource type=”NoiseTexture2D” id=”NoiseTexture2D_c6jme”]
width = 1020
height = 1020
seamless = true
noise = SubResource(“FastNoiseLite_gwerp”)

[sub_resource type=”FastNoiseLite” id=”FastNoiseLite_ij666″]
fractal_type = 3

[sub_resource type=”NoiseTexture2D” id=”NoiseTexture2D_s04fh”]
width = 640
height = 640
seamless = true
noise = SubResource(“FastNoiseLite_ij666”)

[resource]
shader = ExtResource(“1_odgra”)
shader_parameter/front_colour = Color(0.254902, 0.843137, 0.843137, 1)
shader_parameter/middle_colour = Color(0.0784314, 0.627451, 0.803922, 1)
shader_parameter/back_colour = Color(0.0784314, 0.411765, 0.764706, 1)
shader_parameter/layer_distort = 0.5
shader_parameter/sin_scale = 1
shader_parameter/highlights_threshold = 0.85
shader_parameter/reflection_strength = 0.25
shader_parameter/refraction_strength = 0.08
shader_parameter/sun_refraction_scale = 0.195
shader_parameter/sun_refraction_mag_min = 0.8
shader_parameter/sun_refraction_mag_max = 0.9
shader_parameter/refraction_colour_split = 0.03
shader_parameter/godot_light_discount = 0.5
shader_parameter/layer_ID = 3
shader_parameter/height = 0.36
shader_parameter/saturation = 1.1
shader_parameter/waves = SubResource(“NoiseTexture2D_s04fh”)
shader_parameter/highlights_0 = SubResource(“NoiseTexture2D_a3l0h”)
shader_parameter/highlights_1 = SubResource(“NoiseTexture2D_c6jme”)
“`

Shader code
shader_type canvas_item;


// Global
uniform sampler2D SCREEN_TEXTURE: hint_screen_texture, filter_nearest;

global uniform vec2 WORLD_SUN_POSITION;

// Fluid
uniform vec4  front_colour: source_color;
uniform vec4  middle_colour: source_color;
uniform vec4  back_colour: source_color;
uniform float layer_distort: hint_range(0.0, 1.0) = 0.5;
uniform uint  sin_scale = 1;

uniform float highlights_threshold:    hint_range(0.0, 1.0) = 0.85;
uniform float reflection_strength:     hint_range(0.0, 1.0) = 0.25;
uniform float refraction_strength:     hint_range(0.0, 1.0) = 0.0;
uniform float sun_refraction_scale:    hint_range(0.0, 1.0) = 0.195;
uniform float sun_refraction_mag_min:  hint_range(0.0, 1.0) = 0.8;     // Afternoon
uniform float sun_refraction_mag_max:  hint_range(0.0, 1.0) = 0.9;     // Sunrise/Sunset
uniform float refraction_colour_split: hint_range(0.0, 1.0) = 0.03;

uniform sampler2D waves;
uniform sampler2D highlights_0: hint_default_black;
uniform sampler2D highlights_1: hint_default_black;   // This texture should be a copy of the above, with a slightly smaller resolution.

// Backdrop
uniform float height = 0.36;
uniform float saturation: hint_range(0.0, 2.0) = 1.0;


// Called for every pixel the material is visible on.
void fragment() {
	
	// If we're above a certain height, then discard.
	// Also determine the in-fluid UV.
	float total_height = height;   // Used to be another parameter here...
	if (1.0 - UV.y > total_height)
		discard;
	vec2 fluid_uv             = vec2(UV.x, (UV.y - (1.0 - total_height)) / (1.0 - (1.0 - total_height)));
	vec2 undistorted_fluid_uv = fluid_uv;
	
	// Distort the fluid UV.
	float wave  = texture(waves, undistorted_fluid_uv).r;  // +-
	fluid_uv.x += wave * layer_distort;
	
	// Get the fluid's colour, which is basically two sine waves over a background colour, distorted with a noise texture.
	vec4  colour     = middle_colour;   // TODO: Original multiplier was 10x, modified it to 2 PI for trig reasons.
	float front_wave = (sin( TIME * 0.25 + fluid_uv.x * (PI * 2.0) * float(sin_scale) + 0.0) + 1.0) * 0.5;
	float back_wave  = (sin(-TIME * 0.25 + fluid_uv.x * (PI * 2.0) * float(sin_scale) + 0.5) + 1.0) * 0.5;
	float mag        = 1.0 / 3.0;
	
	bool back_colour_used = false;
	if (fluid_uv.y - mag > front_wave * mag)
		colour = front_colour;
	if (fluid_uv.y < back_wave * mag) {
		colour           = back_colour;
		back_colour_used = true;
	}
	
	// Add highlights to the edges of distant waves.
	float highlights_0_n = (texture(highlights_0, fract(undistorted_fluid_uv + vec2(0.0, 0.0) + TIME * vec2(-0.01, -0.01))).r + 1.0) * 0.5;
	float highlights_1_n = (texture(highlights_1, fract(undistorted_fluid_uv + vec2(0.5, 0.0) + TIME * vec2( 0.01,  0.01))).r + 1.0) * 0.5;
	if ((highlights_0_n + highlights_1_n) * 0.5 > highlights_threshold)
		colour = vec4(1.0);
	
	// Add screen reflections with refractions.
	float refraction_0     = texture(waves, fract(undistorted_fluid_uv + TIME * vec2(-0.01, 0.0))).r;
	float refraction_1     = texture(waves, fract(undistorted_fluid_uv + TIME * vec2( 0.01, 0.0))).r;
	float refraction       = (refraction_0 + refraction_1) * 0.5;
	vec2 reflect_uv        = vec2(SCREEN_UV.x, mix(1.0 - total_height, 0.0, undistorted_fluid_uv.y));
	     reflect_uv.x     += refraction * refraction_strength;
	     reflect_uv.x     -= refraction_strength * 0.5;
	vec4 screen            = texture(SCREEN_TEXTURE, reflect_uv);
	colour = mix(colour, screen, reflection_strength);
	
	// Add the sun's refraction across the surface of the fluid.
	float proximity_to_sun = distance(WORLD_SUN_POSITION.x, SCREEN_UV.x);
	float refraction_sun   = (refraction + 1.0) * 0.5 * sun_refraction_scale;
	if (refraction_sun > proximity_to_sun) {
		float strength = (refraction_sun - proximity_to_sun) / (1.0 - proximity_to_sun);
		float magnitude;
		
		if (1.0 - WORLD_SUN_POSITION.y < total_height) {   // Sun below horizon.
			float how_far_under = (WORLD_SUN_POSITION.y - (1.0 - total_height)) / total_height;
			magnitude = mix(sun_refraction_mag_max, 0.0, smoothstep(0.0, 1.0, how_far_under));
		} else {   // Sun above horizon.
			float how_far_above = ((1.0 - total_height) - WORLD_SUN_POSITION.y);
			magnitude = mix(sun_refraction_mag_max, sun_refraction_mag_min, smoothstep(0.0, 1.0, how_far_above));
		}
		
		if (strength > (1.0 - magnitude)) {
			float refractivity = (strength - (1.0 - magnitude)) / (1.0 - (1.0 - magnitude));
			vec4  lower_colour = front_colour;
			
			if (back_colour_used)
				lower_colour = mix(middle_colour, screen, reflection_strength);
			
			colour = mix(lower_colour, vec4(1.0), step(refraction_colour_split, refractivity));
		}
	}
	
	// SATURATION
	// positive = more saturation
	// negative = less saturation
	// 0.0 = unchanged
	// -1.0 = grayscale
	// < -1.0 = color inversion
	float saturation_scale = mix(-1.0, 0.0, saturation);
	float average = (colour.x + colour.y + colour.z) / 3.0;
	float xd = average - colour.r;
	float yd = average - colour.g;
	float zd = average - colour.b;
	colour.r += xd * -saturation_scale;
	colour.g += yd * -saturation_scale;
	colour.b += zd * -saturation_scale;
	
	COLOR = colour;
}
Tags
2d, Backdrop, pixel-art, Pixelated, stylized, 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 LunaticWyrm467

Pixel Cloud Shader

Related shaders

Sea water with 4 layers.

Sea and Moon

Pixelated Chosen Color With Dithering

Subscribe
Notify of
guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments