Lección 10 de 10 · El Sistema Solar en GLSL
Tu sistema solar
Un Sol, ocho planetas, una luna. Kepler pone los tiempos.
Aquí está todo junto. La esfera exacta y la ley del limbo del Sol. La luz de Mercurio, que ahora sale de un punto: cada planeta se ilumina con normalize(-cen), la dirección al Sol, así que cada uno tiene su propia fase según dónde va. Las bandas de Júpiter. El plano de Saturno.
El truco: los tiempos. La tercera ley de Kepler dice que el periodo crece como a^1.5: T*speed*0.9/pow(a, 1.5). Mercurio da la vuelta en 88 días; Neptuno, en 165 años. Las distancias y los tamaños están juntados y exagerados: a escala, los planetas no se verían.
El anillo de Saturno está inclinado 26.7° y fijo en el espacio, así que a lo largo de su órbita lo ves abrirse y cerrarse. Las órbitas son el mismo rayo contra el plano de la eclíptica. El puntero te lleva alrededor. Abajo: Velocidad, Altura y Tamaño.
El móvil de la cuna. Kepler le puso los tiempos.
pow(a, 1.5) = tercera ley de Kepler. normalize(-cen) = la luz viene del Sol. Plano de la eclíptica = las órbitas. Nueve lecciones = una escena.speed de 1.0 a 4.0: un siglo en un minuto. Baja height a 0.1: todo de canto, en fila. Lleva size a 2.0 y cambia los colores de planet() para tu firma. Fírmalo.El código
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);
}