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.
pow(a, 1.5) = Kepler's third law. normalize(-cen) = light comes from the Sun. The ecliptic plane = the orbits. Nine lessons = one scene.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);
}