Imagen de cabecera: una larga columnata iluminada por el sol, con sombras nítidas y estables sobre el paseo

Las sombras son uno de esos detalles que hacen que una escena 3D resulte creíble y bien asentada, y también de los más fáciles de estropear. Con esta actualización, las sombras del sol son más estables, más limpias y mucho más fáciles de ajustar, y las mismas mejoras llegan a las spot, point y area lights.

En resumen:

En este artículo

Por qué importan las sombras

Sin sombras, una escena parece flotar. Los objetos pierden su conexión con el suelo, cuesta más leer la profundidad y la iluminación se ve plana. Una buena sombra indica dónde está cada cosa, qué tamaño tiene y de dónde viene la luz.

Una mala sombra se nota igual de rápido: bordes que se arrastran cuando se mueve la cámara, rayas sobre una superficie o una sombra que se despega del objeto que la proyecta rompen la ilusión al instante. Eso es justo lo que ataca esta versión.

Cómo funciona una sombra en tiempo real

Una sombra real aparece porque algo bloquea la luz, pero un motor en tiempo real no traza la luz. Recurre a un truco más barato y muy eficaz, el shadow map:

  1. Mirar desde la luz. Se renderiza la escena desde el punto de vista de la luz, pero de cada píxel solo se guarda la distancia a la superficie más cercana. El resultado es el shadow map.
  2. Mirar desde la cámara. Para cada píxel de la pantalla se calcula dónde queda visto desde la luz y se compara su distancia con la que registró el shadow map.
  3. Decidir. Si el shadow map registró algo más cerca de la luz, el píxel está en sombra. Si no, está iluminado.

Diagrama: un shadow map en tres pasos

El problema es que un shadow map es una textura de resolución fija: es como una foto de baja resolución de la escena, tomada desde la luz. Casi todas las técnicas de sombras son, en el fondo, formas de sacarle el máximo partido a esa resolución limitada.

¿Y las sombras con ray tracing y path tracing?

Los shadow maps son una aproximación, así que es lógico preguntarse por qué no se traza directamente la luz. En GPUs con ray tracing por hardware se puede: se lanza un rayo desde cada punto visible hacia la luz y, si algo se interpone, el punto está en sombra. No hay resolución de textura que se agote, así que no hay bloques, ni acne, ni peter-panning, y las sombras suaves salen con precisión. El path tracing va un paso más allá y sigue muchos caminos aleatorios de luz mientras rebotan por la escena, de modo que sombras, reflejos e iluminación indirecta salen del mismo proceso. El sample de path tracer de Evergine muestra cómo se ve.

Diagrama: shadow maps frente a sombras con ray tracing

El precio es el rendimiento. Una imagen limpia necesita varios rayos por píxel en cada frame, normalmente seguidos de un denoiser. La escena necesita una estructura de aceleración que hay que reconstruir cuando los objetos se mueven. Y los rayos se dispersan en todas direcciones, lo que castiga mucho más a la memoria de la GPU que rasterizar triángulos sobre una textura. Incluso las GPUs de escritorio más potentes reparten sus rayos con cuidado, y la mayoría de móviles, tablets y dispositivos modestos no tienen ray tracing por hardware.

Por eso los shadow maps siguen siendo el caballo de batalla de las sombras en tiempo real: son baratos, predecibles y funcionan en prácticamente cualquier dispositivo. El ray tracing es una gran opción donde el hardware lo permite, pero los shadow maps son lo que mantiene una escena funcionando en todo lo demás, y por eso seguimos invirtiendo en ellos.

El problema del sol y las cascadas

Una directional light hace de sol, y el sol lo ilumina todo. Un único shadow map tendría que cubrir todo el mundo visible, así que una textura estirada sobre cientos de metros daría sombras pixeladas justo delante del jugador, que es donde más importa el detalle.

La solución clásica son los cascaded shadow maps. Se divide la vista en franjas según la distancia a la cámara: una pequeña cerca de la cámara, otra más grande detrás, y así sucesivamente. Cada franja tiene su propio shadow map, de modo que las sombras cercanas son nítidas y las lejanas más toscas. Lo lejano se ve pequeño en pantalla, así que casi no se nota.

Diagrama: el frustum de la cámara dividido en cuatro cascadas, cada una renderizada en un shadow map de la misma resolución

Evergine ya usaba cascadas. Lo que cambia con esta versión es que ahora se comportan mucho mejor.

Dónde se quedaba corto el enfoque anterior

Tres problemas aparecían una y otra vez:

Novedades

Cascadas que ya no parpadean

El espacio de sombras depende ahora solo de la orientación de la luz, no de hacia dónde mira la cámara. Además, cada cascada se ajusta a la cuadrícula de texels del shadow map y su tamaño solo cambia en pequeños saltos de en torno al 2 %. Gracias a eso, mover o girar la cámara ya no cambia qué texel cubre cada parte del mundo, y el shimmering desaparece.

