Lighting y Lightmass

En UE4 hay multitud de técnicas para iluminar y hacer sombras. Las luces y sombras de un juego suelen ser el mayor problema de rendimiento y hacerlo de manera eficiente es clave para conseguir que el juego se vea bien y vaya fluido.

¿Por qué hay diferentes técnicas de iluminación? ¿Por qué no solo colocar las luces y que el Engine haga el resto? Bueno, eso puede ser verdad en motores de render que pueden tomar todo el tiempo del mundo para renderizar un frame (o frames) pero en motores de render en tiempo real, como Unreal, que deben renderizar 60 frames por segundo no hay más remedio que usar una serie de técnicas muy eficientes para iluminar la escena y proyectar sombras.

Cornell Box

Fíjate en la escena como hay una luz directa en el techo, esta luz rebota a lo largo de toda la escena e ilumina el resto de elementos. Fíjate como la luz blanca del techo, rebota en la pared derecha de color azul y a su vez rebota hacia el cubo derecho tiñendolo de color azul. Tenemos una primera luz directa, la luz blanca del techo, y unas luces secundarias que van rebotando de objeto en objeto, llamada luz bouncess o luz indirecta.

Light bouncess

En la imagen de arriba, de izquierda a derecha: sin luz, con luz directa, con luz directa e indirecta.

Nota como la imagen más de la derecha está más iluminada gracias a que la luz ha ido rebotando de objeto en objeto.

El sistema que modela como la luz es "rebotada" entre las superficies de los objetos que componen la escena se le suele denominar Global Illumination (GI). Esta luz que "rebota" (bouncess) también se suele llamar luz indirecta.

Nota importante: En UE4 tamibén se llama luz indirecta a la luz recogida por las lightprobes. Más info de qué es una lightprobe más abajo.

Tradicionalmente los videojuegos no tenían GI porque computar la luz indirecta es extremadamente costoso en realtime.

Una manera de aproximar el problema es pre-computar la luz indirecta de los objetos que sabemos que no van a moverse (también denominados static) y que la luz que les incide tampoco cambiara sus propiedades (intensidad, color, dirección, etc.,).

Esta técnica es llamada Baked GI ó Baked Lightmaps o simplemente el Baked. Además, esta técnica permite sombras más realistas ya que precomputar puede tomar todo el tiempo que queramos a diferencia de hacerlo en realtime. En concreto, bakear la luz es "meter" la luz en una textura que luego se aplica. Incluso si no se requiere computar la luz indirecta, bakear sigue siendo una buena idea por tema de rendimiento.

¿Cómo indicamos en UE4 que un actor es estático? En el panel de propiedades:

Modo estático para un actor

Las luces, en cambio, tienen 3 modos: estáticas static, estacionarias stationary y móviles movable.

Luces estáticas e introducción

Las luces estáticas son 100% bakeadas, tanto la luz que incide (directa e indirecta) así como las sombras que proyecta.

Técnicamente las luces estáticas no existen en tiempo de ejecución, todas son simuladas por texturas (también llamadas lightmaps).

Por tanto, sus propiedades no pueden cambiar en tiempo de ejecución. Debido a su propia naturaleza estática solo tiene sentido contra meshes estáticos.

Es decir, estas luces no tienen ninguna incidencia en meshes no estáticos (movable). Son extremadamente eficientes y ofrecen gran calidad ya que se calculan offline.

Luz estática

En la escena, la luz es estática. El actor izquierdo es estático y proyecta sombras y recibe luz mientras que el actor movable, a la derecha, no.

A diferencia de Unity que hay que colocar lightprobes, en UE4 ya hay colocadas lightprobes (en UE4 se llaman volumetric lightmass samples) en un grid alrededor del mapa.

Por tanto, la escena superior en realidad se vería en UE4 así:

Volumetric Probes

Nota como a pesar de que la luz es estática, el actor movable "recibe" luz y no proyecta sombra. Estos volumetric lightmass samples son en esencia lightprobes pero más sotisficados, abajo más detalles.

Aquí otro ejemplo, todas las esferas son movables mientras que el resto de elementos, incluida las luces, son static:

Light Probes

Nota como, efectivamente, todas las esferas reciben su luz gracias a los volumetric samples, sin embargo no proyectan sombras.

Por último notar que los actores estáticos también hacen luz indirecta, es decir, luz rebotada (en la imagen de abajo el actor rojo es estático a diferencia de las imágenes de arriba que era movable y por tanto la luz blanca puede "rebotar" en su superficie y es recibida por el actor estático amarillo):

