Sombras en Unreal

En UE4 hay muchas técnicas para sombreado: sombras bakeadas, stationary light shadows, dynamic shadows, directional light cascading shadow maps, distance field shadows, cascading shadow maps, contact shadows, ray traced distance field soft shadows, distance field indirect shadows y quizás alguna otra. Casi todas son complementarias y sirven para distintos propósitos. Por defecto hay activadas algunas y otras están desactivadas. Como ves todo un arsenal de sombras, veamos como funcionan y cuál debemos escoger en cada momento.

Distance Field Shadows

El algoritmo base para las sombras en UE4 es el Distance Field Shadows (también llamado distance field shadowmaps). Está basado en el clásico cascading shadow maps.

¿Qué es Cascading Shadow Maps? Es un algoritmo clásico para añadir sombras a los juegos. La idea básica es testear cuando un pixel es visible desde la fuente de luz, si no es visible, entonces hay sombra.

El algoritmo consiste en dos pasadas: la primera construye un mapa de profundidad (a veces llamado shadow depth map) en el espacio de luces; la segunda compara la profundidad de cada pixel (en espacio de luces) con la correspondiente profundidad en el mapa calculado en el paso uno. Si el pixel $(x,y)$ tiene mayor profundidad (z) que el valor $(x,y)$ en el shadow depth buffer significa que la luz está ocluida y por tanto hay que pintar sombra en dicho pixel.

Esta técnica presenta un problema bastante común y es el perspective aliasing:

A la izquierda, artefactos propios de perspective aliasing

¿Por qué ocurre esto? Esto es debido a que cerca de la cámra los píxeles son más cercanos los unos a los otros y requieren de una resolución shadow depth map mayor.

En la siguiente imagen se ilustra la resolución del depth map junto al frustum de la cámara. Cuanto más cerca de la cámara necesitamos más resolución y cuanto más lejos necesitaremos menos resolución.

Shadow map con respecto al frustum de la cámara

Y ahí es dónde entra el Cascaded Shadow Map. La idea es dividir el frustum d ela cámara y usar para cada división un depth buffer de mayor resolución cuanto más cerca de la cámara esté.

Cascaded Shadow Map Frustum

Sin embargo hemos dicho que el algoritmo base para el cálculo de sombras en UE4 es Distance Field Shadows. ¿En qué es mejor Distance Field Shadows de Cascaded Shadow Map?

Distance Field Shadows es una mejora, propuesta por Valve, del algoritmo clásico de shadow casting. En el shadow casting clásico, a cada pixel se le asocia sombra (1) o sin sombra (0). En el distance field shadowmaps a cada pixel se le asocia la distancia a la sombra más cercana. De este modo, las sombras tienen mejor calidad porque se puede hacer una transición entre sombra y sin sombra. También funciona muy bien en bajas resoluciones.

Las cuatro técnicas principales de sombreado en UE4 son las static lights, los directional light cascading shadow maps, las stationary light shadows y las dynamic shadows.

Static Lights

Las Static Lights hacen sombras bakeadas, sombras gratis. Obviamente los actores movables no proyectan sombra ya que la luz sencillamente "no existe", está bakeada en la textura. Requiere de "cocinado" previo, de light building.

En las static lights se pueden suavizar las sombras usando la propiedad Light Source.

Shadow Light Source

La luz de la izquierda tiene menos Light Source que la luz de la derecha.

En la mayoría de ocasiones serán los componentes StaticMeshComponent, que son los responsables de renderizar los static mesh, quienes en última instancian reciben la sombra que proyecten otros objetos.

Una propiedad importante de las static light son las Lightmap Resolution sobre dichos componentes que permite controlar el nivel de detalles de las sombras.

Existen dos técnicas para que los actores movables proyecten sombra bajo luces estáticas: Capsule Shadow para los SkeletalMeshComponent y Distance Field Indirect Shadows para los StaticMeshComponent. Lo discutiremos más abajo.

Directional Light Cascading Shadow Maps

Hay cuatro tipos de luces: Directional, Point, Spot y Sky. Y tres modos: static, stationary y movable. Este tipo de sombra solo se dan en las luces Directional Stationary.

Las luces direccionales estacionarias son especiales porque soportan toda las sombras de la escena a través de Cascaded Shadow Maps al mismo tiempo que soporta sombras estáticas. A partir de una determinada distancia todas las sombras son estáticas mientras que menos de esa distancia son dinámicas, la transición es indistinguible.

Es muy útil en niveles con mucho follaje animado dónde quieres sombras moviendo alrededor del personaje pero no pagar el coste de tener que calcular las sombras del follaje lejano.

