wandres.dev
BAKING Y LIGHTMAPS · Precalcular la luz

Tiempo real o horneado: el criterio con números

Cuánto cuesta cada opción en memoria, en descarga y en milisegundos de frame, qué preguntas decidir antes de modelar, y el patrón híbrido que usa casi toda la producción.

⏱ 17 min

Horneado y tiempo real no son dos filosofías enfrentadas: son dos formas de pagar la misma factura. El horneado paga en descarga y memoria; el tiempo real paga en milisegundos de frame. Cuál de las dos monedas te sale más barata depende de tres cosas medibles —cuánto se mueve la escena, cuánta superficie estática tiene y en qué dispositivo se va a ver—, y decidirlo con números al principio del proyecto evita el rehacer que se come los plazos.

🎯 Al terminar esta lección sabrás
  • Estimar el coste en megabytes de hornear una superficie dada a una densidad dada.
  • Estimar el coste en frame de la alternativa en tiempo real equivalente.
  • Aplicar el árbol de decisión a un caso concreto y justificar la respuesta.
  • Montar el patrón híbrido separando la escena en capa estática y capa dinámica.

Los números del horneado

El coste de un lightmap se calcula desde la superficie de la escena y la densidad elegida. Para un interior a 10 texels por metro:

Escena Superficie estática Texels Atlas Peso WebP VRAM
Habitación pequeña 60 m² 6 000 1024² ~120 KB 4 MB
Piso completo 400 m² 40 000 2048² ~450 KB 16 MB
Nave industrial 3 000 m² 300 000 4096² × 4 ~6 MB 256 MB

La columna de VRAM es la que sorprende. Un WebP de 450 KB se descomprime en la GPU a una textura RGBA sin comprimir de 2048 por 2048 por 4 bytes, es decir 16 MB, más un tercio si genera mipmaps. El peso de descarga y el peso en memoria no tienen nada que ver, y el que limita en móvil es el segundo. Ahí es donde entra KTX2 con compresión de bloques, que mantiene la textura comprimida en la GPU y divide la VRAM entre cuatro o seis.

La última fila muestra dónde deja de escalar el horneado. Un almacén grande a densidad decente no cabe en un solo atlas, y con cuatro atlas de 4096 ya estás en un cuarto de gigabyte de memoria de vídeo para iluminación estática. En ese punto, la respuesta no es hornear mejor: es bajar la densidad a 2 o 4 texels por metro, o cambiar de estrategia.

Los números del tiempo real

La alternativa equivalente —una DirectionalLight con sombras más un entorno IBL— cuesta esto en una escena de 200 objetos:

El pase de sombra. Un recorrido completo de la escena, con culling y draw calls, por cada luz con sombra. Para 200 objetos con castShadow, entre 0.8 y 2.5 ms de CPU en un portátil, y el doble o el triple en un móvil.

El muestreo por fragmento. Con PCFSoftShadowMap, dieciséis lecturas de textura por fragmento sombreado. En una pantalla de 1920 por 1080 con la escena cubriendo el 70 %, unas 23 millones de lecturas por frame. Entre 0.5 y 2 ms de GPU según el ancho de banda.

La memoria del shadow map. 16 MB a 2048, más el entorno PMREM, que ronda los 5 MB para un cubemap de 256 en half-float.

Y a cambio de eso no obtienes iluminación indirecta. Ese es el punto que cierra la comparación: el tiempo real no es una versión más barata del horneado, es una técnica que resuelve un problema distinto y peor.

El árbol de decisión

Las preguntas, en el orden en que hay que hacérselas:

¿La escena tiene iluminación indirecta que importe? Un interior con ventanas, un espacio con paredes de color, cualquier sitio donde el rebote defina la atmósfera: sí, y entonces el horneado no tiene sustituto. Un objeto flotando sobre fondo neutro: no, y entonces IBL basta y el horneado es trabajo tirado.

¿La geometría estática es una fracción grande de lo que se ve? Si el 90 % de los píxeles son arquitectura fija, hornear paga. Si el 90 % es un personaje que se mueve, no hay nada que hornear.

¿Cambia la iluminación? Un ciclo día-noche o un interruptor invalidan el lightmap. Hornear varios e interpolar es posible pero multiplica la memoria por el número de estados.

