Lesson 10 of 10 · The Solar System in GLSL

Your solar system

One Sun, eight planets, one moon. Kepler keeps the time.

Here it is, all of it. The Sun's exact sphere and its limb. Mercury's light, now coming from a point: each planet is lit along normalize(-cen), the direction to the Sun, so each has its own phase depending on where it is. Jupiter's bands. Saturn's plane.

The trick: the timing. Kepler's third law says the period grows as a^1.5: T*speed*0.9/pow(a, 1.5). Mercury goes around in 88 days; Neptune, in 165 years. Distances and sizes are squeezed and exaggerated: to scale, the planets would not show.

Saturn's ring is tilted 26.7° and fixed in space, so along its orbit you see it open and close. The orbits are the same ray against the plane of the ecliptic. The pointer takes you around. Below: Speed, Height and Size.

The crib mobile. Kepler set the timing.

the piecepow(a, 1.5) = Kepler's third law. normalize(-cen) = light comes from the Sun. The ecliptic plane = the orbits. Nine lessons = one scene.
TryRaise speed from 1.0 to 4.0: a century in a minute. Drop height to 0.1: everything edge-on, in a row. Take size to 2.0 and change the colours in planet() to sign it. Sign it.

The code

precision highp float;
// The exact sphere: how far along the ray it is hit, or -1 if the ray misses
float sphere(vec3 ro, vec3 rd, float r){
  float b = dot(ro, rd), c = dot(ro, ro) - r*r, h = b*b - c;
  return h < 0.0 ? -1.0 : -b - sqrt(h);
}
// WoGL close: knee at 0.70, sRGB, chroma 1.3
vec3 wogl(vec3 c){
  float l = dot(c, vec3(0.2126, 0.7152, 0.0722)), k = max(l - 0.7, 0.0);
  c *= l > 0.0 ? (l - k + k/(1.0 + k))/l : 0.0;
  c = pow(clamp(c, 0.0, 1.0), vec3(0.4545));
  l = dot(c, vec3(0.2126, 0.7152, 0.0722));
  return clamp(mix(vec3(l), c, 1.3), 0.0, 1.0);
}
// A 2D turn (to spin the globe about its axis)
mat2 turn(float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }
// The background: black, with a star now and then
vec3 sky(vec2 f){
  float s = h2(floor(f));
  return vec3(0.0002, 0.0003, 0.0006) + vec3(0.7, 0.7, 0.8)*step(0.9982, s)*h2(floor(f) + 3.1);
}
float speed = 1.0;      // how fast the year goes
float height = 0.55;    // how high you look from
float size = 1.0;       // the planets' size (exaggerated: to scale they would not show)
// Each planet: its orbit's radius, its radius and its colour
void planet(int i, out float a, out float r, out vec3 c){
  if (i == 0)      { a = 1.15; r = 0.045; c = vec3(0.40, 0.37, 0.34); }   // Mercury
  else if (i == 1) { a = 1.55; r = 0.080; c = vec3(0.90, 0.80, 0.55); }   // Venus
  else if (i == 2) { a = 2.00; r = 0.085; c = vec3(0.16, 0.34, 0.80); }   // Earth
  else if (i == 3) { a = 2.45; r = 0.060; c = vec3(0.75, 0.34, 0.16); }   // Mars
  else if (i == 4) { a = 3.30; r = 0.220; c = vec3(0.80, 0.64, 0.45); }   // Jupiter
  else if (i == 5) { a = 4.20; r = 0.190; c = vec3(0.85, 0.74, 0.50); }   // Saturn
  else if (i == 6) { a = 4.95; r = 0.130; c = vec3(0.55, 0.82, 0.86); }   // Uranus
  else             { a = 5.60; r = 0.125; c = vec3(0.32, 0.52, 0.92); }   // Neptune
}
// Kepler: the period grows as a^1.5. Mercury takes 88 days; Neptune, 165 years.
vec3 where(int i, float a){
  float ang = T*speed*0.9/pow(a, 1.5) + float(i)*2.4;
  return vec3(cos(ang), 0.0, sin(ang))*a;
}
void main(){
  vec2 p = (gl_FragCoord.xy*2.0 - R)/min(R.x, R.y);
  vec3 ro = vec3(0.0, sin(height), cos(height))*7.4;
  ro.xz = turn((M.x - 0.5)*3.0)*ro.xz;                         // the pointer takes you around
  vec3 fw = normalize(-ro), rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0))), up = cross(rt, fw);
  vec3 rd = normalize(p.x*rt + p.y*up + 2.2*fw);
  vec3 col = sky(gl_FragCoord.xy);
  float best = 1e9;
  // The Sun: the exact sphere and the limb's law (01)
  float ts = sphere(ro, rd, 0.6);
  if (ts > 0.0){
    best = ts;
    float mu = dot(normalize(ro + rd*ts), -rd);
    col = vec3(1.0, 0.62, 0.28)*1.8*(1.0 - 0.6*(1.0 - mu))*mix(vec3(1.0, 0.62, 0.36), vec3(1.0), mu);
  }
  // The eight, lit from the Sun (02): the light is no longer a constant, it is the direction to the center
  vec3 earth = vec3(0.0), saturn = vec3(0.0);
  for (int i = 0; i < 8; i++){
    float a, r; vec3 c; planet(i, a, r, c); r *= size*1.4;
    vec3 cen = where(i, a);
    if (i == 2) earth = cen;
    if (i == 5) saturn = cen;
    float t = sphere(ro - cen, rd, r);
    if (t > 0.0 && t < best){
      best = t;
      vec3 n = normalize(ro + rd*t - cen), L = normalize(-cen);
      if (i >= 4) c *= 0.88 + 0.12*sin(n.y*28.0);              // bands on the giants (06)
      col = c*max(dot(n, L), 0.0)*2.2;
    }
  }
  // The Moon, around the Earth
  vec3 moon = earth + vec3(cos(T*speed*3.0), 0.0, sin(T*speed*3.0))*0.24*size;
  float tm = sphere(ro - moon, rd, 0.03*size);
  if (tm > 0.0 && tm < best){ best = tm; vec3 n = normalize(ro + rd*tm - moon); col = vec3(0.5)*max(dot(n, normalize(-moon)), 0.0)*2.2; }
  // Saturn's ring: a plane (07), tilted 26.7° and fixed in space
  vec3 axis = vec3(0.0, cos(0.47), sin(0.47));
  float tr = -dot(ro - saturn, axis)/dot(rd, axis);
  if (tr > 0.0 && tr < best){
    float r = length(ro + rd*tr - saturn)/(0.19*size*1.4);
    float ring = smoothstep(1.24, 1.3, r)*smoothstep(2.27, 2.2, r)*(1.0 - 0.8*smoothstep(0.04, 0.0, abs(r - 1.99)));
    col = mix(col, vec3(0.85, 0.75, 0.58)*1.2, ring*0.75);
  }
  // The orbits: the ray against the plane of the ecliptic
  float to = -ro.y/rd.y;
  if (to > 0.0 && to < best){
    float d = length((ro + rd*to).xz), line = 0.0;
    for (int i = 0; i < 8; i++){ float a, r; vec3 c; planet(i, a, r, c); line += smoothstep(0.018, 0.0, abs(d - a)); }
    col += vec3(0.85, 0.65, 0.40)*line*0.05;
  }
  float b = dot(ro, rd), near = sqrt(max(dot(ro, ro) - b*b, 0.0));
  if (near > 0.6 && best > 1e8) col += vec3(1.0, 0.56, 0.24)*0.5*exp(-(near - 0.6)*6.0);   // the corona
  gl_FragColor = vec4(wogl(col), 1.0);
}