Diagrama: el borde de una sombra a lo largo de tres frames, antes y después de ajustar las cascadas a la cuadrícula de texels

Un bias que entiende la escena

El bias se aplica ahora en espacio mundo y se mide en texels del shadow map. Dos propiedades nuevas, ShadowDepthBias y ShadowNormalBias, se escalan solas según el tamaño de los texels de cada cascada y la suavidad del filtro de sombras. Un único ajuste se comporta ahora con sentido desde la cascada más cercana hasta la más lejana, lo que elimina las bandas de acne en ángulos rasantes y las sombras flotantes en la distancia.

Diagrama: bias insuficiente, bias equilibrado y bias excesivo

Spot, point y area lights

El modelo de bias basado en texels no se limita al sol: las spot, point y area lights también lo usan ahora, así que ShadowDepthBias y ShadowNormalBias significan lo mismo en cualquier tipo de luz. De paso se han corregido dos problemas:

Diagrama: la guard band que hace que las caras del cubo se solapen alrededor de una point light

Cascadas totalmente configurables

Las directional lights exponen ahora el equilibrio entre calidad y rendimiento:

Diagrama: los fit modes Stable y Tight de las cascadas

Nota: el update interval no tiene efecto cuando una luz la renderizan varias cámaras.

Mejor depuración

El Debug Mode de las luces muestra ahora el color de cada cascada (y de cada cara del cubo en las point lights), además de un tablero de ajedrez de 8x8 texels del shadow map, que permite ver con exactitud lo densas que son las sombras en pantalla. Las spot lights también tienen ahora su propio DebugMode.

También se han corregido varios problemas relacionados: las distancias de división de las cascadas usan ahora la fórmula correcta, las cámaras ortográficas con el plano near a cero ya no generan divisiones inválidas, y renderizar varias cámaras con varias directional lights que proyectan sombras ya no hace que unas luces pisen los shadow maps de otras. Además, los backends gestionan el depth clamping de forma coherente, de modo que los objetos situados delante de una cascada ya no se recortan.

El antes y el después

Basta con arrastrar el control para comparar el sistema de sombras anterior con el nuevo, en la misma escena y con la misma posición de cámara.

Antes: columnata vista desde el paseo, con el sistema de sombras anterior Después: columnata vista desde el paseo, con el nuevo sistema de sombras
Comparación 1
Antes: escena de prueba, vista 2, con el sistema de sombras anterior Después: escena de prueba, vista 2, con el nuevo sistema de sombras
Comparación 2
Antes: valla y base de una columna, con el sistema de sombras anterior Después: valla y base de una columna, con el nuevo sistema de sombras
Comparación 3
Antes: escena de prueba, vista 4, con el sistema de sombras anterior Después: escena de prueba, vista 4, con el nuevo sistema de sombras
Comparación 4
Antes: escena de prueba, vista 5, con el sistema de sombras anterior Después: escena de prueba, vista 5, con el nuevo sistema de sombras
Comparación 5
Antes: columnata y obeliscos en la distancia, con el sistema de sombras anterior Después: columnata y obeliscos en la distancia, con el nuevo sistema de sombras
Comparación 6

Primeros pasos

Todo se puede ajustar desde el editor o desde código. Los valores siguientes son puntos de partida orientativos que conviene adaptar a cada escena:

// Obtener la directional light de la entidad que hace de sol
var sun = sunEntity.FindComponent<DirectionalLight>();

sun.IsShadowEnabled = true;
sun.ShadowDistance = 100;                   // Hasta dónde se renderizan las sombras desde la cámara
sun.ShadowCascadeCount = 3;                 // Entre 1 y 4

// Mantener las cascadas estables y desvanecer las sombras con suavidad
sun.CascadeFitMode = DirectionalLight.CascadeFitModes.Stable;
sun.ShadowFadeFraction = 0.1f;
sun.CascadeBlendFraction = 0.1f;

// El bias se mide en texels del shadow map
sun.ShadowDepthBias = 1;
sun.ShadowNormalBias = 1;

Esto activa un sol que proyecta sombras con tres cascadas estables, una mezcla suave entre ellas y un ligero desvanecimiento en la distancia de sombras. Si aparece acne, conviene subir un poco los bias; si las sombras se ven despegadas de los objetos que las proyectan, bajarlos.

Actualización desde la versión anterior

Algunos cambios afectan a los proyectos existentes:

Pruebas y comentarios

¿Listo para dar a las escenas sombras más estables y limpias? Basta con actualizar Evergine, abrir una escena con una directional light y probar las nuevas opciones.

Nos encantará conocer la opinión de la comunidad.

José Antonio Collado Araque
Autor
José Antonio Collado Araque
Graphics Engineer - Researcher