wandres.dev
PBR I · La teoría del renderizado físico

Metallic-roughness frente a specular-glossiness

Las dos parametrizaciones del mismo modelo físico, qué gana y qué pierde cada una, y cómo Three.js te devuelve el control del F0 sin salirte del flujo estándar.

⏱ 18 min

La BRDF de microfacetas necesita dos cosas para el especular: la reflectancia en incidencia normal, F0, y la rugosidad. Cómo le pides al artista esos dos valores es una decisión de interfaz, y en la industria se han usado dos: una que se los pide directamente y otra que se los deduce de una clasificación. La segunda ganó, y entender por qué explica la mitad de las decisiones de glTF.

🎯 Al terminar esta lección sabrás
  • Describir los canales que define cada una de las dos parametrizaciones.
  • Calcular el F0 que resulta de un índice de refracción dado.
  • Identificar el artefacto de costura que produce metallic-roughness y por qué se aceptó igualmente.
  • Usar ior, specularIntensity y specularColor para recuperar control fino sin abandonar el flujo estándar.

Las dos parametrizaciones

Specular-glossiness te pide tres cosas: un color difuso, un color especular que es directamente el F0 en RGB, y un valor de brillo. Es la parametrización más expresiva porque puedes poner cualquier F0, incluido uno tintado en un material no metálico, cosa que en la realidad no ocurre pero en producción a veces conviene.

Metallic-roughness te pide otras tres: un color base, un valor de metalicidad y uno de rugosidad. El F0 no se escribe, se deduce: si el material es metálico, F0 es el color base; si no lo es, F0 es un valor fijo pequeño derivado del índice de refracción.

En Three.js, ese “valor fijo pequeño” es exactamente esto:

material.specularColor = min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor;
material.specularColorBlended = mix( material.specularColor, diffuseColor.rgb, metalnessFactor );

Esa primera fórmula es la ecuación de Fresnel en incidencia normal para la frontera entre aire e índice n. Con el ior por defecto de 1.5, sale (0.5 / 2.5) al cuadrado, es decir 0.04. El cuatro por ciento que aparece en todas partes no es una constante mágica: es el vidrio.

Con otros valores:

material ior F0 calculado
agua 1.33 0.020
plástico común 1.45 0.034
vidrio 1.50 0.040
rubí 1.76 0.076
diamante 2.42 0.172

Todos son valores bajos y todos son grises. Esa es la afirmación física fuerte del flujo metallic-roughness: todos los dieléctricos reflejan poco y sin tinte. Un plástico rojo refleja un brillo blanco, no rojo. Solo los metales tintan su reflexión.

Por qué ganó metallic-roughness

Tres razones, en orden de peso.

La primera es que restringe el espacio de materiales imposibles. Con specular-glossiness es trivial escribir un material con difuso blanco y especular blanco, que devuelve más energía de la que recibe, o un dieléctrico con F0 de 0.9, que no corresponde a ninguna sustancia. Con metallic-roughness eso no se puede expresar: si es metal, no hay difuso; si no lo es, el F0 está acotado. Un formato de intercambio prefiere un espacio pequeño de materiales correctos a uno grande donde la mitad son basura.

La segunda es el ahorro de textura. Specular-glossiness necesita seis canales, tres para el difuso y tres para el especular, más el brillo. Metallic-roughness necesita tres para el color base y dos escalares, que caben en dos canales de una misma textura. Menos memoria, menos peticiones, menos VRAM.

La tercera es que el artista razona mejor con una clasificación que con una constante. “Esto es metal, esto es plástico” es una pregunta que cualquiera responde. “El F0 de este plástico es 0.034 o 0.042” no la responde nadie sin una tabla.

glTF adoptó metallic-roughness como núcleo del formato. La extensión de specular-glossiness existió y hoy está archivada; el ecosistema entero se ha movido al núcleo más las extensiones que amplían el modelo.

El artefacto que sí tiene

Metallic-roughness tiene un problema real y bien documentado: las costuras blancas en los bordes entre metal y no metal. Si en el mapa de metalicidad hay una transición de uno a cero en un píxel, el filtrado bilineal produce texeles intermedios con metalness a 0.5. Y en ese texel el material tiene un F0 que es la mezcla del color base con el 0.04, y a la vez conserva la mitad del difuso. El resultado es una línea que refleja de más y se ve como un halo claro alrededor de cada zona metálica, sobre todo con mipmaps y con la superficie vista de lejos.

Specular-glossiness no lo sufre porque interpola entre dos valores de F0 que siempre son válidos. Es la única ventaja técnica objetiva que tenía, y la industria decidió que un halo en una transición pesa menos que un catálogo lleno de materiales imposibles.

La mitigación práctica es sencilla y conviene conocerla: en el mapa de metalicidad, evita los degradados. Un mapa de metalicidad quiere ser binario, con transiciones de un texel a resolución suficiente, y si necesitas suavizar el borde, suavízalo en el mapa de rugosidad y en el color base, no en el de metalicidad.

Three.js te devuelve el F0 con tres propiedades, y no rompe glTF

La restricción de metallic-roughness aprieta en casos reales: pinturas con capa especular tintada, materiales revestidos, telas tratadas. Por eso el estándar creció con la extensión KHR_materials_specular, y MeshPhysicalMaterial la implementa con specularIntensity y specularColor, más ior para mover el valor base.

Míralo en la fórmula de arriba: el F0 final es el Fresnel del ior multiplicado por specularColor y por specularIntensity. Con eso puedes escribir un dieléctrico con reflexión tintada o con el especular apagado del todo, y hay un detalle que casi nadie usa: poner specularIntensity: 0 convierte el material en un lambertiano puro, sin ningún especular, cosa que no puedes conseguir subiendo roughness a uno, porque un material muy rugoso sigue teniendo un especular ancho y visible en incidencia rasante.

Hay una trampa: si el material no entra por la rama IOR del shader, el F0 se queda en el 0.04 fijo. Y specularIntensity y specularColor solo existen en MeshPhysicalMaterial, no en MeshStandardMaterial. Además, specularIntensity afecta solo a los no metales; con metalness a uno, specularF90 se fuerza a uno y el F0 es el color base, así que estas tres propiedades no hacen absolutamente nada sobre un metal.

Convertir de una a otra

Si recibes assets antiguos con specular-glossiness, la conversión no es exacta pero se aproxima bien.

De specular-glossiness a metallic-roughness: roughness = 1 - glossiness, y la metalicidad se estima comparando el especular con el umbral dieléctrico. Si el F0 del asset es cercano a 0.04 y acromático, es un dieléctrico y el color base es su difuso. Si es alto y tintado, es un metal y el color base es su especular. Los casos intermedios no tienen respuesta correcta y hay que decidirlos a mano.

De metallic-roughness a specular-glossiness es trivial y sin pérdida, porque el destino es más expresivo: glossiness = 1 - roughness, el difuso es el color base por uno menos la metalicidad, y el especular es la interpolación entre 0.04 y el color base según la metalicidad. Es exactamente lo que hace el shader.

La herramienta habitual para estas conversiones en el ecosistema web es gltf-transform, que también resuelve el otro lado del problema: reempaquetar los canales de rugosidad y metalicidad en la misma textura, que es el tema de los mapas de material.