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

La Tierra

Agua que refleja el sol. Un punto de luz, en el lugar exacto.

Mercurio y Venus solo difundían: la luz salía igual para todos lados. El agua refleja como espejo. El truco: h = normalize(sun + v), el vector a medio camino entre el sol y tu ojo. Donde la normal coincide con h, el mar te manda el sol directo.

pow(dot(n, h), shine) aprieta ese reflejo: con shine alto, el mar está liso y el punto es chico. Fresnel decide cuánto refleja: de frente el agua refleja el 2 %, de canto casi todo. Es la aproximación de Schlick, 0.02 + 0.98*pow(1.0 - dot(h, v), 5.0).

La Tierra es 71 % agua. Las nubes giran un poco más rápido que el suelo, y del lado de noche se prenden las ciudades. Mueve el puntero hasta que el reflejo caiga en el océano. Abajo: Brillo, Nubes y Giro.

El sol en el parabrisas. Del tamaño del Pacífico.

h y Fresnelh = normalize(sun + v) = el medio camino. pow(dot(n, h), shine) = el reflejo. Schlick con 0.02 = el agua de frente refleja 2 %.
PruebaBaja shine de 180.0 a 20.0: mar picado. Súbelo a 600.0: espejo. Pon clouds en 1.0: lunes.

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 shine = 180.0;    // how smooth the sea is: more = a smaller glint
float clouds = 0.55;
float spin = 0.06;
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), v = -rd;
    vec3 q = n; q.xz = turn(T*spin)*q.xz;
    float land = smoothstep(0.50, 0.53, f3(q*1.6 + 4.0));          // 29% land, 71% water
    float ice = smoothstep(0.80, 0.86, abs(q.y));
    vec3 ground = mix(vec3(0.04, 0.08, 0.025), vec3(0.22, 0.16, 0.08), f3(q*6.0));   // forest and desert
    vec3 albedo = mix(vec3(0.004, 0.022, 0.065), ground, land);
    albedo = mix(albedo, vec3(0.75), ice);
    float lambert = max(dot(n, sun), 0.0);
    // The trick: halfway between the sun and your eye
    vec3 h = normalize(sun + v);
    float fresnel = 0.02 + 0.98*pow(1.0 - max(dot(h, v), 0.0), 5.0);   // water seen head-on reflects 2%
    float glint = pow(max(dot(n, h), 0.0), shine)*fresnel*(shine + 8.0)/8.0*(1.0 - land)*(1.0 - ice);
    vec3 surface = (albedo + glint)*lambert*3.0;
    vec3 qc = n; qc.xz = turn(T*spin*1.4)*qc.xz;                       // the clouds move faster
    float cloud = smoothstep(0.52, 0.78, f3(qc*3.0 + f3(qc*2.0)*0.8))*clouds;
    col = mix(surface, vec3(0.7)*lambert*3.0, cloud);
    float night = smoothstep(0.05, -0.15, dot(n, sun));
    float city = step(0.94, h3(floor(q*90.0)))*step(0.45, f3(q*7.0))*land*(1.0 - ice)*(1.0 - cloud);
    col += vec3(1.0, 0.62, 0.28)*city*night*0.5;                         // the cities at night
    col += vec3(0.06, 0.13, 0.40)*pow(1.0 - dot(n, v), 3.0)*max(dot(n, sun) + 0.3, 0.0);   // the edge of the air
  }
  gl_FragColor = vec4(wogl(col), 1.0);
}