⏱️ Lectura: 13 min
El 12 de mayo de 2026, Maxime Heckel publicó un análisis técnico extenso sobre cómo renderizar cielos, atardeceres y planetas directamente en el navegador usando shaders. Su punto de partida es una foto de la NASA del transbordador Endeavour suspendido en órbita baja, con la atmósfera terrestre desplegada como un degradado que va del naranja profundo al azul y termina disolviéndose en el negro del espacio.
📑 En este artículo
- TL;DR
- Por qué un degradado azul no es un cielo
- Raymarching: muestrear la atmósfera paso a paso
- Dispersión de Rayleigh: el azul del cielo
- Dispersión de Mie: el halo del sol y los atardeceres
- Absorción de ozono: el matiz del crepúsculo
- De cúpula a planeta: renderizar mundos desde la órbita
- LUTs: el enfoque de Sebastian Hillaire para 60 FPS
- Cómo probarlo en tu máquina
- Por qué importa
- Preguntas frecuentes
- Referencias
El artículo es una guía paso a paso para reproducir ese efecto en tiempo real, y toca de lleno el fenómeno físico que lo produce: el scattering atmosférico. Acá repasamos las ideas centrales, las decisiones de implementación y por qué este tipo de rendering importa más allá del demo bonito.
TL;DR
- Maxime Heckel publicó el 12 de mayo de 2026 un análisis sobre rendering atmosférico en el navegador con shaders post-procesamiento.
- El efecto se construye con raymarching: cada rayo acumula transmittance (luz que sobrevive) y scattering (luz redirigida hacia la cámara).
- La dispersión de Rayleigh explica el azul del cielo: longitudes de onda cortas se dispersan más que las largas a través del aire.
- La dispersión de Mie aporta el halo blanco alrededor del sol y los tonos cálidos visibles al amanecer y atardecer.
- La absorción de ozono entre 25 y 35 km añade los matices rojizos profundos del horizonte al anochecer.
- Sebastian Hillaire propuso un método basado en LUTs para mantener 60 FPS sin sacrificar realismo, viable en producción.
- La misma técnica se adapta para renderizar planetas con shell atmosférico vistos desde la órbita.
Por qué un degradado azul no es un cielo
El primer impulso de cualquier desarrollador que necesita un fondo de cielo es poner un linear-gradient azul detrás de la escena. Funciona como decoración, pero nunca termina de convencer al ojo. La razón es que un cielo real no es un color plano sino el resultado de la luz solar interactuando con un volumen de aire, polvo y gases. El color depende del ángulo del sol, la altitud del observador, la cantidad de partículas en suspensión y la longitud del camino óptico que recorre cada rayo antes de llegar a la cámara.
Heckel lo plantea con claridad: para reproducir un cielo creíble hay que tratar la atmósfera como un medio participativo, no como una superficie. Eso significa muestrear ese volumen punto por punto, y es ahí donde entra el raymarching.
💭 Clave: Un cielo realista no es un degradado, es la integral de la luz que atraviesa kilómetros de aire. Ese cambio de mentalidad es la mitad del problema resuelto.
Raymarching: muestrear la atmósfera paso a paso
El raymarching es la técnica base para todo el efecto. Por cada pixel de la pantalla disparamos un rayo desde la cámara hacia la escena y avanzamos en pasos discretos a lo largo de ese rayo. En cada paso preguntamos dos cosas: cuánta luz solar logra llegar a este punto del aire (la transmittance), y cuánta de esa luz se redirige hacia la cámara (el scattering).
La densidad de la atmósfera no es uniforme. A nivel del mar es máxima y decae exponencialmente con la altura. Heckel modela esto con la función de densidad de Rayleigh, que toma como referencia una altura de escala de 8 km y un techo atmosférico de 100 km (la línea de Kármán). Acumular esta densidad a lo largo del rayo da lo que en física se llama profundidad óptica.
const float RAYLEIGH_SCALE_HEIGHT = 8.0; // km
const float ATMOSPHERE_HEIGHT = 100.0; // km, linea de Karman
const int PRIMARY_STEPS = 24;
float rayleighDensity(float h) {
return exp(-max(h, 0.0) / RAYLEIGH_SCALE_HEIGHT);
}
float opticalDepth(vec3 rayOrigin, vec3 rayDir, float length) {
float stepSize = length / float(PRIMARY_STEPS);
float depth = 0.0;
for (int i = 0; i < PRIMARY_STEPS; i++) {
float t = (float(i) + 0.5) * stepSize;
float h = (rayOrigin + rayDir * t).y;
if (h < 0.0 || h > ATMOSPHERE_HEIGHT) break;
depth += rayleighDensity(h) * stepSize;
}
return depth;
}
El número de pasos es un compromiso clásico de rendering: más pasos significan más detalle y más coste de GPU. Heckel usa 24 pasos primarios para el rayo de cámara y un loop interno más corto para los rayos de luz que van desde cada muestra hacia el sol. Ese loop anidado es lo que hace caro al algoritmo, y es exactamente lo que las técnicas con LUTs intentan eliminar.
Dispersión de Rayleigh: el azul del cielo
La dispersión de Rayleigh describe cómo las moléculas de aire dispersan la luz en función de su longitud de onda. La dispersión es proporcional a 1/λ⁴, lo que significa que las longitudes de onda cortas (azul, ~450 nm) se dispersan unas 5,5 veces más que las largas (rojo, ~700 nm).
Cuando el sol está alto, la luz atraviesa menos atmósfera para llegar al observador. La componente azul se dispersa en todas direcciones y nos rodea desde cualquier punto del cielo, mientras que la roja sigue casi recta. Eso explica el color azul del cenit. Al atardecer, el camino del sol a través de la atmósfera es mucho más largo y casi todo el azul se dispersa fuera de la línea de visión antes de llegar al observador: solo sobreviven los rojos y naranjas.
En código, los coeficientes de Rayleigh son tres valores fijos, uno por canal de color, en unidades de inversa de longitud. El shader los multiplica por la densidad acumulada y por la función de fase, que modela la distribución angular de la dispersión.
Dispersión de Mie: el halo del sol y los atardeceres
Si el cielo solo tuviera Rayleigh, los atardeceres serían rojizos pero sin esa banda dorada cálida que vemos cerca del horizonte. Esa banda viene de la dispersión de Mie, que ocurre cuando la luz interactúa con partículas más grandes que las moléculas de aire: aerosoles, polvo, vapor de agua, hollín de incendios.
A diferencia de Rayleigh, la dispersión de Mie es casi independiente de la longitud de onda, por lo que la luz dispersada conserva el color del sol. Su característica clave es que es fuertemente anisotrópica: dispersa la luz hacia adelante, en la misma dirección general en la que viaja el rayo original. Esto es lo que produce el halo brillante alrededor del sol y la franja cálida del horizonte al atardecer.
En el shader, Mie se modela con una función de fase de Henyey-Greenstein parametrizada por un valor g cercano a 0.8, que controla qué tan pronunciado es el pico de dispersión hacia adelante. Los desarrolladores que vienen del mundo de gráficos en juegos reconocerán la fórmula porque es la misma que se usa en niebla volumétrica y volumetric clouds.
💡 Tip: Si tu cielo de WebGL se ve correcto al mediodía pero apagado al atardecer, el problema casi siempre es Mie: estás obviando los aerosoles o tu parámetro g está cerca de 0 (dispersión isotrópica).
Absorción de ozono: el matiz del crepúsculo
El ingrediente menos conocido es la capa de ozono. Entre los 25 y 35 km de altitud existe una concentración de O₃ que absorbe selectivamente longitudes de onda en el azul y el verde. Por sí sola la absorción es sutil, pero es responsable del característico tono rojizo profundo del horizonte cuando el sol ya está debajo de la línea visible y el crepúsculo se vuelve casi púrpura.
El modelo de Heckel agrega un coeficiente de absorción específico para el ozono y una distribución de densidad con forma de tienda (tent function) centrada en los 30 km. Sin ozono, los crepúsculos se ven planos. Con ozono, aparece esa transición naranja-violeta que cualquiera reconoce de las fotos satelitales.
De cúpula a planeta: renderizar mundos desde la órbita
La segunda parte del análisis adapta el mismo motor de raymarching para renderizar un planeta visto desde el espacio. La diferencia es geométrica: ya no asumimos un suelo plano y un cielo encima, sino una esfera con un shell atmosférico de espesor finito.
El cambio principal es la intersección rayo-esfera: por cada pixel calculamos las distancias t0 y t1 donde el rayo entra y sale de la atmósfera, y ejecutamos el raymarching solo dentro de ese segmento. El resto del shader (Rayleigh, Mie, ozono) queda igual. Es elegante: el modelo físico no cambia, solo cambia el dominio de integración.
vec2 raySphereIntersect(vec3 ro, vec3 rd, float radius) {
float b = dot(ro, rd);
float c = dot(ro, ro) - radius * radius;
float disc = b * b - c;
if (disc < 0.0) return vec2(-1.0);
float s = sqrt(disc);
return vec2(-b - s, -b + s);
}
El resultado es lo que ves en cualquier render moderno de planetas en motores como Three.js o Babylon.js: un halo azul fino, un terminador suave entre día y noche y, si el ángulo es el correcto, el degradado completo de naranja a azul que produjo aquella foto del Endeavour.
LUTs: el enfoque de Sebastian Hillaire para 60 FPS
El raymarching con loops anidados es físicamente correcto pero pesado. Renderizar un cielo full-screen a 1080p con 24 pasos primarios y 8 pasos por sol son del orden de 400 millones de evaluaciones por frame, demasiado para móviles de gama media.
Sebastian Hillaire publicó en 2020 un método pensado para producción que reemplaza buena parte de ese cálculo por lookup tables precomputadas. La idea: si la transmittance hacia el sol solo depende de la altitud del punto y del ángulo solar, no hace falta recalcularla por frame; se puede precomputar a una textura 2D de unos 256×64 y leerla durante el render. Lo mismo aplica a la sky-view LUT, que captura el cielo entero en una textura panorámica de baja resolución y se actualiza solo cuando el sol o la cámara se mueven significativamente.
Heckel reconoce que esta sección lo sacó de su zona de confort. La complejidad no está en la física sino en la coreografía de pases de render: cada LUT se calcula en su propio render target, en el orden correcto, con sus muestras correctas. La recompensa es un cielo equivalente al raymarched corriendo a 60 FPS estables incluso en hardware modesto.
📌 Nota: Las LUTs no eliminan la necesidad de entender el modelo físico. Son una optimización que asume que tu cielo no necesita resolución infinita por pixel. Si estás haciendo un planetario científico, quizá quieras quedarte con el raymarching puro.
Cómo probarlo en tu máquina
Si querés experimentar con scattering atmosférico, hay un par de caminos accesibles. El primero es clonar uno de los demos open source que implementan estas técnicas. El segundo es montar un proyecto mínimo con Three.js o react-three-fiber.
Instalar Three.js (LATAM-friendly)
# macOS / Linux
mkdir cielo-shader && cd cielo-shader
npm init -y
npm install three vite
npx vite
# Windows (PowerShell)
mkdir cielo-shader; cd cielo-shader
npm init -y
npm install three vite
npx vite
Una vez con Three.js corriendo, podés agregar un ShaderMaterial con un fragment shader que implemente Rayleigh + Mie. Hay bibliotecas como three-geospatial de Shota Matsuda que empaquetan estos efectos listos para integrar, ideales para empezar a experimentar sin escribir cada línea de GLSL desde cero.
Por qué importa
Más allá del placer estético, dominar el rendering atmosférico tiene aplicaciones concretas. Los simuladores de vuelo, los visualizadores de datos satelitales, los juegos AAA con ciclos día/noche y las experiencias web inmersivas (think landing pages de aerolíneas, agencias espaciales o startups de earth-observation) dependen de cielos creíbles. Que toda la pipeline corra en el navegador, sin plugins ni instaladores, multiplica los escenarios donde se puede usar.
El trabajo de Heckel también es un recordatorio de que la web sigue siendo una plataforma de gráficos seria. Entre WebGL, WebGPU y los shaders compilados a SPIR-V, la distancia entre lo que se puede hacer en un browser y lo que históricamente requería Unity o Unreal se sigue acortando. Que un análisis de scattering atmosférico se publique con demos interactivos embebidos en el propio post es el tipo de cosa que hace cinco años pertenecía a las charlas de SIGGRAPH y hoy circula en un blog.
📖 Resumen en Telegram: Ver resumen
Preguntas frecuentes
¿Necesito saber GLSL para entender este tema?
Ayuda mucho. Los conceptos físicos (Rayleigh, Mie, raymarching) se pueden seguir sin escribir shaders, pero implementarlos exige al menos GLSL básico o un wrapper como TSL de Three.js. Si nunca tocaste shaders, vale la pena un tutorial de raymarching antes.
¿Funciona en móviles?
El raymarching completo puede saturar GPUs móviles. El enfoque con LUTs de Hillaire fue diseñado precisamente para correr a 60 FPS en hardware de consumo, incluidos celulares de gama media. La mayoría de los demos de producción usan LUTs.
¿Cómo se diferencia del rendering de nubes volumétricas?
Las nubes volumétricas usan la misma técnica base (raymarching a través de un medio participativo) pero con una función de densidad procedural (noise) en vez de una distribución exponencial limpia. Las nubes son cientos de veces más caras que el cielo solo.
¿Sirve para visualización científica?
Sí, con cuidado. El modelo físico es correcto en lo cualitativo pero los coeficientes están ajustados para verse bien, no para reproducir mediciones espectrales. Para aplicaciones científicas serias hay que partir de los valores publicados por agencias como la NASA y validar contra fotometría real.
¿Qué pasa con WebGPU?
WebGPU permite usar compute shaders, lo que abre la puerta a precomputar las LUTs en GPU sin pasos de render adicionales, y a explorar técnicas más avanzadas como path tracing atmosférico. Los demos de Heckel siguen en WebGL pero la migración es natural.
Referencias
- Maxime Heckel — On Rendering the Sky, Sunsets, and Planets — artículo original con demos interactivos.
- NASA — Shuttle Silhouette — foto del Endeavour que inspiró el proyecto.
- Wikipedia — Atmospheric scattering — fundamento físico del fenómeno.
- Wikipedia — Rayleigh scattering — explicación del modelo y su dependencia de la longitud de onda.
- Wikipedia — Mie scattering — teoría de dispersión por partículas grandes.
📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.
0 Comentarios