Noon Break Mountains

Noon Break Mountains is a GLSL WebGL2 shader study that builds a calm midday landscape from layered silhouettes, a soft sun glow, and subtle atmospheric haze. This page is for anyone who wants to view the loop, download the renders, and learn from a readable, self-contained fragment shader.

Preview

Downloads

  • Raw GIF (original loop)
  • Video (MP4) (smaller, smoother playback)
  • Video (OGV) (alternate format)
  • Preview (PNG) (still frame)

Launch in editor

Source code (GLSL ES 3.00)

This is a clean reconstruction of the original idea: a midday sky gradient, a bright sun with a gentle halo, and multiple mountain layers blended front-to-back. It is intended to be easy to tweak (palette, layer spacing, and motion) while keeping the same overall “noon break” mood.

#version 300 es
precision highp float;

in vec2 UV;
uniform float time;
uniform float ratio;
out vec4 out_color;

float hash21(vec2 p){
  p = fract(p * vec2(123.34, 456.21));
  p += dot(p, p + 34.345);
  return fract(p.x * p.y);
}

float noise(vec2 p){
  vec2 i = floor(p);
  vec2 f = fract(p);
  float a = hash21(i);
  float b = hash21(i + vec2(1.0, 0.0));
  float c = hash21(i + vec2(0.0, 1.0));
  float d = hash21(i + vec2(1.0, 1.0));
  vec2 u = f * f * (3.0 - 2.0 * f);
  return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
}

float fbm(vec2 p){
  float v = 0.0;
  float a = 0.5;
  for(int i = 0; i < 5; i++){
    v += a * noise(p);
    p *= 2.0;
    a *= 0.5;
  }
  return v;
}

vec4 alpha_over(vec4 top, vec4 bottom){
  float a = top.a + bottom.a * (1.0 - top.a);
  vec3  c = (top.rgb * top.a + bottom.rgb * bottom.a * (1.0 - top.a)) / max(a, 1e-5);
  return vec4(c, a);
}

float mountain_mask(vec2 uv, float seed, float base_h, float scale){
  float n = fbm(vec2(uv.x * scale + seed, uv.y * scale * 0.35));
  float wav = 0.015 * sin(uv.x * 2.2 + seed + time * 0.15);
  float h = base_h + (n - 0.5) * 0.18 + wav;
  return smoothstep(h, h - 0.03, uv.y);
}

vec3 sky_colour(vec2 uv){
  vec3 nearHorizon = vec3(0.06, 0.12, 0.18);
  vec3 zenith      = vec3(0.12, 0.20, 0.28);
  float t = smoothstep(0.0, 1.0, uv.y);
  return mix(nearHorizon, zenith, t);
}

vec3 sun_colour(vec2 uv){
  vec2 p = uv - vec2(0.18, 0.78);
  p.x *= ratio;
  float d = length(p);
  float core = smoothstep(0.11, 0.0, d);
  float halo = smoothstep(0.55, 0.0, d);
  vec3 warm  = vec3(1.00, 0.86, 0.58);
  return warm * core + warm * halo * 0.20;
}

void main(){
  // Normalised UV for aspect-correct shapes
  vec2 uv = UV;
  vec2 p  = uv - 0.5;
  p.x *= ratio;

  vec3 col = sky_colour(uv);

  // Sun glow
  col += sun_colour(uv);

  // Light atmospheric haze near horizon
  float haze = smoothstep(0.05, 0.55, uv.y) * 0.12;
  col = mix(col, vec3(0.16, 0.22, 0.28), haze);

  // Layered mountains (back to front)
  vec4 acc = vec4(col, 1.0);

  vec4 back = vec4(vec3(0.10, 0.18, 0.24), mountain_mask(uv, 10.0, 0.52, 2.2));
  vec4 mid  = vec4(vec3(0.07, 0.14, 0.19), mountain_mask(uv, 20.0, 0.44, 2.8));
  vec4 front= vec4(vec3(0.04, 0.09, 0.12), mountain_mask(uv, 30.0, 0.36, 3.6));

  acc = alpha_over(back,  acc);
  acc = alpha_over(mid,   acc);
  acc = alpha_over(front, acc);

  // A tiny bit of grain to keep gradients looking “gif-friendly”
  float g = (hash21(UV * vec2(900.0, 600.0) + time) - 0.5) * 0.015;
  acc.rgb += g;

  out_color = vec4(clamp(acc.rgb, 0.0, 1.0), 1.0);
}

What to tweak

  • Palette: change the three mountain RGB values and the sky gradient in sky_colour().
  • Layer spacing: adjust base_h in the three mountain_mask() calls.
  • Motion: increase or remove the wav term for a calmer or more animated horizon.
  • Compression friendliness: reduce haze and gradients if you want smaller GIF sizes.

Explore next

Last updated: 28 December 2025