Skip to content

Construir un mundo abierto en el navegador, parte 22: Nubes que puedes iluminar y un culling que hay que alimentar

Por Oleg Sidorkin, director de tecnología y cofundador de Cinevva

¿Acabas de llegar? Consulta la guía de la serie. Explica qué es un spike y enlaza todas las partes.

La parte 21 trataba sobre una técnica de renderizado que no dio resultado. Esta parte presenta una que sí lo dio y otra que necesitó una corrección cuidadosa para funcionar. El Spike 43 es el cielo: una atmósfera basada en la física y nubes volumétricas, la base que hace que una escena parezca un lugar en vez de una demostración técnica. El Spike 44 es culling de estilo meshlet en la GPU, cuya lección fue que la prueba de oclusión solo es tan buena como los oclusores que se le proporcionan.

Un cielo creado mediante física, no con un gradiente

Abrir el Spike 43 en una pestaña nueva ↗ · Ver código fuente

Casi todos los efectos meteorológicos cinematográficos dependen de dos componentes de infraestructura: una atmósfera basada en la física, para que el color del cielo y el del sol sigan la hora del día mediante la física en lugar de un gradiente ajustado a mano, y un volumen de nubes volumétricas, para que el cielo tenga estructura 3D en vez de un cubemap precalculado. El Spike 43 construye exactamente ese par sobre la pila existente de WebGPU y TSL, y nada más, porque una vez que ambos existen, el resto de la pila meteorológica —niebla, rayos crepusculares, superficies mojadas y nieve— se convierte en una serie de ampliaciones más pequeñas y conocidas.

La atmósfera utiliza el modelo de Hillaire de 2020, un conjunto de tablas de consulta calculadas en shaders de cómputo WGSL. Una tabla de transmitancia integra la luz solar a través del perfil de densidad de Rayleigh, Mie y ozono, y solo se recalcula cuando se mueve el sol. Una tabla de vista del cielo se vuelve a generar en cada fotograma porque es lo bastante barata como para que no merezca la pena añadir código para condicionarla, y usa una parametrización no lineal alrededor del horizonte para evitar el banding. Por ahora, la dispersión múltiple usa un ajuste analítico en lugar de la tabla correcta, y los atardeceres se ven bien, así que el atajo queda bien disimulado. Las nubes emplean un ray marching al estilo Schneider Nubis a través de una capa horizontal, con formas generadas mediante una textura Perlin-Worley de 128³ erosionada por una textura Worley de 32³. Ambas se precalculan al arrancar mediante cómputo, sin ninguna descarga de red. La iluminación utiliza extinción según la ley de Beer, una función de fase de doble lóbulo y una aproximación de polvo. El acoplamiento clave es que el color solar de las nubes muestrea la misma tabla de transmitancia en cada paso, por lo que la iluminación de las nubes sigue el atardecer sin una segunda fase de ajuste.

En un M1, todo el sistema tarda entre 1,1 y 2,0 ms con las nubes a media resolución, muy por debajo del presupuesto de 6 ms, usa unos 14 MB de memoria de la GPU y funciona a más de 100 FPS. Las dos hipótesis principales se confirmaron en la práctica. El atardecer es la toma estrella, el momento que hace que el renderizador resulte cinematográfico, y surge de la física sin ajustes específicos para cada hora del día. Y hubo un accidente afortunado: al parametrizar la capa de nubes entre los 800 m y los 4000 m, desde una cámara baja las nubes lejanas del horizonte parecen crestas montañosas oscuras, lo que proporciona relieve de fondo al mundo sin que nadie tenga que modelarlo.

Conviene conservar una nota sobre la arquitectura. La estructura natural consiste en pintar primero el cielo en la cadena de intercambio y dejar después que three.js dibuje la geometría encima con autoClear = false. Esto no funciona con el renderizador WebGPU de r184, porque la opción no controla la operación de carga del color como lo hace en WebGL, por lo que three.js sobrescribe el cielo en cada fotograma. La solución es renderizar three.js en un destino fuera de pantalla y realizar la composición final (mix(skyCloud, scene, scene.alpha), seguida de ACES y luego sRGB) en un pase propio que controle la cadena de intercambio.

Un culling que solo es tan bueno como sus oclusores

Abrir el Spike 44 en una pestaña nueva ↗ · Ver código fuente

El Spike 44 compara el rendimiento de cuatro modos de renderizado: forward sencillo, culling de clústeres en la CPU, culling mediante cómputo en la GPU y un búfer de visibilidad con culling por oclusión Hi-Z. La ruta Hi-Z es la interesante y tenía un error discreto: su HUD indicaba que la oclusión estaba activada, pero el contador «Eliminados por Hi-Z» permanecía exactamente en el 0,0 % para siempre. El culling del frustum funcionaba, así que la fase previa del shader de culling estaba bien. La parte de oclusión no hacía nada, aunque se pagaba todo su coste.

