wandres.dev
ENTORNOS E IBL · HDRI, envMap y la luz realista

Por qué un entorno ilumina mejor que diez luces

Qué integral resuelve la iluminación basada en imagen, cómo la aproximación de suma dividida la vuelve viable en tiempo real, y por qué el especular es donde se nota la diferencia.

⏱ 18 min

Una luz analítica aporta una dirección de luz. Un entorno aporta todas. La diferencia no es de grado: cuando iluminas con un entorno, cada fragmento integra la radiancia que le llega desde el hemisferio completo, y por tanto un metal recibe un reflejo con la forma de la habitación en vez de un punto blanco. Ese salto explica por qué una escena con un buen HDRI y ninguna luz se ve mejor que la misma escena con seis focos colocados a mano, y explica también por qué la iluminación basada en imagen es la técnica dominante desde hace quince años.

🎯 Al terminar esta lección sabrás
  • Escribir la integral que resuelve el IBL y señalar qué la hace intratable en tiempo real.
  • Explicar la aproximación de suma dividida y en qué se apoya para funcionar.
  • Distinguir la parte difusa de la especular del entorno y por qué se precalculan por separado.
  • Justificar por qué un entorno cuesta lo mismo con una fuente que con mil.

La integral que hay detrás

La radiancia que sale de un punto hacia el observador es la integral, sobre todas las direcciones entrantes del hemisferio, de la radiancia que llega multiplicada por el BRDF y por el coseno del ángulo con la normal:

Lo( v ) = integral sobre el hemisferio de  Li( l ) * f( l, v ) * ( n . l )  dl

Con una luz puntual, esa integral se colapsa a un único término porque Li es cero en todas partes menos en una dirección: una delta de Dirac. Eso es exactamente lo que hace una luz analítica y por eso es barata.

Con un entorno, Li está definida en todas las direcciones, y viene dada por una textura. La integral es real y hay que resolverla. Muestrearla por fuerza bruta —cientos de muestras por fragmento— es lo que hace un trazador de rayos y es lo que no puedes permitirte a sesenta hercios.

La suma dividida

La solución que usa toda la industria desde 2013, publicada por Brian Karis para Unreal Engine 4, es partir la integral en dos factores que se calculan por separado y se multiplican:

integral( Li * f * cos )  ~=  [ integral( Li * cos ) ]  *  [ integral( f * cos ) ]

Matemáticamente esto no es una identidad; es una aproximación, y buena porque en la práctica el BRDF especular está concentrado alrededor de la dirección de reflexión y la radiancia entrante varía suavemente. El error es pequeño salvo con entornos de contraste extremo.

Lo importante es que cada factor se puede precalcular.

El primer factor depende del entorno y de la rugosidad, no del material concreto ni del ángulo de visión. Es una convolución del entorno con el lóbulo especular, y su resultado se guarda en una cadena de mips donde cada nivel corresponde a una rugosidad: mip 0 es el entorno nítido para superficies especulares, y los mips altos son el entorno progresivamente difuminado para superficies rugosas. Ese es exactamente el trabajo del PMREMGenerator, y es el tema de la lección de PMREM.

El segundo factor no depende del entorno en absoluto: solo de la rugosidad y del ángulo entre la normal y la vista. Se puede tabular una vez para siempre en una textura 2D de búsqueda —la famosa BRDF LUT—, o aproximar con un polinomio. Three.js usa la segunda opción: una función analítica en el chunk bsdfs, lo que ahorra una textura y una lectura por fragmento.

El resultado es que en tiempo de ejecución un fragmento con entorno cuesta una lectura de textura con textureLod en el nivel que corresponde a su rugosidad, más unas cuantas operaciones aritméticas. Una sola lectura para representar la contribución de todo el hemisferio.

ℹ️
De aquí sale la propiedad más importante del IBL

El coste no depende del contenido del entorno. Un HDRI de un estudio con tres softboxes y otro de una plaza al atardecer con cientos de fuentes de luz cuestan exactamente lo mismo: una textura, una lectura. Diez luces analíticas cuestan diez evaluaciones del BRDF por fragmento. Ese es el argumento económico que gana siempre en escenas de producto.