Se puede setear la distancia de dicha transición cambiando la propiedad Dynamic Shadow Distance StationaryLight.

Se puede cambiar las sombras estáticas por Ray Traced Distance Field Shadows, de manera que la transición sea de CSM (Cascade Shadow Maps) a RTDF (Ray Traced Distance Field). Más info más abajo.

Por otra parte, las luces direccionales estacionarias, a partir de la versión 4.9, tienen disponible Area Shadows (se activa con un checkbox) que ofrecen sombras más suaves:

Area Shadows

Stationary Light Shadows

Las luces stationary proyectan sombras estáticas para los objetos estáticos y sombras dinámicas (distance field shadowmaps) para los objetos dinámicos (como los StaticMeshComponents o SkeletalMeshComponent con mobility en Movable).

Bajo una luz dinámica (es decir, luz movable) todos los objetos (estáticos o movables) proyectan sombra y, por tanto, un objeto movable recibe la sombra que proyecta un objeto estático bajo una luz movable, se trata de una sombra dinámica.

En las luces estáticas, en cambio, se bakean las sombras y los objetos movable no solo no proyectan si no que no reciben la sombra que proyecta un objeto estático.

El caso de las luces estacionarias es complicado, porque la sombra que proyecta un objeto estático bajo una luz stationary es una sombra estática (está bakeada) y el objeto movable DEBE recibir dicha sombra. Para ello, las luces estacionarias soportan un nuevo tipo de sombra las stationary light shadows:

Shadows Stationary

En la imagen superior la pared es estática así que la sombra proyectada bajo una luz stationary es estática (está bakeada). La esfera es movable y la sombra que proyecta bajo una luz stationary es dinámica.

Shadows Stationary

En esta imagen es dónde ocurre la "magia" de las luces stationary. El objeto es movable y sin embargo está recibiendo sombra estática, cosa que no ocurriría si la luz fuera estática**. Eso es debido a que bajo las luces stationary todos los objetos movables tienen dos luces dinámicas, una proveniente del mundo "estático" y otra la luz dinámica que proyecta, el engine se encarga de mezclar ambas.

** Si bajo esa misma escena seteas la luz a estática verás como la esfera parece que sigue "recibiendo" la sombra (en realidad no recibe sombra alguna, lo que recibe es menos luz y parece más oscura) y cuando la mueves a una zona iluminada la esfera reciba dicha luz. Es como si la luz estática fuera dinámica. Lo que ocurre es que la esfera movable recibe la luz de su volumetric sample. Más info aquí en el post sobre Lighting & Lighmass.

Dynamic Shadows

Las sombras dinámicas no están bakeadas en absoluto y dan sombra a TODO (static o movables). Son las más caras. Cuantos más elementos tengan que sombrear, más caras son.

Para las luces dinámicas UE4 usa Shadow Map Caching para mejorar el rendimiento. Esto es, si la luz dinámica junto con el resto de elementos que sombrea no se mueven en un determinado frame se reutiliza las sombras calculadas del frame anterior.

Se puede medir el rendimiento de las sombras con el siguiente comando de la consola:

stat shadowrendering

En concreto, las estadísticas relevantes son CallCount y InclusiveAug.
Y puedes activar/desactivar el shadow map caching con:

r.Shadow.CacheWholeSceneShadows 0

Preview Shadows

Son las sombras que ofrece el engine cuando aún no se ha hecho el build lighting.

Capsule Shadows

En entornos dónde sólo hay luz indirecta (estática / bounce) los objetos movables no proyectan sombra. Por ejemplo bajo una luz estática.

Las capsule shadows sirven para que los Skeletal Mesh Component (por ejemplo el de un character) proyecte sombra bajo luz estática.

Para los Static Mesh está la técnica Distance Field Indirect Shadows (ver más abajo).

La técnica capsule shadows usa las cápsulas de colisión de sus respectivos Physics Asset junto a la información sobre la dirección e intensidad de la luz que provee los volumetric samples más cercanos para proyectar su sombra.

Shadows Capsule

Para activar este modo de sombreado, en el componente skeletal mesh en el apartado lighting añadir el physics asset:

Shadow Capsule Setting

Este modo de sombreado es capaz de proveer sombras dónde el método estándar no podría. En concreto activando el capsule indirect shadow utilizará la información de los volumetric sample para proyectar sombra.

Shadows Capsule Diferencia

Aquí un how-to de la documentación oficial de Unreal.

Contact Shadows

En las luces hay una propiedad llamada Contact Shadow Length.

