Sea and Moon

A port to Godot 3.5 of this shader.

I’ve added a lot of uniforms, either by changing already used constants or by deciphering some values from the rest of the code. By doing so, the original shader’s colors have changed, for reasons beyond my comprehension, but other than that it works very well and it’s very easy to customize.

I’ve mantained most of the original code and added some more comments, both for preservation and education sake: this conversion was possible by studying similar ones like this one, so I wanted to make it as easy as possible to learn from it like I did myself.

Despite most shaders from shadertoy, you can use this one freely, without restrictions; let me know if you end up using it!

Shader code
/*
	Original shader by mrange (https://www.shadertoy.com/view/csfXzN)
	Godot 3.5 port with customizable parameters by PmkExpert
	As much as possible of the original has been kept, including comments
	
	MIT License

*/

// License CC0: Sea and moon
//  Tinkering with the colors of an old shaders to make it a better fit for windows terminal

shader_type canvas_item;

const float PI = 3.141592654;
//const float TAU = 6.283185308;

//Kept the original names, but they mostly change the waves' speed
uniform float gravity : hint_range(0.0,25.0) = 1.0;
uniform float waterTension : hint_range(0.0,10.0) = 0.01;

//Generally, the shader is dark, so some colors have less effect than others overall
uniform vec4 skyCol1 : hint_color = vec4(0.6, 0.35, 0.3, 1.0);
uniform vec4 skyCol2 : hint_color = vec4(1.0, 0.3, 0.3, 1.0);
uniform vec4 sunCol1 : hint_color = vec4(1.0, 0.5, 0.4, 1.0);
uniform vec4 sunCol2 : hint_color = vec4(1.0, 0.8, 0.8, 1.0);
uniform vec4 seaCol1 : hint_color = vec4(0.1, 0.2, 0.2, 1.0);
uniform vec4 seaCol2 : hint_color = vec4(0.2, 0.9, 0.6, 1.0);

//New uniforms made of values from the code for easier customization
uniform float sunPosX : hint_range(-1.5,1.5) = 0.0; //The celestial body's position
uniform float sunPosY : hint_range(-0.6,0.4) = 0.06; //The celestial body's altitude
uniform float Zoom : hint_range(0.5,20.0) = 2.5; //How much close is the horizon
uniform float sunShine : hint_range(0.1,100.0) = 1.0; //The amount of light emitted
uniform float sunSize : hint_range(0.1,100.0) = 1.0; //Can make it appear better under sea level than sunPos
uniform int seaNoise : hint_range(1,20) = 8; //Makes the sea appear differently
uniform int crestSize : hint_range(1,20) = 4; //How big the waves' crests look
uniform float seaLevel : hint_range(-0.5,0.5) = -0.1; //The horizon's level
//Some other values in the code can be changed for different effects, but their behavior is less predictable

// License: Unknown, author: Unknown, found: don't remember
float tanh_approx(float x) {
  //  Found this somewhere on the interwebs
  //  return tanh(x);
  float x2 = x*x;
  return clamp(x*(27.0 + x2)/(27.0+9.0*x2), -1.0, 1.0);
}

vec2 wave(in float t, in float a, in float w, in float p) {
  float x = t;
  float y = a*sin(t*w + p);
  return vec2(x, y);
}

vec2 dwave(in float t, in float a, in float w, in float p) {
  float dx = 1.0;
  float dy = a*w*cos(t*w + p);
  return vec2(dx, dy);
}

vec2 gravityWave(in float t, in float a, in float k, in float h) {
  float w = sqrt(gravity*k*tanh_approx(k*h));
  return wave(t, a ,k, w*TIME);
}

vec2 capillaryWave(in float t, in float a, in float k, in float h) {
  float w = sqrt((gravity*k + waterTension*k*k*k)*tanh_approx(k*h));
  return wave(t, a, k, w*TIME);
}

vec2 gravityWaveD(in float t, in float a, in float k, in float h) {
  float w = sqrt(gravity*k*tanh_approx(k*h));
  return dwave(t, a, k, w*TIME);
}

vec2 capillaryWaveD(in float t, in float a, in float k, in float h) {
  float w = sqrt((gravity*k + waterTension*k*k*k)*tanh_approx(k*h));
  return dwave(t, a, k, w*TIME);
}

void mrot(inout vec2 p, in float a) {
  float c = cos(a);
  float s = sin(a);
  p = vec2(c*p.x + s*p.y, -s*p.x + c*p.y);
}

