transmission, thickness e ior: vidrio de verdad
La diferencia entre opacity y transmission, qué hace el renderer para que un objeto vea lo que hay detrás, y cómo se configura un volumen con absorción.
Opacidad no es transmisión
opacity es una mezcla alfa: el color final es una interpolación entre lo que ya había en el buffer y lo que el material calcula. Todo el material se atenúa por igual, incluido su brillo especular. Un cristal con opacity: 0.1 casi no tiene reflejos, y un cristal real con esa transparencia los tiene enteros.
transmission modela otra cosa: la luz que atraviesa la superficie en lugar de reflejarse. El especular se mantiene intacto, porque lo que se transmite es justo lo que el Fresnel no reflejó. Un material con transmission: 1 sigue reflejando el cuatro por ciento en incidencia normal y casi el cien por cien en incidencia rasante, que es exactamente el comportamiento de una ventana.
La documentación es explícita en un punto: cuando transmission no es cero, opacity debe valer 1. Mezclar los dos mecanismos produce resultados sin sentido físico.
import * as THREE from 'three';// Cristal de pared fina: una ventana, una copa, una burbuja.const ventana = new THREE.MeshPhysicalMaterial({ transmission: 1, thickness: 0, // pared fina: sin volumen roughness: 0.02, ior: 1.5, metalness: 0, opacity: 1});// Volumen solido: un prisma, una canica, una gota.const canica = new THREE.MeshPhysicalMaterial({ transmission: 1, thickness: 1.2, // en unidades del espacio de la malla roughness: 0.05, ior: 1.52, attenuationColor: new THREE.Color(0x7fd1c1), attenuationDistance: 0.6, metalness: 0, opacity: 1});Pared fina o volumen
thickness es la propiedad que decide entre dos modelos distintos, y su valor por defecto es 0.
Con thickness en cero, el material es de pared fina: la luz entra y sale por la misma superficie sin recorrer distancia interior. No hay refracción apreciable ni absorción. Es lo correcto para una ventana, una pompa, una lámina de plástico o el cristal de un reloj.
Con thickness mayor que cero, el material es un límite de volumen: la luz recorre esa distancia por dentro antes de salir, se desvía según el ior y se atenúa según el color y la distancia de atenuación. Es lo correcto para una canica, un prisma, un vaso lleno de líquido o una gota.
El valor de thickness está en las unidades del espacio de la malla, no en metros ni en píxeles. Para una esfera de radio uno, un thickness de dos es el diámetro completo; poner veinte no es “más sólido”, es un volumen veinte veces más largo, con la absorción correspondiente. Es el error más común: subir el grosor hasta que quede bien en vez de medirlo con la geometría.
thicknessMap guarda el grosor en el canal verde y multiplica al escalar, lo que permite que una malla con partes gruesas y finas se comporte correctamente en cada zona.
El color de un volumen no es su color base
En un material transmisivo, color casi no se ve, porque el difuso ha desaparecido: lo que había de difuso ahora se transmite. El color de un vidrio verde no está en color, está en la absorción del volumen.
attenuationColor es el color al que se convierte la luz blanca tras recorrer exactamente attenuationDistance unidades dentro del material. attenuationDistance vale Infinity por defecto, lo que significa ninguna absorción y un vidrio perfectamente incoloro.
La relación es exponencial, como manda la ley de Beer-Lambert: al doble de recorrido, el color se aplica dos veces. Por eso un objeto de vidrio coloreado se ve casi transparente en sus partes finas y muy saturado en las gruesas, y por eso este modelo produce cristal creíble mientras que teñir el color produce plástico.
Ajustar la pareja es cuestión de dos pasos. Primero elige el color de saturación máxima que quieres ver en la parte más gruesa del objeto. Después baja attenuationDistance hasta que la parte fina tenga el tinte que buscas: valores del orden de la mitad del tamaño del objeto suelen quedar bien, y valores muy pequeños producen un material casi opaco.
dispersion completa el conjunto: separa las longitudes de onda al refractar, que es el arcoíris de un prisma. Solo funciona con transmisión, y valores realistas están en el rango de cero a uno. Es de los pocos parámetros donde pasarse es inmediatamente visible y bastante feo.
Lo que hace el renderer por debajo
Un objeto transmisivo tiene que muestrear lo que hay detrás de él, y en un rasterizador eso no está disponible: cuando dibujas el cristal, el framebuffer contiene lo que ya se ha dibujado, no la escena completa.
La solución de Three.js es dibujar la escena dos veces. Antes de la pasada normal, el renderer hace una pasada de transmisión que dibuja la escena en un render target aparte. Ese target se le pasa al material en dos uniforms, transmissionSamplerMap y transmissionSamplerSize, y el shader lo muestrea desplazando la coordenada según el vector refractado. La rugosidad del material selecciona un nivel de mipmap más borroso de ese target, que es lo que produce el aspecto de vidrio esmerilado.
Esto explica de golpe todo el perfil de coste y todas las limitaciones.
El coste, con números
La pasada de transmisión dibuja la escena una segunda vez. Si tu escena cuesta 400 llamadas de dibujo, con un solo objeto transmisivo pasa a costar unas 800. No importa cuántos objetos transmisivos haya: la pasada es una, así que el segundo cristal es casi gratis comparado con el primero. Esa es la asimetría clave: el primer material con transmisión es carísimo y los siguientes son baratos.
De ahí salen dos decisiones prácticas. Si solo necesitas un objeto de vidrio y estás justo de presupuesto, plantéate un MeshPhysicalMaterial sin transmisión, con roughness bajo, metalness cero y un buen envMap: refleja como el cristal y no refracta, y a cierta distancia nadie lo distingue. Y si ya has aceptado el coste de la pasada, no te cortes en usar transmisión en todos los objetos que la necesiten, porque el grueso ya lo has pagado.