Qué luz se puede precalcular y cuál no
La partición entre lo que depende de la vista y lo que no, entre lo estático y lo dinámico, y por qué el difuso indirecto es exactamente el término que se hornea.
La partición por dependencia de la vista
La radiancia que sale de un punto se puede descomponer en cuatro términos según su origen y su comportamiento:
| Término | ¿Depende de la vista? | ¿Depende del movimiento? | ¿Horneable? |
|---|---|---|---|
| Difuso directo | no | sí, si la luz o el objeto se mueven | sí, si todo es estático |
| Difuso indirecto | no | sí, si algo se mueve | sí, es el caso ideal |
| Especular directo | sí | sí | no |
| Especular indirecto | sí | sí | no, pero se aproxima con IBL |
La columna que decide es la primera. Un BRDF lambertiano dispersa la luz por igual en todas las direcciones salientes, así que la radiancia difusa que sale de un punto es la misma vista desde donde sea. Es un número por punto de la superficie, y un número por punto de la superficie es exactamente lo que cabe en una textura. Eso es un lightmap.
El especular, en cambio, es un lóbulo alrededor de la dirección de reflexión: mueve la cámara y el brillo se desplaza sobre la superficie. Guardarlo en una textura congelaría el brillo en su sitio y el objeto parecería tener una mancha pintada. Por eso ningún flujo de horneado hornea el especular, y por eso una escena con lightmaps sigue necesitando scene.environment para que los materiales brillantes se comporten.
Por qué el indirecto es el candidato perfecto
De los dos términos horneables, el que de verdad justifica todo el flujo de trabajo es el difuso indirecto, y por una razón económica: es el único de los cuatro que ningún renderizador en tiempo real puede calcular bien.
El difuso directo, si hace falta, sale de una DirectionalLight y un shadow map: cuesta un pase extra pero se puede. El especular directo es una evaluación de GGX. El especular indirecto tiene el PMREM. Pero el difuso indirecto —la luz que entra por una ventana, rebota en el suelo, tiñe el techo de amarillo y baja de nuevo— exige resolver el transporte global de luz, con múltiples rebotes e interreflexiones. Ningún motor web lo hace.
Y es justo el término que produce las señales visuales que el ojo asocia con «esto es un espacio real»: el color de rebote de las paredes, la penumbra suave en los rincones, la gradación de sombra bajo un mueble. Un renderizador sin ese término se ve plano y sin atmósfera, por muchas luces que le añadas.
El trazador de Blender, en cambio, sí lo calcula. Puede tardar cinco minutos por textura, pero eso da igual: se paga una vez, en tu ordenador, antes de publicar. Ese es todo el argumento del horneado: comprar tiempo offline para gastarlo en calidad online.
Qué cuenta como estático
Un lightmap es válido mientras nada de lo que contribuyó a él cambie. En concreto, tres cosas tienen que quedarse quietas:
La geometría que emite y la que recibe. Si un mueble se mueve, la sombra que proyectaba sigue pintada donde estaba y la que debería proyectar en su nueva posición no existe.
Las luces. Cambiar la hora del día invalida el lightmap entero. Si tu escena necesita un ciclo día-noche, la solución es hornear varios lightmaps e interpolar entre ellos, con el coste de memoria correspondiente.
Los materiales que participan en el rebote. Cambiar una pared de blanca a roja cambia el color del rebote sobre todo lo demás.
En la práctica, la escena se parte en dos categorías desde el principio del proyecto:
// Estatico: arquitectura, mobiliario fijo, terreno.// Recibe su iluminacion del lightmap. No necesita luces analiticas.paredes.material.lightMap = lightmapTextura;paredes.material.lightMapIntensity = 1.0;// Dinamico: personaje, puertas, objetos que el usuario mueve.// No tiene lightmap. Se ilumina con IBL mas una luz con sombra.personaje.castShadow = true;personaje.receiveShadow = true;Esa separación se decide antes de modelar, no después. Reasignar un objeto de una categoría a otra a mitad de proyecto implica rehacer UVs y volver a hornear.
Los tres artefactos de mezclar dinámico y horneado
Todos aparecen en el punto de contacto entre las dos categorías, y todos tienen la misma raíz: el lightmap no sabe que el objeto dinámico existe.
El personaje no recibe el rebote. El suelo está bañado de una luz amarilla preciosa que viene del lightmap, y el personaje que camina sobre él se ve gris, iluminado solo por el entorno. La mitigación estándar son las light probes: muestrear la iluminación indirecta en una rejilla de puntos y aplicar la más cercana al objeto dinámico. Three.js tiene LightProbe en el núcleo y LightProbeGenerator en three/addons/lights/LightProbeGenerator.js, que sabe construir una probe a partir de un cubemap.
El personaje no proyecta sombra sobre el lightmap. El lightmap es una textura fija; una sombra dinámica encima de él tiene que venir de un shadow map en tiempo real, lo que significa mantener una luz con sombra activa aunque toda la iluminación estática venga horneada. Es el motivo de que las escenas con lightmaps sigan teniendo una DirectionalLight: no ilumina, solo proyecta la sombra de lo que se mueve.
Doble contabilidad de la luz. El error más frecuente. Horneas el lightmap con el sol incluido y después dejas la DirectionalLight encendida en Three.js: la luz directa se suma dos veces y la escena sale lavada. Hay dos flujos válidos y hay que elegir uno: hornear solo el indirecto y dejar la luz directa en tiempo real, o hornear todo y apagar la luz salvo para proyectar sombras dinámicas, con intensity a cero.