vec4 sea(in vec2 p, in float ia) {
  float y = 0.0;
  vec3 d = vec3(0.0);

  int maxIter = seaNoise;
  int midIter = crestSize;

  float kk = 1.0/1.3;
  float aa = 1.0/(kk*kk);
  float k = 1.0*pow(kk, -float(maxIter) + 1.0);
  float a = ia*0.25*pow(aa, -float(maxIter) + 1.0);

  float h = 25.0;
  p *= 0.5;
  
  vec2 waveDir = vec2(0.0, 1.0);

  for (int i = midIter; i < maxIter; ++i) {
    float t = dot(-waveDir, p) + float(i);
    y += capillaryWave(t, a, k, h).y;
    vec2 dw = capillaryWaveD(-t, a, k, h);
    
    d += vec3(waveDir.x, dw.y, waveDir.y);

    mrot(waveDir, PI/3.0);

    k *= kk;
    a *= aa;
  }
  
  waveDir = vec2(0.0, 1.0);

  for (int i = 0; i < midIter; ++i) {
    float t = dot(waveDir, p) + float(i);
    y += gravityWave(t, a, k, h).y;
    vec2 dw = gravityWaveD(t, a, k, h);
    
    vec2 d2 = vec2(0.0, dw.x);
    
    d += vec3(waveDir.x, dw.y, waveDir.y);

    mrot(waveDir, -step(2.0, float(i)));

    k *= kk;
    a *= aa;
  }

  vec3 t = normalize(d);
  vec3 nxz = normalize(vec3(t.z, 0.0, -t.x));
  vec3 nor = cross(t, nxz);

  return vec4(y, nor);
}

vec3 sunDirection() {
  vec3 dir = normalize(vec3(sunPosX, sunPosY, 1));
  return dir;
}

vec3 skyColor(in vec3 rd) {
  //Added code to change the colors to be modifiable shader params
  vec3 skyCol1_ = skyCol1.xyz * 0.5;
  vec3 skyCol2_ = skyCol2.xyz * 0.5;
  
  vec3 sunDir = sunDirection();
  float sunDot = max(dot(rd, sunDir), 0.0);
  vec3 final = vec3(0.0);
  final += mix(skyCol1_, skyCol2_, rd.y);
  final += 0.5*sunCol1.rgb*pow(sunDot, 90.0 / sunShine);
  final += 4.0*sunCol2.rgb*pow(sunDot, 900.0 / sunSize);
  return final;
}

vec3 render(in vec3 ro, in vec3 rd) {
  //Added code to change the colors to be modifiable shader params
  vec3 seaCol1_ = seaCol1.rgb * 0.2;
  vec3 seaCol2_ = seaCol2.rgb * 0.5;
  
  vec3 col = vec3(0.0);

  float dsea = (0.0 - ro.y)/rd.y;
  
  vec3 sunDir = sunDirection();
  
  vec3 sky = skyColor(rd);
  
  if (dsea > 0.0) {
    vec3 p = ro + dsea*rd;
    vec4 s = sea(p.xz, 1.0);
    float h = s.x;    
    vec3 nor = s.yzw;
    nor = mix(nor, vec3(0.0, 1.0, 0.0), smoothstep(0.0, 200.0, dsea));

    float fre = clamp(1.0 - dot(-nor,rd), 0.0, 1.0);
    fre = fre*fre*fre;
    float dif = mix(0.25, 1.0, max(dot(nor,sunDir), 0.0));
    
    vec3 refl = skyColor(reflect(rd, nor));
    vec3 refr = seaCol1_ + dif*sunCol1.rgb*seaCol2_*0.1; 
    
    col = mix(refr, 0.9*refl, fre);
    
    float atten = max(1.0 - dot(vec2(dsea),vec2(dsea)) * 0.001, 0.0);
    col += seaCol2_*(p.y - h) * 2.0 * atten;
    
    col = mix(col, sky, 1.0 - exp(-0.01*dsea));
    
  } else {
    col = sky;
  }
  
  return col;
}

void fragment() {
  vec2 q = FRAGCOORD.xy/(1.0 / SCREEN_PIXEL_SIZE).xy;
  vec2 p = -1. + 2. * q;
  p.x *= (1.0 / SCREEN_PIXEL_SIZE).x/(1.0 / SCREEN_PIXEL_SIZE).y;

  vec3 ro = vec3(0.0, 10.0, 0.0);
  vec3 ww = normalize(vec3(0.0, seaLevel, 1.0));
  vec3 uu = normalize(cross( vec3(0.0,1.0,0.0), ww));
  vec3 vv = normalize(cross(ww,uu));
  vec3 rd = normalize(p.x*uu + p.y*vv + Zoom*ww);

  vec3 col = render(ro, rd);

  COLOR.rgb = vec3(col);
  COLOR.a = texture(TEXTURE, UV).a;
}
Tags
2d, canvas item, horizon, moon, parameters, sea, Sun
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 PmkExpert

Hexagonal tiling + cog wheels

Floating Bubbles

Related shaders

Moon Phase

Rain and Snow with Parallax Effect

Animated Noise and Sprite Mixer

Subscribe
Notify of
guest

3 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Kikkie
Kikkie
1 year ago

Very nice, i jsut have a small issue, shader is upside down for some reason in godot 4

Ethan
4 months ago
Reply to  Kikkie

I’m very late lol, but simply write p.y = -p.y, which flips the image