¿El objetivo es móvil? Si lo es, la balanza se inclina fuerte hacia el horneado, porque el móvil tiene poca CPU para pases de sombra y poca GPU para muestreo, pero la descarga la puede hacer una vez y cachearla. El horneado convierte coste recurrente por frame en coste único de carga.

¿Hay presupuesto de producción? Un horneado decente de un interior son entre uno y tres días de trabajo manual de UVs y ajuste. Si el proyecto no los tiene, la respuesta honesta es tiempo real con un buen IBL y aceptar que no habrá rebote.

El patrón híbrido

Lo que hace en la práctica casi toda la producción no es elegir: es partir la escena.

// CAPA ESTATICA: arquitectura, mobiliario fijo.
// Iluminacion horneada. Ni recibe ni necesita luces analiticas.
escenario.traverse( ( o ) => {
  if ( ! o.isMesh ) return;
  o.material.lightMap = atlasLightmap;
  o.material.lightMapIntensity = 1.0;
  o.receiveShadow = true;    // para recibir la sombra de lo dinamico
  o.castShadow = false;      // su sombra ya esta en el lightmap
} );

// CAPA DINAMICA: personajes, objetos manipulables.
// Iluminacion de entorno mas una luz con sombra.
scene.environment = pmrem.fromEquirectangular( hdr ).texture;

const sol = new THREE.DirectionalLight( 0xffffff, 0.0 );  // intensity 0
sol.castShadow = true;                                     // solo proyecta
sol.position.set( 6, 12, 4 );
scene.add( sol );

personaje.castShadow = true;
personaje.receiveShadow = true;

Las dos líneas que resumen todo el patrón son estas: castShadow = false en el escenario, porque su sombra ya está pintada en el lightmap y volver a calcularla sería duplicarla; e intensity = 0 en la luz direccional, porque no está ahí para iluminar sino solo para generar el shadow map que proyecta el personaje sobre el suelo horneado.

Es un montaje raro la primera vez que lo ves —una luz que no ilumina— y es exactamente lo correcto. La luz directa vive en el lightmap; lo único que falta es la sombra de lo que se mueve, y una luz con intensity cero y castShadow verdadero produce exactamente eso y nada más.

💡
Sombras de contacto sin ningún shadow map

Para muchos objetos dinámicos ni siquiera hace falta la luz. Un plano horizontal bajo el objeto con una textura de mancha radial, transparent: true y depthWrite: false, siguiendo la posición del objeto, da una sombra de contacto convincente por el coste de un quad. Es lo que usan la mayoría de los configuradores de producto, y es del orden de mil veces más barato que un shadow map.

El horneado progresivo cambia la ecuación, y Three.js lo trae sin que casi nadie lo sepa

Todo lo anterior asume que hornear es una operación offline en Blender con un flujo de trabajo pesado. Existe una tercera vía que rompe esa dicotomía y que está en los addons desde hace años: ProgressiveLightMap, en three/addons/misc/ProgressiveLightMap.js. La idea es acumular la iluminación en el navegador y en tiempo de ejecución, frame a frame. En cada frame se mueve la luz aleatoriamente dentro de un volumen que representa el tamaño de la fuente, se renderiza la escena a la textura de lightmap usando el segundo juego de UVs como coordenadas de salida en el vertex shader, y se promedia con lo acumulado. Después de unos cientos de frames —dos o tres segundos— la textura converge a la solución con penumbra suave real, sombras de área correctas y un rebote aceptable. Lo que esto habilita es un caso que ninguna de las dos opciones clásicas cubre: escenas que el usuario configura. Un planificador de cocinas donde el usuario coloca los muebles no puede tener un lightmap horneado, porque la geometría la decide él; y con sombras en tiempo real nunca va a tener el aspecto de un render. Con horneado progresivo, la escena se recalcula sola cada vez que el usuario suelta un mueble, tarda dos segundos en converger y el resultado tiene calidad de render offline. Las restricciones son reales —hace falta el segundo juego de UVs igual que en el horneado clásico, la convergencia bloquea la GPU mientras dura, y la geometría no puede moverse durante la acumulación—, pero cuando el caso encaja, no hay alternativa que se le acerque. Es el ejemplo más claro de que la pregunta «horneado o tiempo real» está mal planteada: lo que decide es cuándo puedes permitirte hacer el cálculo, y «dos segundos después de que el usuario suelte el ratón» es una respuesta perfectamente válida que la dicotomía clásica no contempla.