Saltar al contenido

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.

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:

Texto
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:

Texto
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.

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 ).

JS
// 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.