La luz estática rebota y "tiñe" al resto de objetos. Global Illumination.

Una prueba aún más evidente; luz estática blanca, plataforma estática blanca y cubo estático rojo:

La luz estática rebota en el cubo rojo y tiñe el plano blanco

La luz estática incide en el cubo rojo y rebota.

Aquí con el cubo rojo siendo movable. No existe luz indirecta (bounce) en elementos movables. Todo lo movable no forma parte en el build lighting y por tanto no calcula la luz indirecta (bounce).

Misma escena, pero el cubo rojo es movable

Lightmass es el sistema de UE4 para calcular la luz indirecta, las sombras bakeadas, y en definitiva todas las interacciones complejas de la luz.

Lightmass crea texturas/mapas llamadas lightmaps. Los lightmaps almacenan los cálculos de la luz bakeada y que usará el motor para mostrarla en runtime.

Nota importante: Existe confusión en el término luz indirecta. En Unity, e internet en general, se suele denominar luz indirecta a la luz que rebota (bounce). Sin embargo, en la documentación de UE se llama luz indirecta a la luz que recogen los volumetric samples. Ya que el actor movable no es iluminado "directamente" por la luz si no que toma la luz de su volumetric sample más cercano. Es iluminado "indirectamente".

Volumetric Lightmaps

... o cómo hacer que las luces no movables den luz a los actores movables y que los actores movables reciban luz indirecta (bounce).

El sistema volumetric lightmaps con sus volumetric samples resuelven el mismo problema que el anterior sistema que había en UE (llamado Indirect Lighting Cache que es igual a los lightprobe de Unity) que es básicamente como hacer que los actores movables reciban luces estáticas y la luz rebotada. En esencia:

  1. El sistema volumetric lightmaps coloca los volumetric samples en un grid  a lo largo del mapa.
  2. Durante el lighting build estos samples recogen la luz indirecta.
  3. Los actores movables toman la luz de su sample más cercano.

Hasta aquí igual que el sistema anterior Indirect Lighting Cache. La diferencia viene en:

  • Si un actor lightmass importance volume es colocado, entonces los samples son colocados de manera "inteligente" dando más importancia a los alrededores de los actores estáticos:
Light Volumetric Grid
  • La interpolación de la luz se hace por pixel y no por componente. Es decir, anteriormente un componente (todo el componente) recibía la luz de su sample más cercano. Ahora es a cada pixel del componente. Cada pixel del componente recibe la luz de su sample más cercano. De manera que con el nuevo método cada parte del componente puede estar iluminada de una manera distinta.

Viejo método: todo el componente SkeletalMeshComponent (que es movable) toma la misma iluminación de su sample más cercano:

Nuevo método: en la imagen de más abajo, el actor (o mejor dicho, su componente SkeletalMeshComponent) toma su iluminación de distintos samples. La parte inferior está más iluminada que la superior, más oscura.

Stationary & Movable lights

Las luces movable son completamente dinámicas, 0 bakeadas, y por tanto iluminan y dan sombra todos los actores (movable o static).

Dado que no bakean nada no producen luz indirecta (bounce) ni contribuyen de modo alguno a los lightmaps.

Son las luces más costosas. Su coste depende del número de elementos que iluminen ya que deben proyectar sombra dinámica.

Para aliviar el coste en UE4 existe la característica Shadow Map Cache que como su propio nombre indica cachea los mapas de sombra de frame a frame y para los elementos que no se mueven se reutiliza las sombras de frames anteriores. Más info aquí.

En medio camino de la luz estática y la luz dinámica se encuentran las luces estacionarias (Stationary).

Las luces stationary no pueden moverse pero pueden cambiar algunas propiedades en tiempo de ejecución como el color o la intensidad.

Las luces stationary tienen todas las ventajas de las static lights: contribuyen a los lightmaps con luz indirecta (bounce), sombras bakeadas, etc., Es decir, cuando haces el build lighting las luces stationary actúan como una static light más. Como desventaja:

  • el coste, que las luces estáticas son baratas y las stationary están a medio camino entre las estáticas y las movables y
  • cada luz stationary necesita un canal de sombra diferente y solo hay 4 canales de sombra por tanto solo puede haber 4 luces stationary solapadas.

En tiempo de ejecución, los actores movables reciben su luz (como ocurre con las luces movables) y proyectan sombra.

Si la luz Stationary cambia de color o intensidad, todos los actores (static o movables) son actualizados en consonancia, al igual que una luz movable. Y al igual que una luz movable, estos cambios en tiempo de ejecución no influyen en la luz indirecta o cualquier otro elemento bakeado.

