wandres.dev
TEXTURAS II · Los mapas de un material

map, alphaMap y emissiveMap: el color y lo que se ve

Los tres mapas que afectan directamente al color de salida, qué canal usa cada uno, y por qué el mapa emisivo no ilumina nada.

⏱ 16 min

Un material PBR se parametriza con seis o siete texturas, y cada una alimenta una entrada distinta del modelo. Las tres de esta lección son las que actúan sobre el color final de forma directa: la que dice de qué color es la superficie, la que dice dónde no hay superficie, y la que dice dónde la superficie brilla por sí misma. Las tres tienen una convención de canal que no es la que esperarías.

🎯 Al terminar esta lección sabrás
  • Explicar cómo se combinan map y color y en qué espacio de color va cada uno.
  • Usar alphaMap sabiendo qué canal lee y qué mecanismo de transparencia acompañarlo.
  • Configurar emissiveMap con los dos valores que necesita para verse.
  • Distinguir emisión de iluminación.

map: el color base

map es el albedo en un dieléctrico y el F0 en un metal, como ya sabes. Se multiplica por la propiedad color del material, que vale blanco por defecto para que la multiplicación sea neutra.

Esa multiplicación es útil de verdad: con una sola textura en escala de grises y distintos valores de color tienes variantes de un mismo objeto sin duplicar memoria. Es como se hacen los coches de distintos colores en un configurador, o los uniformes de equipo en un videojuego.

Lo que hay que tener claro es que la multiplicación ocurre después de que ambos se hayan llevado al espacio lineal. La textura se decodifica de sRGB si la has marcado como tal, y el color se decodifica desde el hexadecimal que le pasaste. Multiplicar en lineal es lo correcto físicamente y es lo que hace que un color de valor medio realmente reduzca a la mitad la luz reflejada.

import * as THREE from 'three';

const albedo = new THREE.TextureLoader().load('/texturas/tela_color.jpg');
albedo.colorSpace = THREE.SRGBColorSpace;    // es color: obligatorio

const material = new THREE.MeshStandardMaterial({
  map: albedo,
  color: 0x8899ff,     // tinta la textura
  roughness: 0.8,
  metalness: 0
});

map puede llevar canal alfa, y si lo lleva se usa junto con transparent o alphaTest. Es la vía más simple para un recorte, y evita cargar una segunda textura.

alphaMap y el canal verde

Cuando el alfa va en una textura aparte, se usa alphaMap, y aquí está la sorpresa que documentan todos los materiales de Three.js con el mismo párrafo: se lee el canal verde, no el alfa y no la luminancia.

El motivo es de precisión. En los formatos comprimidos de la familia DXT y en el RGB565 sin comprimir, el canal verde tiene un bit más que el rojo y el azul, porque el ojo humano es más sensible al verde y los formatos aprovechan ese sesgo. Para una máscara de un solo valor, el verde es el canal con mejor calidad disponible.

La consecuencia práctica: si guardas tu máscara en escala de grises, funciona, porque los tres canales valen lo mismo. Si la guardas solo en el rojo para ahorrar, verás negro.

alphaMap no decide por sí solo el mecanismo de transparencia. Necesita acompañarse:

// Recorte duro, sin problemas de orden. Hojas, vallas, texto.
const hoja = new THREE.MeshStandardMaterial({
  map: colorHoja,
  alphaMap: mascaraHoja,
  alphaTest: 0.5,
  side: THREE.DoubleSide,
  forceSinglePass: true
});

// Degradado real de opacidad. Cristal esmerilado, humo, desvanecidos.
const velo = new THREE.MeshStandardMaterial({
  map: colorVelo,
  alphaMap: mascaraVelo,
  transparent: true,
  depthWrite: false
});

Y hay un detalle de espacio de color que se cuela: una alphaMap no es una textura de color. Guarda una máscara, un dato. No hay que marcarla como sRGB. Marcarla mal no da ningún error y cambia sutilmente el umbral efectivo de tu alphaTest.

emissiveMap y la trampa del negro

emissiveMap marca dónde la superficie emite luz propia. El resultado se multiplica por dos cosas: la propiedad emissive del material y el escalar emissiveIntensity. Así lo compone el renderer:

uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );

Y aquí está la trampa: emissive vale negro por defecto. Negro multiplicado por cualquier textura es negro. Asignar emissiveMap sin tocar nada más no produce absolutamente ningún efecto visible, que es exactamente el mismo patrón que ya viste con sheenColor.

La configuración correcta son tres líneas:

const pantalla = new THREE.MeshStandardMaterial({
  map: carcasa,
  emissiveMap: pixeles,
  emissive: 0xffffff,        // sin esto no se ve nada
  emissiveIntensity: 2.0,    // por encima de 1 para que el bloom lo capte
  roughness: 0.4
});

emissiveMap sí es una textura de color y hay que marcarla como SRGBColorSpace.

Subir emissiveIntensity por encima de uno es lo habitual cuando hay post-proceso: un valor de dos o tres deja la zona emisiva fuera del rango estándar, que es la señal que busca un pase de bloom para decidir qué resplandece. Sin post-proceso, cualquier valor por encima de uno se recorta a blanco tras el mapeo de tonos y no aporta nada.

Un material emisivo no ilumina nada, y eso rompe la intuición constantemente

La emisión de Three.js es el término Le de la ecuación de renderizado: luz que sale de la superficie hacia la cámara. No es una fuente de luz. Un cartel de neón con emissive blanco y emissiveIntensity a cinco brilla en pantalla y no proyecta ni un fotón sobre la pared que tiene detrás. La pared queda negra y el resultado parece un adhesivo luminoso pegado en una habitación oscura, que es exactamente lo que es.

La razón es arquitectural: la iluminación indirecta viene del mapa de entorno, y el mapa de entorno se calcula antes de dibujar y no sabe nada de tus materiales. Ninguna cantidad de emisión entra en esa integral.

Las tres salidas, en orden de coste: colocar una luz real en el mismo sitio que el objeto emisivo, que es lo que hace todo el mundo y funciona; hornear la contribución en un lightMap si la escena es estática; o construir el entorno a partir de la propia escena con una cámara cúbica, lo que sí captura los emisivos pero cuesta seis renders. Si vienes de un motor con iluminación global en tiempo real, esta es la diferencia que más te va a costar interiorizar, porque en esos motores un emisivo sí ilumina y aquí nunca.

Un apunte sobre los canales

Resumiendo las convenciones de esta lección, que no son intuitivas y conviene tener a mano:

mapa canal que se lee espacio de color
map RGB, y alfa si lo hay SRGBColorSpace
alphaMap verde ninguno, es dato
emissiveMap RGB SRGBColorSpace

La columna de la derecha es la que produce errores silenciosos, y tiene nivel propio. La de en medio es la que produce texturas que salen negras sin explicación.