specularIntensity, specularColor y el coste de cada capa
Las tres propiedades que devuelven el control del F0 en un flujo metallic-roughness, y la tabla que decide cuándo MeshPhysicalMaterial compensa frente a MeshStandardMaterial.
Las capas vistosas de MeshPhysicalMaterial son cinco, pero las que más se usan en producción son las tres que no se ven: ior, specularIntensity y specularColor. No añaden un lóbulo ni un efecto, ajustan el F0 que el flujo metallic-roughness dejaba fijo, y con eso resuelven una familia entera de materiales que antes había que falsear. Con ellas cerrado, queda decidir cuándo todo esto compensa.
- Ajustar el
F0de un dieléctrico conior,specularIntensityyspecularColor. - Conocer los canales que usan
specularIntensityMapyspecularColorMap. - Explicar la relación entre
reflectivityeiory su límite. - Decidir con criterio entre
MeshStandardMaterialyMeshPhysicalMaterial.
Las tres palancas del F0
Recordando la fórmula del shader, el F0 de un no metal en r184 sale de aquí:
material.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );
material.specularColor = min( pow2( ( material.ior - 1.0 ) / ( material.ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor;
Tres entradas, tres efectos distintos.
ior mueve la base. Es el índice de refracción, va de 1.0 a 2.333 y su valor por defecto es 1.5, que da el 0.04 habitual. Subirlo hace la superficie más reflectante en todos los ángulos, y además es el índice que usa la refracción cuando hay transmission. Es la palanca correcta cuando sabes qué sustancia estás describiendo.
specularIntensity escala el resultado, de cero a uno. Su valor por defecto es 1. Bajarlo apaga el especular sin tocar nada más, y fíjate en que también entra en specularF90, así que reduce la reflexión en incidencia rasante, cosa que el ior no hace. Es la palanca correcta para superficies que han perdido su brillo: madera sin tratar, tiza, tela muy mate, polvo.
specularColor tinta. Su valor por defecto es blanco. La física dice que un dieléctrico refleja sin tinte, así que usarlo es salirse del modelo a propósito, y hay dos motivos legítimos para hacerlo: recubrimientos ópticos reales, que sí tintan, y ajuste artístico controlado cuando el resultado físicamente correcto no encaja con la dirección de arte.
import * as THREE from 'three';
// Madera sin tratar: apenas refleja.
const madera = new THREE.MeshPhysicalMaterial({
color: 0x8a6a45,
roughness: 0.85,
metalness: 0,
specularIntensity: 0.25
});
// Diamante: indice altisimo, reflexion fuerte, volumen transmisivo.
const diamante = new THREE.MeshPhysicalMaterial({
transmission: 1,
thickness: 0.5,
roughness: 0,
metalness: 0,
ior: 2.33,
dispersion: 0.6
});
// Recubrimiento optico sobre una lente: reflexion tintada.
const lente = new THREE.MeshPhysicalMaterial({
color: 0x0a0a0a,
roughness: 0.05,
metalness: 0,
ior: 1.52,
specularColor: new THREE.Color(0x66ccaa)
});
Los dos mapas asociados usan canales distintos y confundirlos es un error silencioso: specularIntensityMap lee el canal alfa, y specularColorMap lee los canales RGB. Como el primero necesita alfa, una textura JPEG no sirve para él.
Un aviso que ahorra tiempo: las tres propiedades no hacen absolutamente nada sobre un metal. Con metalness a uno, specularF90 se fuerza a uno y el F0 pasa a ser el color base, así que la fórmula de arriba deja de intervenir. Si estás moviendo specularIntensity en un material metálico y no cambia nada, es porque no tiene que cambiar.
MeshPhysicalMaterial expone reflectivity, y no es un campo: es un par de accesores definidos sobre ior.
get reflectivity() { return clamp( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ); }
set reflectivity( r ) { this.ior = ( 1 + 0.4 * r ) / ( 1 - 0.4 * r ); }De ahí salen tres cosas que sorprenden. Primera: escribir reflectivity modifica ior, así que si asignas los dos en el mismo objeto de parámetros, el último gana y el otro se pierde en silencio. Segunda: el valor por defecto de reflectivity es 0.5 porque el de ior es 1.5, y esa correspondencia es exacta por construcción. Tercera: la ida y vuelta no es reversible por encima de ior igual a 2.333, porque el getter recorta a uno; si pones ior: 3 y después lees y reescribes reflectivity, el material se queda en 2.333 y tu diamante deja de ser un diamante.
Y ojo con la homonimia: MeshBasicMaterial, MeshLambertMaterial y MeshPhongMaterial tienen una propiedad reflectivity de verdad, que significa otra cosa completamente distinta, cuánto pesa el mapa de entorno al combinarlo. El mismo nombre, dos semánticas, y ningún aviso si te equivocas de material.
La tabla del coste
Con todo el material recorrido, este es el resumen que conviene tener presente. El coste es relativo a un MeshStandardMaterial con dos luces y entorno, medido en tiempo de fragment shader:
| capa | coste añadido | qué lo domina |
|---|---|---|
ior, specularIntensity, specularColor |
casi cero | dos multiplicaciones |
clearcoat |
+30 por ciento | un lóbulo GGX y un muestreo de entorno más |
anisotropy |
+25 por ciento | distribución y visibilidad anisótropas, más el TBN |
iridescence |
+35 por ciento | Fresnel de película fina en lugar de Schlick |
sheen |
+40 por ciento | lóbulo por luz más compensación de energía |
transmission |
la escena se dibuja dos veces | la pasada de transmisión |
La última fila no es comparable con las demás y por eso está expresada de otra forma. Las cinco primeras son coste por píxel del material; la sexta es coste global de la escena, independiente de cuántos píxeles ocupe el objeto transmisivo.
Cuándo Physical y cuándo Standard
La pregunta correcta no es cuál es mejor, es si el material que quieres tiene algo que MeshStandardMaterial no puede expresar. Cuatro criterios, en orden:
¿Tiene dos superficies? Laca sobre pintura, agua sobre asfalto, barniz sobre madera. Entonces clearcoat, y no hay alternativa.
¿Deja pasar la luz? Cristal, líquido, plástico traslúcido, hielo. Entonces transmission, y hay que asumir la pasada extra o renunciar a la refracción y quedarse con reflexión más envMap.
¿Su brillo tiene una forma que GGX no produce? Halo en los bordes de un tejido, brillo estirado de un metal cepillado, arcoíris por ángulo. Entonces sheen, anisotropy o iridescence respectivamente.
¿Su reflectancia base no es la del vidrio? Un material muy mate o de índice alto. Entonces ior y specularIntensity, que además son gratis.
Si ninguna de las cuatro se cumple, MeshStandardMaterial produce el mismo píxel más barato. Y si dudas, hay una comprobación honesta: pon las dos versiones lado a lado, aléjate a la distancia real a la que el usuario va a ver el objeto, y mira si distingues cuál es cuál. En la mayoría de los objetos de fondo de una escena web, no se distingue.
Todo lo visto hasta aquí ha sido con valores escalares uniformes en toda la superficie. Un material real no es uniforme: su rugosidad, su metalicidad y su color cambian de un punto a otro, y eso se expresa con texturas. Es lo que viene ahora.