Anisotropía: el suelo que deja de verse borroso
Por qué un mipmap emborrona las superficies vistas en ángulo, cómo lo arregla el filtrado anisótropo, y cuál es el límite que impone el dispositivo.
La huella no es un cuadrado
Cuando una superficie está de frente a la cámara, un píxel de pantalla se proyecta sobre la textura como un cuadrado aproximado. El mipmap está diseñado exactamente para eso: cada nivel promedia cuadrados de dos por dos.
Cuando la superficie está inclinada, la proyección se deforma. En un suelo visto casi de canto, un píxel puede abarcar dos téxeles de ancho y cincuenta de largo. El promedio correcto sería el de esa franja de dos por cincuenta, pero no hay ningún nivel de mipmap que contenga ese promedio: los niveles solo tienen cuadrados de dos por dos, de cuatro por cuatro, de ocho por ocho.
La GPU tiene que elegir un nivel y solo puede equivocarse de dos maneras. Si elige el nivel que corresponde a la dirección corta, promedia demasiado poco a lo largo y vuelve el aliasing. Si elige el que corresponde a la dirección larga, promedia demasiado a lo ancho y la imagen sale borrosa. El hardware elige lo segundo, porque el desenfoque molesta menos que el hervido.
Ese es el aspecto que reconoces de cualquier suelo en un motor sin anisotropía: nítido justo bajo los pies, papilla dos metros más allá.
Qué hace el filtrado anisótropo
En lugar de una lectura en un nivel demasiado bajo, el filtrado anisótropo toma varias muestras a lo largo del eje mayor de la huella, en un nivel de mipmap elegido según el eje menor. Ocho muestras a lo largo de la franja aproximan el promedio real mucho mejor que una sola muestra de un cuadrado gigante.
El número máximo de muestras es lo que configuras en texture.anisotropy. Vale 1 por defecto, que significa filtrado isótropo normal. Subirlo a 4, 8 o 16 permite al hardware tomar hasta esa cantidad de muestras cuando la huella lo justifique.
La palabra clave es cuando lo justifique. El hardware calcula la relación entre el eje mayor y el menor de la huella y toma tantas muestras como esa relación pida, hasta el máximo que hayas puesto. En una pared vista de frente la relación es uno y toma una muestra: la anisotropía no cuesta nada donde no hace falta. Por eso es de las opciones con mejor relación entre calidad y coste que existen, muy por delante de subir la resolución de la textura.
Tampoco cuesta memoria: no hay datos adicionales, solo más lecturas de los mipmaps que ya existen. Y necesita esos mipmaps: con minFilter puesto en NearestFilter o LinearFilter, que son los que no los usan, la anisotropía no tiene sobre qué trabajar.
El límite del dispositivo
El máximo que admite la GPU se consulta a través del renderer:
import * as THREE from 'three';const renderer = new THREE.WebGLRenderer({ antialias: true });const maximo = renderer.capabilities.getMaxAnisotropy();console.log('anisotropia maxima soportada:', maximo);const suelo = new THREE.TextureLoader().load('/texturas/parquet.jpg');suelo.colorSpace = THREE.SRGBColorSpace;suelo.wrapS = suelo.wrapT = THREE.RepeatWrapping;suelo.repeat.set(16, 16);suelo.anisotropy = Math.min(8, Math.max(1, maximo));Ese Math.max(1, ...) no es paranoia. La implementación real es esta:
function getMaxAnisotropy() { if ( maxAnisotropy !== undefined ) return maxAnisotropy; if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) { const extension = extensions.get( 'EXT_texture_filter_anisotropic' ); maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); } else { maxAnisotropy = 0; } return maxAnisotropy;}Si la extensión no está disponible, devuelve cero, no uno. Un Math.min(8, maximo) sin más te deja la anisotropía en cero, que no es un valor válido. La extensión está prácticamente en todas partes, pero “prácticamente” no es “siempre”, y el fallo solo aparecería en el dispositivo del usuario.
En cuanto al valor razonable: la mayoría del hardware moderno reporta 16. Subir de 8 a 16 es una diferencia casi imperceptible que dobla las lecturas en las zonas más inclinadas, así que 8 es el valor sensato por defecto y 4 una elección conservadora en móvil.