El sistema de extensiones y las que de verdad importan
Cómo funcionan extensionsUsed y extensionsRequired, el catálogo de extensiones de material y de compresión, y qué hace GLTFLoader cuando encuentra una que no conoce.
La negociación
En la raíz del JSON hay dos arrays de cadenas:
{ "extensionsUsed": [ "KHR_materials_clearcoat", "KHR_texture_transform" ], "extensionsRequired": [ "KHR_draco_mesh_compression" ]}extensionsUsed es informativo: «este fichero usa estas extensiones». Un cargador que no conozca alguna puede ignorarla y renderizar el resto correctamente, con una pérdida de fidelidad acotada. Un modelo con KHR_materials_clearcoat cargado sin soporte se ve sin barniz, pero se ve.
extensionsRequired es un contrato: «sin esto, el fichero no se puede interpretar». Un cargador que no soporte una extensión requerida debe negarse a cargar. Y tiene sentido: si la geometría viene comprimida con Draco y no sabes descomprimirla, los bufferViews no contienen vértices sino un blob opaco. No hay degradación posible.
La regla práctica que se deduce: las extensiones de apariencia van solo en used; las de codificación van también en required. Un exportador que meta KHR_materials_sheen en extensionsRequired está siendo innecesariamente restrictivo y rompe la carga en visores que se verían perfectamente bien sin el terciopelo.
El prefijo indica el estatus: KHR_ son ratificadas por Khronos, EXT_ están respaldadas por varios implementadores sin ratificación formal, y cualquier otro prefijo —ADOBE_, MSFT_, GOOGLE_— es de un solo vendedor.
Las extensiones de material
Son las más numerosas y en Three.js casi todas se traducen a propiedades de MeshPhysicalMaterial, que existe precisamente para cubrirlas.
| Extensión | Propiedad en Three.js | Qué modela |
|---|---|---|
KHR_materials_clearcoat |
clearcoat, clearcoatRoughness |
capa de barniz sobre el material |
KHR_materials_transmission |
transmission |
transparencia refractiva, cristal |
KHR_materials_volume |
thickness, attenuationColor |
absorción dentro del volumen |
KHR_materials_ior |
ior |
índice de refracción |
KHR_materials_specular |
specularIntensity, specularColor |
control del especular en dieléctricos |
KHR_materials_sheen |
sheen, sheenColor, sheenRoughness |
terciopelo, tela |
KHR_materials_iridescence |
iridescence, iridescenceIOR |
pompas de jabón, capas finas |
KHR_materials_anisotropy |
anisotropy, anisotropyRotation |
metal cepillado, pelo |
KHR_materials_emissive_strength |
emissiveIntensity |
emisión por encima de 1 |
KHR_materials_unlit |
MeshBasicMaterial |
sin iluminación, estilo plano |
Dos que no producen MeshPhysicalMaterial y conviene conocer:
KHR_texture_transform. Permite aplicar offset, rotación y escala a las UVs de una textura concreta sin duplicar el atributo de UVs. Es lo que hace posible un atlas donde cada material usa su región. Se traduce a texture.offset, texture.repeat y texture.rotation.
KHR_lights_punctual. Define luces —direccional, puntual y foco— dentro del glTF, con intensidades en las unidades fotométricas correctas: lux para direccionales, candelas para el resto. Es exactamente el modelo que Three.js adoptó en r155, y no es coincidencia: la convergencia hacia glTF fue una de las razones del cambio. Con esta extensión, un modelo puede traer su propia iluminación y verse igual en cualquier visor.
Las extensiones de compresión
Tres, y comprimen cosas distintas. Es importante no confundirlas porque se combinan.
KHR_draco_mesh_compression. Comprime geometría. Sustituye los bufferViews de los atributos por un blob Draco, que el cargador descomprime. Ratios de 5 a 10 veces sobre la geometría. Va siempre en extensionsRequired.
EXT_meshopt_compression. También comprime geometría, con otro algoritmo: cuantización más un codificador de bytes que se descomprime con un WASM diminuto. Ratios algo menores que Draco pero descompresión mucho más rápida. También va en required.
KHR_texture_basisu. Comprime texturas. Sustituye el PNG o JPEG por un fichero KTX2 con datos Basis Universal, que se transcodifican en el cliente al formato de bloque nativo de la GPU. Esto es cualitativamente distinto de las otras dos: no solo reduce la descarga, sino que la textura sigue comprimida en la VRAM.
Junto a ellas hay dos auxiliares que conviene reconocer:
KHR_mesh_quantization. Permite que los atributos usen tipos enteros en lugar de floats. Es lo que habilita la cuantización de meshopt y también se usa sola.
EXT_mesh_gpu_instancing. Un nodo puede declarar N instancias con sus matrices, y el cargador las convierte en un InstancedMesh. Para un bosque o una multitud, es la diferencia entre mil draw calls y uno.
Qué soporta GLTFLoader
GLTFLoader implementa directamente las extensiones de material listadas arriba, KHR_texture_transform, KHR_lights_punctual, KHR_mesh_quantization, EXT_mesh_gpu_instancing, EXT_texture_webp y EXT_texture_avif. Las tres de compresión requieren que le conectes el decodificador correspondiente:
const loader = new GLTFLoader() .setDRACOLoader( dracoLoader ) // KHR_draco_mesh_compression .setKTX2Loader( ktx2Loader ) // KHR_texture_basisu .setMeshoptDecoder( MeshoptDecoder ); // EXT_meshopt_compressionSin esa conexión, un fichero que las declare en extensionsRequired falla con un error explícito, que es el comportamiento correcto.
Para saber qué contiene un fichero antes de cargarlo, gltf-transform inspect lista las extensiones en su salida. Y en tiempo de ejecución, el objeto que devuelve el cargador trae la información del fichero:
const gltf = await loader.loadAsync( '/modelos/coche.glb' );console.log( gltf.parser.json.extensionsUsed );console.log( gltf.parser.json.extensionsRequired );console.log( gltf.asset ); // { generator, version, copyright }Registrar un plugin propio
Para una extensión que Three.js no implementa, GLTFLoader expone un sistema de plugins. Un plugin es un objeto con un name y una o varias de las funciones gancho que el parser invoca.
// Ejemplo minimo: leer una extension propia de nodo y guardar// sus datos en userData para consumirlos despues.loader.register( ( parser ) => ( { name: 'MIEMPRESA_metadatos', createNodeAttachment( nodeIndex ) { const nodeDef = parser.json.nodes[ nodeIndex ]; const ext = nodeDef.extensions?.[ 'MIEMPRESA_metadatos' ]; if ( ! ext ) return null; const marcador = new THREE.Object3D(); marcador.userData.metadatos = ext; return Promise.resolve( marcador ); },} ) );Los ganchos disponibles cubren todos los puntos del parseo: loadMesh, loadMaterial, loadTexture, loadBufferView, createNodeMesh, createNodeAttachment, extendMaterialParams y afterRoot. El propio Three.js implementa sus extensiones con este mismo mecanismo, así que el sistema está bien probado.