Sus valores van de $0$ a $1$. Cuando la activas ($>0$) el engine traza un rayo desde el pixel a la posición de la luz y utiliza este ray para ver si el pixel está ocluido (el ray ha colisionado con algo antes de llegar a la luz) y por tanto este pixel tiene  sombra.

Contact Shadow

El valor Contact Shadow Length es un factor del valor máximo de depth buffer, es decir, el valor 1 significa que el rayo puede tener una longitud máxima que atraviese la pantalla completa, un valor de 0.5 el rayo solo atravesaría la mitad. Un buen valor suele ser 0.1.

Es importante subrayar el hecho de que el número de samples que toma el engine a lo largo del ray es el mismo independientemente de su longitud, por lo que a mayor longitud mayor número de potencial artefactos.

Ray Traced Distance Field Shadows (RTDF)

Esta técnica de sombreado está disponible para luces movables. Esta técnica necesita tener computados los Mesh Distance Fields (también llamado Signed Distance Fields) para cada static mesh.

Calcular los SDF por cada static mesh está desactivado por defecto, hay que activarlo en Settings > Rendering > Calcualte Mesh Distance Fields (requiere reiniciar el editor).

¿Qué es un SDF? Un SDF almacena para cada punto del mesh la distancia a la superficie más cercana. Los puntos interiores almacenan distancia negativa.

Shadow SDF

Aparte de esta técnica de sombreado, los SDF se pueden usar para muchas cosas como por ejemplo: dynamic ambient occlusion, GPU particle collision, dynamic flow maps y otros.

La idea básica que hay detrás del RTDF es trazar un ray desde cada pixel sombreado a través del SDF hasta la fuente de luz. Usar la distancia al objeto más cercano (info que provee el SDF) que ocluye dicho pixel para aproximar un trace cone (con el mismo coste que un trace ray gracias al SDF). Y con ese trace cone se pueden conseguir areas sombreadas de alta calidad que provengan de luces esféricas.

Una de las propiedades principales del SDF es que, gracias a él, hacer un trace cone tiene el mismo coste que un trace ray. Más detalles en la documentación oficial.

Con el método tradicional:

Distance Field Shadows

Las sombras más alejadas no son realistas porque en la vida real deberían ser mucho más suaves. Con RTDF al usar trace cone las sombras quedarían realistas:

Ray Traced Distance Field Shadows

Para activar las RTDF a una luz dinámica usar la propiedad RayTraced DistanceField Shadows.

Esta técnica es muy eficiente para largas distancias porque solo tiene en cuenta los pixeles visibles.

La propiedad Distance Field Shadow Distance puede ser usada para discriminar cuando usar RTDF. Por ejemplo, en una directional light se puede usar Cascade Shadow Maps para cortas distancias y usar RTDF para largas distancias:

Cascaded Shadow Maps
Distance Field Shadow Distance

Se puede ver una representación gráfica de Mesh Distance Field en Show > Visualize > Mesh DistanceFields.

Quizás para algunos static mesh haya que ajustar su escala para calcular correctamente su SDF. En el editor del static mesh:

Distance Field Settings
Distance Field Resolution Scale

Para las point light y spotlight la propiedad Source Radius es usada para determinar la penumbra de las sombras.

Light Source Radius

Distance Field Indirect Shadows

Esta técnica (nueva en 4.18) es la equivalente a Capsule Shadows, que se usaba para los Skeletal Mesh Component, pero para Static Mesh Component.

En concreto, esta técnica trabaja de manera similar a Capsule Shadows ya que usa los volumetric samples generados durante el lighting build para proyectar sombra de Static Mesh Components movables bajo luces estáticas pero en este caso, en vez de usar las cápsulas del physic asset que un static mesh no posee, utiliza su Mesh Distance Field.

A diferencia de RTDF no es necesaria calcular todo el SDF de toda la escena, basta con calcular el mesh distance field para ese static mesh.

Shadow Mesh Distance Field Setting

Una vez colocado el static mesh component con dicho static mesh, para activar el distance field indirect shadow en la luz basta con activar el checkbox correspondiente (recuerda que solo tiene sentido en objetos movables):

Shadow Distance Field Indirect Shadow Checkbox

Ambient Occlusion

Bajo la pestaña World Settings en Lightmass puedes activar la generación del ambient occlusion estático.

En 4.19, existe la posibilidad de tener ambient occlusion dinámico. Se necesita como requisito tener calculado Mesh Distance Fields (Settings > Rendering > Calcualte Mesh Distance Fields).

Una vez calculados los Mesh Distance Fields, se usa una luz Skylight en modo movable. Se activaran los ajustes para el AO dinámico:

Shadow Ambient Occlusion Dinámico
Jorge Moreno Aguilera

Jorge Moreno Aguilera