Saltar al contenido

roughnessMap, metalnessMap y el empaquetado ORM

Los dos mapas que hacen que un material deje de ser uniforme, los canales exactos que lee cada uno, y por qué en glTF viajan en la misma imagen.

Un canal cada uno, y no es casualidad

Las convenciones están fijadas en la documentación de MeshStandardMaterial y son estas:

mapa canal
aoMap rojo
roughnessMap verde
metalnessMap azul

Los tres son valores de un solo componente y los tres caben en canales distintos de una misma imagen RGB. Esa coincidencia no es fortuita: glTF define la textura de material metálico-rugoso con la rugosidad en el verde y la metalicidad en el azul precisamente para dejar el rojo libre, y define la textura de oclusión leyendo el rojo, de forma que las dos pueden ser el mismo fichero. El comentario del propio shader de Three.js lo dice sin rodeos, el canal rojo del mapa de oclusión es “compatible con una textura combinada OcclusionRoughnessMetallic”.

De ahí el nombre con el que se conoce en producción: ORM, por el orden de los canales.

JS
import * as THREE from 'three';const orm = new THREE.TextureLoader().load('/texturas/caja_orm.png');// Es un mapa de datos: NADA de SRGBColorSpace.const material = new THREE.MeshStandardMaterial({  map: colorCaja,  aoMap: orm,  roughnessMap: orm,  metalnessMap: orm,  roughness: 1,     // imprescindible: los mapas multiplican  metalness: 1});

Sí, es la misma instancia de textura asignada tres veces. No se sube tres veces a la GPU ni ocupa memoria tres veces: Three.js identifica la textura por su objeto, así que es una sola subida, un solo sampler y una sola transformación UV. GLTFLoader hace exactamente esto al cargar un modelo con ORM.

El error que deja los mapas sin efecto

Fíjate en las dos últimas líneas del ejemplo. La documentación lo dice para los dos mapas con la misma frase: si el mapa está presente, ambos valores se multiplican.

Ahora recuerda los valores por defecto de MeshStandardMaterial:

JS
this.roughness = 1.0;this.metalness = 0.0;

roughness a uno es neutro para la multiplicación, así que un roughnessMap funciona sin tocar nada. Pero metalness a cero anula por completo el mapa: cualquier valor de la textura multiplicado por cero es cero, y el material entero se comporta como un dieléctrico. Asignas el mapa, no cambia nada, y no hay ningún aviso.

Es, con toda probabilidad, el error más frecuente al montar un material PBR a mano. La regla es corta: al asignar un metalnessMap, pon metalness: 1. Lo mismo aplica si has bajado roughness para un experimento anterior y luego asignas un roughnessMap.

El escalar sigue siendo útil como multiplicador global: roughness: 0.8 con un mapa te da una versión un veinte por ciento más pulida de todo el mapa, lo cual es una forma cómoda de ajustar sin repintar la textura. Pero es un ajuste, no el valor por defecto.

Qué guardar en cada mapa

Un mapa de rugosidad bien hecho cuenta la historia del objeto. Las zonas que se tocan mucho están más pulidas; las que acumulan polvo, más rugosas; los arañazos son líneas de rugosidad distinta a la base; el óxido es rugosidad alta con metalicidad baja. Un objeto con roughness uniforme parece salido de fábrica y esterilizado, y esa es exactamente la sensación de una escena que no acaba de funcionar.

El rango útil está entre 0.1 y 0.9 aproximadamente. Valores por debajo se recortan al mínimo interno de 0.0525; valores por encima de 0.9 producen superficies indistinguibles entre sí.

Un mapa de metalicidad, en cambio, quiere ser binario. Ya conoces el motivo: los valores intermedios describen materiales que no existen. La única excusa para un gris en un mapa de metalicidad es que ese téxel cubra físicamente una mezcla de metal desnudo y de recubrimiento. Un mapa de metalicidad con degradados suaves está mal hecho, y produce las costuras claras características del flujo metallic-roughness.

Nunca sRGB

Cierro con la regla que va a repetirse en el nivel siguiente y que aquí se aplica a los tres canales del ORM: son datos, no color. Un valor de rugosidad de 0.5 significa rugosidad media, no un gris medio percibido. Marcar la textura como SRGBColorSpace haría que Three.js aplicara la decodificación de sRGB y convirtiera ese 0.5 en 0.21, con lo que todo tu material saldría mucho más pulido de lo que pintaste.

El defecto de Texture.colorSpace es NoColorSpace, que es lo correcto para estos mapas, así que la regla operativa es simplemente no tocarlo. Los únicos mapas a los que hay que ponerles SRGBColorSpace explícitamente son los que representan color visible.