clearcoat: la capa de laca que va encima de todo
Qué simula físicamente una segunda capa especular, por qué tiene su propio F0 y su propia normal, y cuándo un clearcoat resuelve algo que roughness no puede.
Dos lóbulos, no uno
Con clearcoat activado, el shader evalúa un segundo lóbulo especular GGX completo, con su propia rugosidad y su propia normal, y lo suma encima del material base. En r184 la función se llama BRDF_GGX_Clearcoat y usa la misma tríada de distribución, Fresnel y visibilidad que la capa principal.
Lo interesante son los parámetros que no puedes tocar:
material.clearcoatF0 = vec3( 0.04 );material.clearcoatF90 = 1.0;El F0 de la capa está fijado en 0.04, es decir, un dieléctrico con índice de refracción 1.5. Y tiene sentido: un barniz, una laca o una película de agua son todos dieléctricos comunes, y ninguno tiene reflectancia tintada. La capa siempre refleja blanco, con la intensidad del Fresnel, sea cual sea el color de lo que hay debajo. Esa es exactamente la razón por la que un coche rojo tiene reflejos blancos y no rojos.
clearcoat va de cero a uno y controla cuánto pesa esa capa. clearcoatRoughness controla lo pulida que está, y sufre el mismo recorte por abajo que la rugosidad principal: se fuerza a un mínimo de 0.0525 y se le suma la rugosidad geométrica derivada de la variación de la normal en pantalla.
import * as THREE from 'three';// Pintura de coche: base rugosa con color, capa de barniz muy pulida.const pintura = new THREE.MeshPhysicalMaterial({ color: 0xb4162c, metalness: 0, roughness: 0.45, clearcoat: 1.0, clearcoatRoughness: 0.03});// Fibra de carbono: base metalica y oscura bajo la misma laca.const fibra = new THREE.MeshPhysicalMaterial({ color: 0x1a1a1a, metalness: 1, roughness: 0.35, clearcoat: 1.0, clearcoatRoughness: 0.05});// Superficie mojada: la capa es agua, mas fina y menos pulida.const mojado = new THREE.MeshPhysicalMaterial({ color: 0x6b5a45, metalness: 0, roughness: 0.85, clearcoat: 0.6, clearcoatRoughness: 0.15});La combinación característica es siempre la misma: base rugosa, capa pulida. Si pones las dos pulidas, no distinguirás el clearcoat de un simple roughness bajo, y habrás pagado un lóbulo de más para nada.
Por qué roughness no puede hacer esto
La pregunta razonable es por qué no basta con bajar roughness. La respuesta es que una superficie con una sola rugosidad produce un solo brillo, y estos materiales producen dos a la vez: uno ancho y difuso que viene de la base, y otro estrecho y nítido que viene de la laca. Los dos coexisten en el mismo píxel.
Míralo en una pintura de coche real bajo una lámpara: hay un reflejo especular pequeño y muy definido de la propia lámpara, y alrededor un brillo amplio y suave que sigue la curvatura de la chapa. Con roughness solo puedes tener uno de los dos.
Hay un segundo efecto que tampoco es replicable: el clearcoat oscurece ligeramente la base, porque parte de la luz que iba a llegar a la capa inferior se refleja antes en la superior. Esa pérdida es lo que hace que una superficie mojada se vea más saturada y más oscura que la misma superficie seca, y sale sola del modelo.
La normal propia
clearcoatNormalMap es la propiedad menos usada y la que más cambia el resultado. Le da a la capa superior una normal independiente de la de la base, con su propia intensidad en clearcoatNormalScale, un Vector2.
Para qué sirve: una laca real no es perfectamente lisa, tiene ondulaciones de escala grande, lo que en pintura de coche se llama piel de naranja. Con una normal propia de baja frecuencia, los reflejos de la laca se ondulan mientras el color y el detalle de la base se mantienen quietos. Es el detalle que separa una pintura de coche creíble de una bola roja brillante.
El caso simétrico es igual de útil: fibra de carbono con un normalMap de trama en la base y sin clearcoatNormalMap, para que el tejido se vea en la base pero la laca refleje liso, como es en realidad.
const carbono = new THREE.MeshPhysicalMaterial({ color: 0x101010, metalness: 1, roughness: 0.3, normalMap: tramaDeFibra, // el tejido, solo en la base clearcoat: 1, clearcoatRoughness: 0.04, clearcoatNormalMap: pielDeNaranja, // ondulacion suave, solo en la laca clearcoatNormalScale: new THREE.Vector2(0.15, 0.15)});El coste
Un lóbulo GGX más por luz, más un muestreo adicional del entorno prefiltrado para la reflexión indirecta de la capa, más una segunda normal que hay que interpolar y, si usas clearcoatNormalMap, una lectura de textura y una transformación al espacio tangente adicionales.
En la práctica, sobre un MeshStandardMaterial de referencia, activar clearcoat sube el coste del fragment shader alrededor de un tercio. No es catastrófico y es el más barato de todos los añadidos de MeshPhysicalMaterial, muy por debajo de transmission.
Dos avisos de rendimiento. El primero ya lo conoces: el setter de clearcoat incrementa la versión del material cuando el valor cruza el cero, así que animarlo desde 0 provoca una recompilación. Arranca en 0.001. El segundo es que clearcoat sin entorno se ve casi igual que sin clearcoat, porque lo que hace brillar la laca es sobre todo la reflexión del entorno, no las luces puntuales. Si vas a pagar la capa, asegúrate de que hay algo que reflejar.