wrapS, wrapT, repeat, offset y rotation: la matriz UV
Los tres modos de repetición, las cuatro propiedades que componen la transformación de textura, y el orden en que se aplican.
Los tres modos de repetición
wrapS controla qué ocurre cuando la coordenada u sale del rango de cero a uno, y wrapT hace lo mismo con v. Los tres valores posibles:
ClampToEdgeWrapping recorta: cualquier valor por debajo de cero muestrea el primer téxel y cualquiera por encima de uno muestrea el último. El resultado es que los píxeles del borde se estiran hacia el infinito. Es el valor por defecto de las dos propiedades.
RepeatWrapping toma la parte fraccionaria: la textura se repite indefinidamente en mosaico.
MirroredRepeatWrapping repite alternando el sentido en cada copia, de forma que los bordes de dos copias contiguas coinciden. Es lo que quieres cuando la textura no es continua en sus bordes, porque elimina la línea de discontinuidad a cambio de introducir un patrón especular reconocible.
Que el defecto sea ClampToEdgeWrapping es la causa del error más frecuente de esta lección: pones texture.repeat.set(4, 4) esperando cuatro baldosas y obtienes una sola baldosa en la esquina y el resto de la superficie pintada con el color de los bordes estirados. La documentación lo advierte: si repeat es mayor que uno en un eje, el modo correspondiente tiene que ser RepeatWrapping o MirroredRepeatWrapping.
import * as THREE from 'three';const suelo = new THREE.TextureLoader().load('/texturas/parquet.jpg');suelo.colorSpace = THREE.SRGBColorSpace;suelo.wrapS = THREE.RepeatWrapping; // obligatorio antes de repetirsuelo.wrapT = THREE.RepeatWrapping;suelo.repeat.set(8, 8);La transformación completa
Cuatro propiedades componen la matriz UV:
offset, unVector2, desplaza. Por defecto(0,0).repeat, unVector2, escala. Por defecto(1,1).rotation, un número en radianes, gira en sentido antihorario. Por defecto0.center, unVector2, es el punto alrededor del cual se gira y se escala. Por defecto(0,0), la esquina inferior izquierda.
El método que las combina es updateMatrix, y su implementación deja claro el orden:
updateMatrix() { this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y );}La composición es: trasladar al centro, escalar por repeat, rotar, volver del centro, y por último aplicar offset. La consecuencia práctica más importante es que center vale (0,0) por defecto, así que una rotación sin tocar center gira alrededor de la esquina, no del medio. Si querías girar la textura sobre sí misma, la línea que falta es siempre la misma:
textura.center.set(0.5, 0.5);textura.rotation = Math.PI / 4;La segunda consecuencia es que offset se aplica después de la escala, así que un offset de 0.5 desplaza medio ancho de la textura original, no medio ancho de una repetición. Con repeat a 4, ese mismo offset de 0.5 mueve la textura dos repeticiones completas.
Esa combinación de repeat y offset es exactamente lo que necesita un atlas o una hoja de sprites. Para una rejilla de cuatro por cuatro celdas:
function seleccionarCelda(textura, columna, fila, columnas = 4, filas = 4) { textura.repeat.set(1 / columnas, 1 / filas); // El origen de v esta abajo, asi que la fila 0 es la de arriba. textura.offset.set(columna / columnas, 1 - (fila + 1) / filas);}Ese 1 - (fila + 1) / filas es la consecuencia directa del origen abajo a la izquierda, y es donde se equivoca todo el mundo la primera vez.
Cuándo se recalcula la matriz
matrixAutoUpdate vale true por defecto, y con él el renderer llama a updateMatrix antes de cada dibujado, así que cualquier cambio en las cuatro propiedades tiene efecto inmediato sin hacer nada más. Esa comodidad cuesta una construcción de matriz por textura y por objeto en cada fotograma, que es despreciable salvo en escenas con cientos de materiales.
Si necesitas escribir la matriz directamente, por ejemplo para una transformación que no se puede expresar con esas cuatro propiedades, pon matrixAutoUpdate a false y rellena texture.matrix tú. A partir de ese momento las cuatro propiedades dejan de tener efecto, y es un buen sitio donde perder una tarde si te olvidas.
El artefacto del borde
Un último detalle que explica un problema muy común. Con RepeatWrapping y filtrado lineal, el téxel del borde derecho se mezcla con el del borde izquierdo, que es lo correcto porque la textura se repite. Con ClampToEdgeWrapping, no.
Eso significa que una textura pensada para repetirse tiene que ser continua en sus cuatro bordes, y si no lo es, verás una rejilla de líneas donde se juntan las copias. Y al revés: una textura pensada para no repetirse, como una etiqueta o un logotipo, tiene que usar ClampToEdgeWrapping, porque con RepeatWrapping el filtrado del borde izquierdo se contamina con el derecho y aparece un halo de un píxel en los cuatro lados. Ese halo es casi invisible en el nivel base y muy visible en los mipmaps pequeños, así que se manifiesta solo cuando el objeto se aleja, que es cuando menos se espera.