Una prueba de oclusión Hi-Z proyecta en pantalla el cuadro delimitador de un clúster, elige un nivel mip de la pirámide de profundidad para que el rectángulo en pantalla ocupe unos 2×2 téxeles, muestrea la profundidad del oclusor más lejano dentro de ese rectángulo y descarta el clúster si su punto más cercano sigue estando más lejos que ese oclusor. La pirámide de profundidad se construye en cada fotograma inicializando el mip 0 a partir de un prepase de profundidad opaca y aplicando reducciones por máximos en los niveles sucesivos. El prepase opaco incluye deliberadamente solo oclusores sólidos —el suelo y proxies individuales para los troncos de los árboles—, porque el follaje con prueba alfa dejaría huecos que engañarían a una reducción por máximos.

El error era geométrico, no lógico. El proxy del tronco era una caja de 0,5 m por 4 m por 0,5 m. A 30 m se proyecta en unos 17 píxeles de pantalla. Sin embargo, un clúster de hierba típico situado a 50 m selecciona el mip 5, donde cada téxel abarca 32 píxeles de origen. Un tronco de 17 píxeles no cubre por completo ni un solo téxel del mip 5, así que cada téxel que toca el tronco también toca el suelo circundante. La primera reducción por máximos de 2×2 selecciona el valor de profundidad mayor, correspondiente al suelo más lejano situado detrás del tronco, y la profundidad del tronco se borra en la primera reducción. Al llegar al mip 5, la pirámide contiene la profundidad del suelo casi en todas partes, el clúster nunca está más lejos que el suelo y nunca se ocluye nada.

La solución consiste en hacer el proxy lo bastante grande como para dominar los téxeles que ocupa, dimensionándolo según la silueta del árbol y no según su madera. Un proxy de aproximadamente 2 m por 6 m por 2 m sigue siendo más pequeño que la copa real, por lo que las hojas visibles a través de los huecos nunca se eliminan en exceso, pero es lo bastante grande como para sobrevivir a la reducción por máximos hasta las distancias relevantes, y el contador de oclusión deja inmediatamente de estar a cero. La conclusión se puede generalizar como una regla para el motor de producción: todo aquello en lo que se confíe como oclusor Hi-Z debe dimensionarse de acuerdo con su silueta en pantalla, porque la eficacia de Hi-Z en escenas abiertas con follaje depende de la cobertura del oclusor en el mip correspondiente, no de la elegancia de las matemáticas de la prueba de profundidad. La oclusión de hierba contra hierba tampoco puede activarse, porque una brizna se encuentra a la misma profundidad que el suelo bajo ella, así que las mejoras reales proceden de los árboles que ocluyen follaje lejano y de los árboles que ocluyen otros árboles.

Tecnología mencionada en este capítulo

LUT de atmósfera de Hillaire 2020. Una tabla de transmitancia —la luz solar a través del perfil de Rayleigh, Mie y ozono— que solo se recalcula cuando se mueve el sol, junto con una tabla de vista del cielo por fotograma con parametrización no lineal del horizonte, proporciona colores del cielo y del sol basados en la física que siguen la hora del día sin ningún gradiente ajustado a mano. Un ajuste analítico para la dispersión múltiple sustituye a la tabla completa hasta que algún artefacto obligue a calcularla correctamente. El atardecer surge de la física sin ajustes específicos para cada hora del día.

Nubes volumétricas Schneider Nubis. Un ray marching a través de una capa horizontal, modelada mediante una textura Perlin-Worley de 128³ precalculada al arrancar y erosionada por una textura Worley de 32³, con iluminación basada en la extinción de la ley de Beer, una fase de doble lóbulo y un término de polvo. Muestrear en cada paso el color solar de las nubes a partir de la misma tabla de transmitancia hace que su iluminación siga el amanecer y el atardecer sin coste adicional. El ray marching a media resolución cuesta aproximadamente cuatro veces menos que a resolución completa, sin una pérdida visible de calidad a distancias habituales: el compromiso estándar en producción.

Composición de WebGPU sin procesar con three.js en r184. Pintar el cielo en la cadena de intercambio y dibujar encima la geometría de three.js con autoClear = false falla porque la opción no controla la operación de carga del color en el backend WebGPU. Hay que renderizar three.js en un destino RGBA16F fuera de pantalla y efectuar el mix final, el mapeo de tonos y la conversión a sRGB en un pase que controle la cadena de intercambio.

Culling por oclusión Hi-Z y dimensionamiento de oclusores. Una pirámide de profundidad construida mediante reducciones por máximos permite que un pase de culling en la GPU descarte los clústeres cuyo punto más cercano está detrás del oclusor más lejano de su rectángulo en pantalla. La prueba no hace nada, sin avisar, si los oclusores son demasiado pequeños para dominar un téxel en el mip seleccionado, porque la primera reducción por máximos sustituye la profundidad del oclusor por la del fondo más lejano situado detrás. Los oclusores deben dimensionarse según su silueta en pantalla, no según su núcleo físico. Consulta LOD controlado por la GPU.


Parte 22 de 29. Anterior: Parte 21: Un renderizador más rápido que no era más rápido Siguiente: Parte 23: Cincuenta avatares y una voz en la sala Guía de la serie: /es/blog/2026-02-25-open-world-browser-series-guide