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

Marte

Óxido de hierro y un cañón de 4 000 km. La luz encuentra cada pendiente.

En Mercurio los cráteres eran color: la luz no los veía. Para que la luz vea el terreno, la normal tiene que inclinarse con él. El truco: mide la altura a los dos lados de cada eje y resta. Eso es el gradiente: hacia dónde sube.

g -= dot(g, q)*q deja solo la parte que corre sobre la superficie. normalize(q - relief*0.06*g) inclina la normal cuesta abajo. Lambert hace lo demás: la ladera que da al sol se prende y la otra se apaga. A esto se le llama bump mapping. La forma no cambia: cambia la luz.

La altura es un ruido en 3D menos Valles Marineris: 4 000 km de largo y hasta 7 km de hondo. El rojo es óxido de hierro. Los casquetes son hielo de agua y de CO₂. Abajo: Relieve, Polos y Giro.

La teja del patio, con una grieta de 4 000 km.

el gradienteheight() = colinas menos cañón. g = hacia dónde sube. q - relief*0.06*g = la normal cuesta abajo. Misma esfera, otra luz.
PruebaPon relief en 0.0: Mercurio pintado de rojo. Súbelo a 2.5: cordillera. Baja caps de 0.84 a 0.6: era de hielo.

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);
}
float relief = 0.9;    // how far the normal tilts with the slope
float caps = 0.84;     // how far up the globe the ice starts
float spin = 0.06;
// The terrain's height: hills and a 4,000 km canyon (Valles Marineris)
float height(vec3 q){
  float lon = atan(q.x, q.z);
  float canyon = smoothstep(0.08, 0.0, abs(q.y + 0.12 + 0.04*sin(lon*4.0)))*smoothstep(1.3, 0.4, abs(lon - 0.6));
  return f3(q*3.0) - canyon*0.22;
}
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);
    float a = T*spin;
    vec3 q = n; q.xz = turn(a)*q.xz;
    vec3 s = sun; s.xz = turn(a)*s.xz;                 // the sun, seen from the turning planet
    // The trick: which way the terrain rises (the gradient), measured on each side
    float e = 0.004;
    vec3 g = vec3(height(q + vec3(e, 0, 0)) - height(q - vec3(e, 0, 0)),
                  height(q + vec3(0, e, 0)) - height(q - vec3(0, e, 0)),
                  height(q + vec3(0, 0, e)) - height(q - vec3(0, 0, e)))/(2.0*e);
    g -= dot(g, q)*q;                                  // only what runs along the surface
    vec3 nb = normalize(q - relief*0.06*g);            // the normal tips downhill
    float dark = smoothstep(0.42, 0.55, f3(q*1.4 + 2.0));
    vec3 albedo = mix(vec3(0.16, 0.08, 0.045), vec3(0.42, 0.19, 0.08), dark);   // iron oxide
    albedo = mix(albedo, vec3(0.80, 0.78, 0.76), smoothstep(caps, caps + 0.03, abs(q.y)));
    col = albedo*max(dot(nb, s), 0.0)*3.0;
    col += vec3(0.50, 0.30, 0.18)*pow(1.0 - dot(n, -rd), 4.0)*max(dot(n, sun) + 0.2, 0.0)*0.25;   // the dust at the edge
  }
  gl_FragColor = vec4(wogl(col), 1.0);
}