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 < 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.float 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.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);
}