Siempre me han llamado la atención los juegos donde se experimenta con estilos artísticos que se salen de lo normal. Recuerdo maravillarme con el aspecto visual de Borderlands, o el Ultimate Spider-Man de la PS2. En tiempos recientes, este efecto ha ido adquiriendo cada vez mayores grados de complejidad, mezclándose con sistemas de renderizado más avanzados. Ejemplos de ello son The Legend of Zelda: Breath of the Wild, o los últimos Borderlands. Por ello, quise hacer una versión de este tipo de cel-shading moderno en un motor como Unreal Engine, cuyo renderizado se basa principalmente en el modelo PBR.

A la izquierda, la escena sin activar el shader. A la derecha, la escena con el shader activado.
Obtención de la información de color e iluminación
Para poder conseguir los valores de iluminación, se obtiene el mapa RGB resultante de la relación entre el render y el búfer BaseColor (que contiene la información de color de los objetos sin el aporte de la iluminación), dada de dividir el primero por el segundo. Este mapa contiene la información de iluminación de la escena.
Sin embargo, trabajar con el RGB es complicado. Por esta razón, se realiza una conversión del espacio RGB al espacio HSV, de donde podemos sacar el valor Hue (color), Saturation (la cantidad de color) y el Value, que nos da el valor de la iluminación (también llamado brillo).

Modelo de iluminación
Los valores de Luminosidad se presentan en una curva exponencial. Es decir, la relación de iluminación entre un píxel con un valor de 100 y otro de 200 es la misma que entre uno de 200 y otro de 400, aunque la diferencia escalar entre los píxeles no sea la misma. Para convertirla en lineal, tenemos que aplicarle una función logarítmica de base 2, lo que nos da un gradiente con el que podemos trabajar más fácilmente. Tras trabajar con la luminosidad codificada linealmente, volvemos a transformarla en exponencial.
Como se puede observar en la imagen de abajo, la gestión de la iluminación en las superficies metálicas se trata por separado con tal de dotar de mayor control al shader, ya que las superficies metálicas suelen tener más variabilidad en el gradiente de iluminación, lo que introduce un mayor número de bandas que si usase la misma configuración que los materiales no-metálicos.

MF_GradientBanding
Para discretizar un gradiente en varias bandas del mismo valor, he creado una Material Function que toma como input:
- El gradiente, un campo escalar continuo que queremos discretizar.
- El Banding Amount, un escalar que controla la cantidad de bandas a generar: si vale 1, saca una banda por cada incremento de unidad del gradiente, si vale 0.5 saca una banda por cada dos incrementos, si vale 2 saca 2 bandas por cada incremento, etc.
- El Gradient Smoothness, otro escalar que permite controlar la compresión de la transición entre las diferentes bandas. Cuanto más cerca de 0 se encuentra, más abrupta es la transición, mientras que si se acerca a 1 la transición es más suave, hasta el punto que deja de notarse. Esta transición sigue una función smoothstep, pudiendo elegir entre la que viene por defecto u otra personalizada, MF_DynamicSCurve.

Gestión del Hue
El shader también permite aplicar banding al Hue. Debido a que este al final es un gradiente escalar más cuando usamos HSV, puede aplicarse la misma función de discretización, lo que nos permite obtener un resultado estilísticamente más interesante. Al aplicar un Banding Amount de 12, por ejemplo, dividiremos todo el espacio Hue en 12 colores únicos, pudiendo elegir como de gradual será la transición entre estos.

Borde metálico
Para dar un poco más de personalidad a los objetos metálicos, aplicamos una función que añade luminosidad a las partes de la superficie metálica que apuntan hacia arriba, y disminuimos las que apuntan hacia abajo. Todo esto recortado por una máscara fresnel.
Nótese el uso del arcoseno para obtener un gradiente fresnel más lineal a partir del dot product entre el vector de la cámara y las normales del objeto.
