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

El Sol

Una esfera exacta, sin marchar. Brilla más al centro que en la orilla.

En Ray Marched llegabas a la esfera a pasos. Una esfera tiene fórmula cerrada: el rayo la toca donde t² + 2bt + c = 0. El truco: si b*b - c es negativo, el rayo pasa de largo; si no, t = -b - sqrt(b*b - c). Un cálculo, cero pasos.

La normal de una esfera en el origen es el punto mismo. mu es el coseno entre la normal y el rayo: 1 al centro del disco, 0 en la orilla. En la orilla ves capas más altas y más frías de la fotosfera, a 5 772 K en promedio. Por eso brilla menos: 1 - limb*(1 - mu), con limb en 0.6 para luz visible.

Encima, la granulación: el Voronoi de 04:10, aplanado sobre el globo. Cada celda es gas que sube, de unos 1 000 km, y dura de 8 a 20 minutos. Afuera del disco, near mide lo más cerca que pasó el rayo del centro: ahí vive la corona. Abajo tienes Limbo, Celdas y Corona.

Una olla de atole hirviendo, vista desde arriba.

b*b - c y mub*b - c < 0 = el rayo no la toca. mu = coseno entre normal y rayo. 1 - limb*(1 - mu) = la ley del limbo. near > 1 = afuera, en la corona.
Leyendo el códigofloat sphere(vec3 ro, vec3 rd, float r): a qué distancia sobre el rayo toca una esfera de radio r, o −1 si no la toca. Cada cuerpo de esta sección se dibuja con ella.
float t = sphere(ro, rd, 1.0): el impacto. Si t > 0.0, el pixel está sobre el Sol.
ro + rd*t: el punto donde cae el rayo, en 3D.
vec3 n = normalize(ro + rd*t): la normal de ese punto. En una esfera en el origen, es el mismo punto.
vec3 wogl(vec3 c): el cierre WoGL. Acomoda la luz a lo que cabe en la pantalla y fija el color; cada pieza de esta sección termina con él.
float limb = 0.60;: los números de arriba son los sliders. Cada uno mueve uno de ellos.
PruebaBaja limb de 0.60 a 0.0: disco de papel. Súbelo a 1.0: la orilla se apaga entera. Lleva cells de 26.0 a 40.0: arroz inflado.

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);
}
// Granulation: the Voronoi of 04:10. Each cell is a bubble of rising gas.
float cell(vec2 x){
  vec2 g = floor(x), f = fract(x);
  float d = 8.0;
  for (int j = -1; j <= 1; j++) for (int i = -1; i <= 1; i++){
    vec2 o = vec2(float(i), float(j));
    vec2 s = 0.5 + 0.4*sin(T*0.35 + 6.2831*vec2(h2(g + o), h2(g + o + 7.3)));
    vec2 r = o + s - f;
    d = min(d, dot(r, r));
  }
  return sqrt(d);
}
float limb = 0.60;     // limb darkening: 0 = a flat disc
float cells = 26.0;    // convection cells across
float corona = 0.35;   // the halo outside
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 col = vec3(0.0002, 0.0003, 0.0006);
  float t = sphere(ro, rd, 1.0);
  if (t > 0.0){
    vec3 n = normalize(ro + rd*t);            // a sphere's normal is the point itself
    float mu = dot(n, -rd);                   // 1 at the disc's center, 0 at its edge
    float c = cell(n.xy/(1.0 + n.z)*cells);   // the globe flattened, with no seam
    float gas = 0.88 + 0.2*(1.0 - smoothstep(0.1, 0.8, c));
    float I = 1.0 - limb*(1.0 - mu);          // the limb's law
    vec3 photo = vec3(1.0, 0.62, 0.28)*1.7;   // the photosphere: 5,772 K
    col = photo*I*gas;
    col *= mix(vec3(1.0, 0.62, 0.36), vec3(1.0), mu);   // the edge also reddens
  }
  // Outside: the closest the ray came to the center. More than 1 = it missed.
  float b = dot(ro, rd), near = sqrt(max(dot(ro, ro) - b*b, 0.0));
  if (near > 1.0){
    float a = atan(p.y, p.x);
    float rays = 0.5 + 0.5*fb(vec2(cos(a), sin(a))*3.0 + near*3.0 - T*0.1);
    col += vec3(1.0, 0.56, 0.24)*corona*exp(-(near - 1.0)*9.0)*rays;
  }
  gl_FragColor = vec4(wogl(col), 1.0);
}