Cuando hablamos de iluminación advertimos lo computacionalmente costoso que es.

Sin embargo, si en nuestro proyecto no necesitamos mover/rotar la cámara podemos usar una técnica que nos ofrece iluminación, reflejos y refracciones casi gratis y con una única textura.

Esta técnica no es otra que Matcap (MATerial CAPture). Está basado en Spherical Mapping y fue popularizado por ZBrush y Modo.

Sphere Mapping

La textura que "captura" el material es una proyección ortográfica sobre una esfera desde el punto de vista de la cámara.

Piensa en una especie de bakeo con la esfera actuando como un skybox:

Es como si la esfera que ves en la imagen fuera el skybox y el personaje fuera 100% reflectivo y 100% liso.

Y de hecho así es como intuitivamente debemos proceder, tomando la textura (la esfera proyectada ortogonalmente en una textura 2d) como si fuera un sky-box (en este caso un sky-sphere) y el personaje como si fuera una superficie 100% reflectiva.

La proyección ortográfica tiene una pinta tal que así:

Así que dada la textura 2d de la izquierda, ¿como obtenemos la UV correspondiente a un punto de la superficie de la geometría?

Vamos a simplificar primero. Imagina que no tenemos la textura 2d, en vez de eso, es una textura 3d codificando cada punto $(x,y,z)$ de la esfera.

En este caso, dado un punto de la superficie de la geometría $P$, la dirección de la cámara $C$ y la normal en dicho punto $N$, sería tan fácil como:

vec3 R = reflect(C, N); // C - 2.0 * dot (N, C) * N
vec3 colorFinal = texture3d(SphereTex, R);

Que es la forma habitual de calcular las reflexiones con un skybox.

Usando una textura 3D
Material editor usando una Skybox

Sin embargo, nuestra textura es en dos dimensiones, no en tres, por lo que, ¿cómo calculamos las UV?

vec3 R = reflect(C, N); // C - 2.0 * dot (N, C) * N
vec2 uv = /* ¿cómo obtener uv en función de R */
vec3 colorFinal = texture2d(SphereTex2d, uv);

Cálculo de las UV en función del vector reflexión

Nuestra textura tiene la siguiente pinta, obtenida de proyección ortográfica:

Matcap Texture de ejemplo. Fuente aquí.

Dónde, como habitualmente en HLSL y UE4, la esquina superior izquierda es $(0,0)$ y la esquina inferior derecha es $(1,1)$.

Nota: en OpenGL / GLSL el $(0,0)$ es esquina inferior izquierda y $(1,1)$ esquina superior derecha.

Nuestra esfera 3d, que actúa de sky y hemos proyectado ortográficamente en la textura, es de radio 1 y está colocada en $(0,0,0)$.

Con estos datos ya tenemos nuestra primera relación:

Sea

$$P = (x, y, z)$$

cualquier punto en la superficie de nuestra esfera unitaria y centrada en el origen:

$$ x ^ 2 + y ^ 2 + z ^ 2 = 1 $$

$$ z = \sqrt{1 - x ^ 2 - y ^ 2} $$

Por otra parte, de la textura sabemos que la coordenada UV central ${uv} = (0.5, 0.5)$ corresponde al punto de la esfera $P = (0, 0, \pm 1)$.

Hemos calculado $z$ el resultado de la expresión anterior. Al ser una ráiz cuadrada son dos puntos, el frontal y anterior.

Observando la textura también sabemos que ${uv} = (1.0, 0.5)$ corresponde al punto de la esfera $P = (1, 0, z)$. Y que ${uv} = (0.0, 0.5)$  corresponde a $P = (-1, 0, z)$.  ¡Compruébalo tu mismo mirando la textura!

De hecho, podemos establecer las siguientes relaciones, que no es más que un "remapeado" de UV a XY:

$$ x = 2  u - 1 $$

$$ y = 2 v - 1 $$

