Lección 2 de 10 · El Sistema Solar en GLSL

Mercurio

Roca gris, sin aire. El sol lo mueves tú.

El Sol daba luz; Mercurio la recibe. El truco: dot(n, sun). Si la normal mira al sol, vale 1; de lado, 0; de espaldas, negativo, y max(..., 0.0) lo deja en noche. Es la ley de Lambert, la misma de Ray Marched, ahora con el sol en el puntero.

Mercurio no tiene atmósfera: nada lleva luz al lado oscuro, y la noche es negra. De día llega a 430 °C; de noche baja a −180 °C. Un día solar le dura 176 días terrestres. Refleja el 14 % de la luz: 0.13 en el código.

Los cráteres son un Voronoi en 3D: crater() da la distancia al impacto más cercano dividida entre su radio. Menos de 1, fondo; justo 1, borde. Aquí son solo color: el relieve llega en Marte. Mueve el puntero sobre el planeta. Abajo: Giro, Cráteres y Sol.

Una bola de boliche de renta. Con los golpes de todos.

dot(n, sun)dot(n, sun) = cuánto sol recibe el punto. Menos de 0 = noche. Sin aire = noche negra. s < 1 = dentro de un cráter.
PruebaLleva el puntero a la orilla izquierda: luna creciente. Al centro arriba: casi llena. Sube craters de 5.0 a 11.0: lluvia de meteoritos.

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);
}
// Noise in 3D: the usual fb with one more dimension. On a globe it leaves no seam.
float h3(vec3 p){ return h2(p.xy + p.z*vec2(37.0, 17.0)); }
float n3(vec3 p){
  vec3 i = floor(p), f = fract(p);
  f = f*f*(3.0 - 2.0*f);
  return mix(mix(mix(h3(i), h3(i + vec3(1,0,0)), f.x), mix(h3(i + vec3(0,1,0)), h3(i + vec3(1,1,0)), f.x), f.y),
             mix(mix(h3(i + vec3(0,0,1)), h3(i + vec3(1,0,1)), f.x), mix(h3(i + vec3(0,1,1)), h3(i + vec3(1,1,1)), f.x), f.y), f.z);
}
float f3(vec3 p){
  float v = 0.0, a = 0.5;
  for (int i = 0; i < 5; i++){ v += a*n3(p); p = p*2.03 + vec3(1.7, 9.2, 4.1); a *= 0.5; }
  return v;
}
// 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);
}
// Craters: Voronoi in 3D. Each cell holds one impact of its own size.
float crater(vec3 x){
  vec3 g = floor(x), f = fract(x);
  float best = 9.0;
  for (int k = -1; k <= 1; k++) for (int j = -1; j <= 1; j++) for (int i = -1; i <= 1; i++){
    vec3 o = vec3(float(i), float(j), float(k));
    float h = h3(g + o);
    vec3 r = o + vec3(h, h3(g + o + 3.7), h3(g + o + 9.1))*0.8 + 0.1 - f;
    best = min(best, length(r)/(0.12 + 0.34*h*h*h));   // few large, many small
  }
  return best;   // less than 1 = inside a crater
}
float spin = 0.10;      // how fast it turns
float craters = 5.0;    // craters per radius
float light = 3.2;      // the sun's strength
void main(){
  vec2 p = (gl_FragCoord.xy*2.0 - R)/min(R.x, R.y);
  vec3 ro = vec3(0.0, 0.0, 3.2), rd = normalize(vec3(p, -2.4));   // the camera looks at the center
  vec3 sun = normalize(vec3(M.x*4.0 - 3.8, M.y*3.0 - 1.0, 1.0));   // you move the sun: it is the pointer
  vec3 col = sky(gl_FragCoord.xy);
  float t = sphere(ro, rd, 1.0);
  if (t > 0.0){
    vec3 n = normalize(ro + rd*t);
    vec3 q = n; q.xz = turn(T*spin)*q.xz;      // the point, on the turning planet
    float s = crater(q*craters);
    float rock = 0.13*(0.7 + 0.6*f3(q*4.0));  // Mercury's albedo: 0.14
    rock *= 1.0 - 0.16*smoothstep(0.85, 0.55, s);                 // the crater floor
    rock *= 1.0 + 0.22*smoothstep(0.18, 0.0, abs(s - 1.0));       // the rim
    rock *= 1.0 + 0.10*smoothstep(1.9, 1.0, s)*step(1.0, s);      // what flew out
    float lambert = max(dot(n, sun), 0.0);     // how much sun this point gets
    col = vec3(1.0, 0.94, 0.86)*rock*lambert*light;
  }
  gl_FragColor = vec4(wogl(col), 1.0);
}