Saltar al contenido

normalMap y el espacio tangente

Qué guardan esos píxeles lavanda, por qué la normal se almacena relativa a la superficie, y el error de convención que hace que los relieves se vean hundidos.

Qué hay en esos píxeles

Un mapa de normales guarda un vector unitario por téxel, con sus tres componentes en los canales rojo, verde y azul. Como los canales van de cero a uno y las componentes de menos uno a uno, hay un remapeo: el valor 0.5 corresponde a componente cero.

De ahí sale el color característico. Una zona plana tiene normal (0, 0, 1), que codificada es (0.5, 0.5, 1.0), ese lavanda azulado que reconoces al instante. Las zonas que se desvían hacia la derecha tienen más rojo, las que se desvían hacia arriba más verde, y el azul se mantiene alto en todas partes porque la componente perpendicular domina siempre.

Ese vector no está en coordenadas del mundo ni del objeto. Está en espacio tangente: un sistema local a cada punto de la superficie, formado por la tangente, que sigue la dirección de crecimiento de u; la bitangente, que sigue la de v; y la normal geométrica. Los tres se llaman colectivamente la base TBN.

Por qué espacio tangente y no espacio de objeto

La alternativa, el espacio de objeto, guarda la normal en coordenadas del modelo y produce mapas de colores vivos y variados. Es más simple de aplicar: no hace falta ninguna base local, se usa el vector directamente.

Y sin embargo, casi nadie lo usa. Tres razones:

No se puede repetir. Un mapa en espacio de objeto es específico de una orientación concreta de la superficie. Un mapa de ladrillo en espacio tangente sirve para una pared, un suelo y un techo; en espacio de objeto haría falta uno por orientación.

No sobrevive a la deformación. Si el modelo se anima, se dobla o se deforma con morph targets, sus normales de objeto dejan de corresponder. Las tangenciales se recalculan solas porque la base TBN sigue a la superficie.

No se puede compartir. Un mismo mapa tangencial vale para todos los modelos que usen esa superficie. Uno de objeto es de un modelo y solo de ese modelo.

Three.js soporta los dos con normalMapType, que acepta TangentSpaceNormalMap, el defecto, y ObjectSpaceNormalMap. El segundo tiene un nicho real: modelos escaneados de alta densidad que no se animan, donde evita los artefactos de las tangentes en desenvolvidos complicados.

normalScale y la convención del verde

normalScale es un Vector2 con valor por defecto (1, 1). Multiplica las componentes X e Y de la normal leída antes de renormalizar, lo que en la práctica controla la intensidad del relieve. Valores menores que uno lo suavizan; mayores que uno lo exageran, con el riesgo de producir normales muy inclinadas que generan brillos raros.

JS
import * as THREE from 'three';const normales = new THREE.TextureLoader().load('/texturas/piedra_normal.png');// Es un dato, NO se marca como sRGB.const material = new THREE.MeshStandardMaterial({  map: colorPiedra,  normalMap: normales,  normalScale: new THREE.Vector2(0.7, 0.7),  roughness: 0.85});

Fíjate en que es un Vector2 y no un número. Eso permite intensidades distintas en cada eje, cosa que rara vez sirve, y permite algo que sí sirve mucho: negar una de las dos componentes.

Bump frente a normal

bumpMap guarda una altura en escala de grises, no un vector. El shader calcula las derivadas de esa altura en pantalla y con ellas perturba la normal. Cuesta menos memoria, un canal en lugar de tres, y es más fácil de pintar a mano.

A cambio pierde precisión y direccionalidad: la derivada calculada en pantalla depende de la resolución y del ángulo de vista, así que el relieve puede parpadear al mover la cámara. Y no puede expresar una normal arbitraria, solo la que se deduce de una superficie de altura, lo que descarta detalles con voladizos.

Hay un dato operativo que resuelve la mitad de los problemas con estos dos mapas: si hay normalMap, el bumpMap se ignora por completo. Lo dice la documentación de todos los materiales que tienen los dos. Si has asignado los dos y estás tocando bumpScale sin ver ningún cambio, ese es el motivo.

Lo que un mapa de normales no puede hacer

Es importante saber dónde está el techo, porque explica cuándo hay que pasar a otra técnica.

No cambia la silueta. El contorno del objeto sigue siendo el de la geometría. Un ladrillo con relieve de dos centímetros visto de canto sigue teniendo el borde perfectamente recto, y eso delata el truco inmediatamente.

No produce paralaje. El relieve no se desplaza respecto al fondo al mover la cámara, porque no hay profundidad real. A distancia media no se nota; en primer plano y con movimiento, sí.

No se auto-ocluye ni se auto-sombrea. Un saliente no proyecta sombra sobre el hueco de al lado. Por eso los mapas de normales se acompañan de un mapa de oclusión ambiental horneado, que aporta justo esa información que falta.

Las tres limitaciones tienen respuesta y ninguna es gratis: mapeo de paralaje para la segunda, con un bucle de búsqueda en el fragment shader; y displacementMap con geometría subdividida para las tres, que es geometría real y cuesta como tal.