$$ z = \sqrt{1 - x ^ 2 - y ^ 2} $$

Podemos calcular la normal de la esfera en un punto $P = (x, y, z)$. Dado que la esfera está situada en el origen, es bien sencillo:

$$ N = P - O = (x, y, z) - (0, 0, 0) = (x, y ,z) = (2  u - 1, 2  v - 1, \sqrt{1 - x ^ 2 - y ^ 2}) $$

Ya tenemos la normal $N$. Para calcular $R$, el vector reflejado, necesitamos saber la dirección de la cámara.

Podemos hacer los cálculos en eye-space para simplicar ya que, en eye-space, por definición, la cámara mira en dirección $(0, 0, -1)$.

$$ C = (0, 0, -1) $$

$$ R = C - 2 (C \cdot N) N $$

Primero calculamos el producto escalar:

$$ C \cdot N = (0, 0, -1) \cdot N = - \sqrt{1 - x ^2 - y ^ 2} = -z $$

Usando este resultado:

$$ R = (0, 0, -1) - 2 (-z) N = (0, 0, -1) + 2z N $$

Si despejamos $N$:

$$ N = \frac{1}{2z} (R - (0, 0, -1)) = \frac{1}{2z} (R_x, R_y, R_z + 1) $$

Obviamente $N$ debe ser un vector normalizado así que podemos obviar el factor que multiplica y normalizarlo:

$$ N = \frac{1}{\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z + 1) ^ 2}} (R_x, R_y, R_z + 1) $$

Y como ya teníamos $N$ en función de las $UV$:

$$ N = (N_x, N_y, N_z), N_x = 2u - 1, N_y = 2v - 1 $$

¡Hemos conseguido obtener las $UV$ en función del vector $R$!

$$ 2u - 1 = \frac{R_x}{\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z + 1) ^ 2}} $$

$$ u = \frac{R_x}{2\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z + 1) ^ 2}} + \frac{1}{2} $$

Lo mismo con $v$:

$$ v = \frac{R_y}{2\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z + 1) ^ 2}} + \frac{1}{2} $$

Implementación en UE4

En UE4 la implementación es directa.

Tomar cada una de las fórmulas,

$$ u = \frac{R_x}{2\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z + 1) ^ 2}} + \frac{1}{2} $$

$$ v = \frac{R_y}{2\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z + 1) ^ 2}} + \frac{1}{2} $$

y desarrollarla:

Un par de apuntes:

  • PixelNormalWS devuelve la normal en WorldSpace, necesitamos el nodo Transform para convertir el vector en EyeSpace.
  • El material es Unlit. Como dijimos, Matcap es una especie de bakeo del color, reflexión, refracción y demás efectos lumínicos.
  • Si rotas o mueves la cámara, verás como, obviamente, los reflejos se mueven. Por eso con Matcap la cámara debe estar quieta.

Echa un vistazo a la imagen anterior y a la textura. ¿Notas algo raro?

Parece como si la textura estuviera invertida.

Esto es así porque hemos hecho los cálculos tal y como aparecen en la mayoria de recursos (libros y documentación), esto es, la cámara, por convenio, mira dirección $(0, 0, -1)$ en eye-space, sin embargo, en UE4 es $(0, 0, 1)$.

Por lo que para UE4 la fórmula cambia en un signo.

$$ u = \frac{R_x}{2\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z - 1) ^ 2}} + \frac{1}{2} $$

$$ v = \frac{R_y}{2\sqrt{R_x ^ 2 + R_y ^ 2 + (R_z - 1) ^ 2}} + \frac{1}{2} $$

Y, por supuesto, en el editor de materiales ahora el nodo dónde indicamos la dirección de la cámara es $(0, 0, 1)$:

Conclusión

Si en tu proyecto la cámara no rota ni se mueve, Matcap es una técnica muy interesante para según que geometrías. Hablamos de un material unlit con reflexiones, refracciones y luces en una sola textura.