En la siguiente imagen: luz estática (de color blanco), cubo y plataforma estática y esfera movable:

Luz estática. Esfera (movable) no proyecta sombra. Plataforma (static) reciba luz roja indirecta.

Nota que la esfera al ser movable no proyecta sombra y la plataforma recibe luz roja (luz bounce) gracias a que la luz (blanca) rebota en el cubo (rojo).

Misma escena pero la luz es movable:

Luz movable. Todos los elementos proyectan sombras.

Todos los elementos proyectan sombra (incluída la esfera movable). Pero no existe luz indirecta. La luz puede moverse y cambiar sus propiedades. Los actores movables reciben luz dinámica.

Ahora la luz es estacionaria:

Luz estacionaria

Nota como todos los elementos proyectan sombra pero la sombra de los elementos estáticos es sombra bakeada mientras que la sombra de la esfera es sombra en tiempo real. Por otra parte hay luz indirecta (bounce).

Si la luz stationary cambia sus propiedades en tiempo real, por ejemplo el color blanco a color azul, ocurre lo siguiente:

Luz estacionaria. Luz indirecta bakeada.

Nota como todo lo "bakeado" y precalculado como la luz indirecta (bounce) de color azul sigue ahí.

Un caso práctico

Lo primero es calibrar la escena.

Dado que por defecto en UE4 están activados los efectos de posprocesados, es buena idea desactivarlos. Por ejemplo, el efecto de postprocesado vignette (viñeteo) oscurece las esquinas de la pantalla, es buena idea desactivarlo para tener una idea clara de como se está comportando la luz en la escena. Una vez chequeado que la luz funciona tal y como queremos, sería hora de activar el posprocesado.

Para calibrar la escena los ajustes que hay que desactivar son:

  • Auto Exposure
  • Vignette
  • Bloom
  • SSAO (Ambient Occlusion)
  • SSR (Reflejos)
  • Ajustes por defecto para Tonemapper
Calibrar la escena

Normalmente cuando creas una escena se empieza con una Directional Light (en modo Stationary).

Directional Light sin bakeo

Se hace el bakeo y entonces aparece la Global Illumination (GI):

Directional Light con bakeo

Posteriormente se añade un actor SkyLight para tomar la luz ambiental de la escena.

Directional Light con bakeo + Skylight

Cuando bakeas la información se almancena en lightmaps. Los lightmaps son una textura que almacena un conjunto de otras texturas, en HDR, junto con el color, la intensidad y la dirección de la luz.

Precisamente la propiedad Lightmap Resolution de cada actor define cuán largo será la textura empaquetada dentro del lightmap.

Por otra parte, la propiedad Min Lightmap Res tiene que ver con el padding entre islas UV, normalmente el Min Lightmap Res y el Lightmap Res coinciden.

El algoritmo para generar las UV (channel 1 por defecto) para el lightmap es muy eficiente y con muy buena calidad. En general no es necesario hacerlo desde un programa externo.

Lightmap Resolution

Para mejorar la calidad de la iluminación hay varios parámetros a tener en cuenta:

Lightmass Settings

La siguiente tabla resume cada parámetro:

Setting Influenciado por Build Time Valores usuales
Static Lighting Level Scale Detalles del Lightmass (Técnicamente, Irradiance Cache recording radius) Medio 0.1 - 1.0
Num Indirect Lighting Bounces Brillo de la escena. Número de fotones para la reflexión Bajo 4 - 5
Indirect Lighting Quality Disminuye el ruido al calcular el Lightmass (Técniacmente, el número de rayos para el final gathering) Alto 1 - 4
Indirect Lighting Smoothness Blurs indirect light component in Lightmap (Irradiance Caching Interpolation Radius) Bajo 0.7 - 1

Static Lighting Level Scale reducirá la escala del mundo y así tomará más samples. Cuando menos valor en teoría más calidad pero introduce mucho ruido. Para reducir dichos artefactos seguir la fórmula:

$${Static Lighting Level} * {Indirect Lighting Quality} = 1.0$$

Como último apunte para mejorar la calidad de la iluminación, se recomienda usar Lightmass Portal para indicarle al engine de dónde viene la luz entrante.  Los lightmass portal no emiten luz, son usados en pequeñas aperturas como puertas o ventanas y así el lightmass puede inteligentemente enfocar sus esfuerzos al calcular las sombras:

Light Portals

A la izquierda sin portal, a la derecha con portal.