Lección 9 de 10 · El Sistema Solar en GLSL
Neptuno
El viento más rápido del sistema solar. Y una orilla de aire azul.
En Urano el aire quitaba color. En Neptuno también lo agrega: el aire esparce la luz azul hacia ti. El truco: una segunda esfera, de radio 1.0 + air, para el aire. El rayo entra en t0 y sale en t1, o en el suelo si lo toca. t1 - t0 es cuánto aire cruzó.
De frente cruzas poco aire; rozando la orilla, mucho. Por eso el borde brilla. El azul sale de Rayleigh: el aire esparce cada color como 1/λ⁴, y el azul se esparce unas 5 veces más que el rojo. lit apaga el aire del lado de noche.
Neptuno tiene los vientos más rápidos medidos: 2 100 km/h. La Gran Mancha Oscura la fotografió el Voyager 2 en 1989. Las nubes blancas son metano congelado: flotan arriba del metano y no se tiñen. En 2024 se recalibraron esas fotos: su color real es apenas más azul que el de Urano. Abajo: Aire, Viento y Azul.
La flama azul de la estufa, alrededor de una canica.
t1 - t0 = el camino por el aire. Rayleigh = el azul se esparce como 1/λ⁴.air de 0.05 a 0.15: halo de foco. Pon blue en 0.0: Neptuno sin cielo. wind a 1.0: 2 100 km/h.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 air = 0.05; // the air's thickness, in radii
float wind = 0.35; // 2,100 km/h in the fastest wind
float blue = 1.0; // how much blue the air scatters
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 tp = sphere(ro, rd, 1.0);
if (tp > 0.0){
vec3 n = normalize(ro + rd*tp);
float lat = asin(n.y);
vec3 q = n; q.xz = turn(T*(0.05 - wind*0.3*cos(2.0*lat)))*q.xz; // the equator runs against the poles
float toSun = max(dot(n, sun), 0.0), toEye = max(dot(n, -rd), 0.0);
vec3 color = exp(-vec3(1.0, 0.28, 0.10)*(0.5/max(toSun, 0.05) + 0.5/max(toEye, 0.05)));
vec3 c0 = vec3(cos(-0.35)*sin(0.15), sin(-0.35), cos(-0.35)*cos(0.15));
vec3 d = q - c0;
float spot = smoothstep(1.0, 0.5, length(vec2(dot(d, normalize(cross(vec3(0, 1, 0), c0)))/0.2, (q.y - c0.y)/0.1))); // the Great Dark Spot
float white = smoothstep(0.56, 0.74, f3(q*vec3(3.0, 14.0, 3.0)))*smoothstep(0.55, 0.15, abs(lat + 0.15)); // frozen-methane clouds: they float above the methane, so they are not tinted
col = color*(1.0 - 0.7*spot)*toSun*1.8;
col = mix(col, vec3(0.85)*toSun*2.2, white*0.85);
}
// The trick: a second sphere, slightly larger, for the air. In at t0, out at t1.
float R1 = 1.0 + air, b = dot(ro, rd), h = b*b - dot(ro, ro) + R1*R1;
if (h > 0.0){
float t0 = -b - sqrt(h), t1 = -b + sqrt(h);
if (tp > 0.0) t1 = tp; // if it hits the planet, the air ends at the ground
float path = max(t1 - t0, 0.0);
vec3 m = ro + rd*(t0 + path*0.5);
float lit = smoothstep(-0.3, 0.5, dot(normalize(m), sun));
col += vec3(0.16, 0.40, 1.0)*blue*path*lit*1.6; // Rayleigh: blue scatters as 1/λ⁴
}
gl_FragColor = vec4(wogl(col), 1.0);
}