Difuso y especular: dos consultas distintas

El material físico de Three.js consulta el entorno dos veces, y por motivos distintos.

Para el término difuso, la respuesta depende solo de la normal, porque un BRDF lambertiano dispersa por igual en todas las direcciones. Lo que se necesita es la irradiancia: la integral del entorno con un lóbulo coseno, que es enormemente suave. Basta con muy poca resolución. Three.js la obtiene del último nivel de la cadena PMREM, el más difuminado.

Para el término especular, la respuesta depende de la dirección de reflexión y de la rugosidad. Se muestrea el nivel de mip correspondiente a la rugosidad del material, en la dirección reflect( -viewDir, normal ).

// La consecuencia practica: metalness y roughness controlan
// cuanto y como se consulta el entorno.
const metal = new THREE.MeshStandardMaterial( {
  metalness: 1.0,   // sin difuso: TODO el color viene del entorno
  roughness: 0.15,  // reflejo casi nitido: mip bajo del PMREM
} );

const yeso = new THREE.MeshStandardMaterial( {
  metalness: 0.0,   // sin especular apenas: casi todo difuso
  roughness: 0.95,  // el especular que hay viene del mip mas alto
} );

Aquí se ve por qué un metal sin entorno sale negro y no es un bug: un material con metalness = 1 tiene color difuso cero por definición física, así que si no hay nada que reflejar, no hay nada que ver. Un metal necesita un entorno; es la única fuente de su apariencia.

Qué gana un entorno frente a más luces

Merece la pena enumerarlo porque no todo es reflejo.

Rango dinámico en la fuente. Un HDRI guarda valores por encima de 1: el sol puede valer 5000 y el cielo 3. Cuando esa distribución se convoluciona, los altos dominan la energía y el reflejo del sol sigue siendo un punto brillante después del desenfoque. Con luces analíticas colocadas a mano esa jerarquía hay que inventarla.

Color coherente. Las paredes de una habitación tiñen todo lo que hay dentro. Un entorno lleva ese color de rebote en cada dirección; simularlo con luces exige colocar una luz por pared y acertar con los colores.

Oclusión implícita en el contenido. Un entorno capturado en un interior ya tiene el techo oscuro y las ventanas brillantes, así que la variación direccional viene incluida.

Lo que un entorno no da: sombras proyectadas. Un scene.environment ilumina desde todas partes y no ocluye nada. Por eso la combinación estándar de producción no es «entorno o luces», sino entorno para la iluminación general y una única luz direccional con sombra para la forma y el contacto.

Un entorno mal expuesto es peor que ninguna luz, y el culpable suele ser el mapeo tonal

Todo el argumento del IBL se apoya en que el entorno traiga valores de radiancia coherentes. En cuanto eso se rompe, la escena se degrada de formas que parecen problemas de material y no lo son. El fallo más frecuente es cargar un HDRI que se capturó con una exposición distinta a la que espera tu escena: el resultado tiene la distribución correcta pero la escala equivocada, y como el mapeo tonal es una función no lineal, un factor global de dos no oscurece la imagen a la mitad sino que la desplaza a otra región de la curva, donde el contraste y la saturación son distintos. La gente entonces sube roughness para «quitar brillo», o baja metalness para «que no se vea tan reflectante», y acaba con materiales que ya no son físicamente coherentes y que se romperán en cuanto cambies de entorno. El diagnóstico correcto se hace al revés y es rápido: pon una esfera de metalness = 0, roughness = 1 y color 0.5 en la escena —una esfera de gris medio perfecto—, y mira si sale gris medio. Si sale gris oscuro, el entorno viene corto de exposición y hay que subir scene.environmentIntensity; si sale casi blanco, viene largo. Solo cuando esa esfera de referencia salga bien tiene sentido tocar un material. Es el equivalente digital de la carta gris del fotógrafo, cuesta treinta segundos montarla, y ahorra tardes enteras de ajustar propiedades que no eran el problema.