Three.js + USDC en el navegador
Tienes un archivo .usdc procedente de Maya o Houdini y quieres mostrarlo en una escena de Three.js. Yo también he pasado por eso. Hasta hace poco, las opciones no eran muy buenas. Podías convertirlo a glTF sin conexión, preparar una compilación WASM de OpenUSD o simplemente rendirte y utilizar otro formato.
Esta guía muestra cómo analizar USDC directamente en JavaScript mediante @cinevva/usdjs y cómo llevar esos datos a Three.js.
¿Qué es USDC?
Los archivos USD se presentan en tres variantes. USDA es la versión de texto, legible para humanos y fácil de depurar, pero verbosa. USDC (a veces llamado «Crate») es el formato binario que utilizan realmente los flujos de producción porque es compacto y se carga rápido en herramientas nativas. USDZ es un archivo ZIP que contiene USDC junto con texturas y que Apple utiliza para AR Quick Look.
Este es el problema: USDC es un formato binario propietario. Pixar escribió la implementación de referencia en C++ y, hasta ahora, no existía una forma basada únicamente en JavaScript de leerlo. Eso está empezando a cambiar: en diciembre de 2025, la Alliance for OpenUSD publicó la Especificación principal 1.0, el primer estándar ratificado que documenta cómo se estructuran, componen e intercambian los datos de escena de OpenUSD, y ya se está preparando una revisión 1.1. Sin embargo, la implementación de referencia sigue estando en el código C++ de Pixar, por lo que un lector independiente continúa siendo la forma práctica de acceder al formato desde JavaScript.
Por qué podría interesarte
Si estás creando aplicaciones web 3D que deben trabajar con contenido procedente de flujos de cine o efectos visuales, acabarás encontrándote con USD. Los artistas exportan desde Maya, Houdini o Blender, y esas exportaciones suelen estar en USDC.
Antes de @cinevva/usdjs, tenías tres opciones. Podías ejecutar usdcat para convertir USDC al formato de texto, lo que añade un paso al proceso de compilación y elimina la ventaja del tamaño compacto del formato binario. Podías compilar OpenUSD o TinyUSDZ a WebAssembly, lo que añade varios megabytes a tu paquete y requiere cabeceras especiales del servidor para utilizar hilos. O podías usar el USDLoader integrado en Three.js, que admite USDZ, pero ofrece compatibilidad limitada con USDC y una composición mínima.
Ahora hay una cuarta opción: analizar USDC de forma nativa en JavaScript.
Cómo funciona
La biblioteca @cinevva/usdjs reimplementa la funcionalidad principal de USD en TypeScript. Creé el analizador de USDC leyendo el código fuente C++ de Pixar y trasladando la especificación del formato Crate a JavaScript. Cuando existe alguna ambigüedad de comportamiento, reproducimos lo que hace OpenUSD.
En la práctica, se utiliza así:
import { parseUsdcToLayer } from '@cinevva/usdjs';
// Fetch the USDC file
const response = await fetch('/model.usdc');
const buffer = await response.arrayBuffer();
// Parse it
const layer = parseUsdcToLayer(buffer, { identifier: 'model.usdc' });
// Now you have a structured layer with prims, properties, and metadata
console.log(layer.pseudoRoot.children);Sin WASM. Sin código nativo. Solo JavaScript leyendo bytes y creando una representación estructurada.
Cómo integrarlo en Three.js
Analizar USD solo resuelve la mitad del problema. También necesitas convertir los conceptos de USD —prims, transformaciones y esquemas de malla— en objetos de Three.js.
Este es un ejemplo mínimo que carga un archivo USDC y crea mallas de Three.js:
import * as THREE from 'three';
import { UsdStage, parseUsdcToLayer } from '@cinevva/usdjs';
async function loadUsdcToThree(url: string, scene: THREE.Scene) {
// 1. Fetch and parse
const buffer = await fetch(url).then(r => r.arrayBuffer());
const layer = parseUsdcToLayer(buffer, { identifier: url });
// 2. Create a stage (handles composition if there are references)
const stage = UsdStage.open(layer);
// 3. Walk the prim tree
for (const prim of stage.traverse()) {
// Skip non-geometry
if (prim.typeName !== 'Mesh') continue;
// Get geometry data
const points = prim.getAttribute('points')?.value;
const faceVertexIndices = prim.getAttribute('faceVertexIndices')?.value;
const faceVertexCounts = prim.getAttribute('faceVertexCounts')?.value;
if (!points || !faceVertexIndices || !faceVertexCounts) continue;
// Convert to Three.js geometry
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position',
new THREE.Float32BufferAttribute(points.flat(), 3)
);
// USD uses faceVertexCounts + faceVertexIndices, Three.js wants a flat index array
// For triangulated meshes, this is straightforward:
geometry.setIndex(Array.from(faceVertexIndices));
geometry.computeVertexNormals();
// Create mesh
const material = new THREE.MeshStandardMaterial({ color: 0x888888 });
const mesh = new THREE.Mesh(geometry, material);
// Apply transform
const xform = getWorldTransform(prim);
mesh.matrix.fromArray(xform);
mesh.matrixAutoUpdate = false;
scene.add(mesh);
}
}
function getWorldTransform(prim): number[] {
// Simplified: in practice you'd compose parent transforms
const xformOp = prim.getAttribute('xformOp:transform');
if (xformOp?.value) {
return xformOp.value.flat();
}
return [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1];
}Este ejemplo está simplificado. El código real debe gestionar el apilamiento de transformaciones (los prims de USD pueden tener varios atributos xformOp que se componen entre sí), las superficies de subdivisión (las mallas con subdivisión catmullClark necesitan subdividirse realmente), los materiales (los parámetros de UsdPreviewSurface se asignan a MeshStandardMaterial de Three.js), las texturas (las rutas de recursos deben resolverse y cargarse) y la animación esquelética (los enlaces de UsdSkel deben convertirse a SkinnedMesh).
Si quieres ver cómo funciona todo esto en conjunto, consulta @cinevva/usdjs-viewer. Es una implementación de referencia completa.
La composición importa más de lo que parece
Analizar un único archivo USDC es la parte fácil. Las escenas USD reales utilizan composición, lo que significa que las subcapas, referencias, cargas y variantes funcionan todas en conjunto.
Imagina el modelo de un coche. La carrocería hace referencia a body.usdc, las ruedas hacen referencia a wheel.usdc y existen variantes para las configuraciones «sport» y «sedan». La escena final se ensambla a partir de todas estas piezas en tiempo de ejecución.
Así es como puedes gestionarlo:
import { UsdStage, parseUsdcToLayer, FetchResolver } from '@cinevva/usdjs';
// Create a resolver that knows how to fetch referenced files
const resolver = new FetchResolver({
baseUrl: '/assets/',
});
// Open with composition
const stage = UsdStage.open(rootLayer, { resolver });
// Set a variant selection
stage.setVariantSelection('/Car', 'bodyStyle', 'sport');
// Now traverse the composed scene
for (const prim of stage.traverse()) {
// You'll see resolved references and the selected variant
}El resolutor obtiene los archivos USDC referenciados cuando son necesarios. La composición se realiza en JavaScript y produce la escena aplanada que obtendrías mediante usdcat --flatten.
Rendimiento
Seamos sinceros: analizar USDC en JavaScript es más lento que hacerlo en C++ nativo. Es el precio de prescindir de WASM y de pasos adicionales de compilación.
En la práctica, para los casos de uso web habituales con modelos de menos de 10 MB y menos de 100 000 triángulos, el análisis tarda entre 50 y 200 ms en hardware moderno. Es un tiempo aceptable para la carga inicial si muestras un indicador de carga.
Puedes acelerarlo. No cargues toda la escena por adelantado. Carga la capa raíz, renderiza lo que puedas y, después, obtén las cargas bajo demanda. Traslada el análisis a un Web Worker para que la interfaz siga respondiendo. Almacena en caché las capas analizadas en IndexedDB para que las visitas posteriores sean instantáneas. Muestra una vista previa de baja resolución mientras se transmite la escena completa.
Esta es una configuración con Web Worker:
// worker.ts
import { parseUsdcToLayer } from '@cinevva/usdjs';
self.onmessage = async (e) => {
const { buffer, identifier } = e.data;
const layer = parseUsdcToLayer(buffer, { identifier });
// Serialize layer data (not the methods)
const data = serializeLayer(layer);
self.postMessage(data);
};
// main.ts
const worker = new Worker(new URL('./worker.ts', import.meta.url));
worker.postMessage({ buffer, identifier: 'model.usdc' });
worker.onmessage = (e) => {
const layerData = e.data;
// Build Three.js scene from layerData
};Qué funciona y qué no
La mayoría de las funciones habituales están disponibles. La geometría —mallas, puntos y curvas— se analiza correctamente, incluidos los arrays de vértices comprimidos. Las transformaciones funcionan con el apilamiento completo de xformOp. Los materiales se convierten de UsdPreviewSurface a PBR. La composición admite subcapas, referencias, cargas, variantes y herencias. Las texturas se resuelven y cargan si proporcionas un resolutor. La animación esquelética básica funciona con los rigs de personajes más comunes.
Sin embargo, todavía hay carencias. Esto no es una implementación de Hydra, por lo que eres responsable de convertir los datos USD al motor de renderizado que utilices. No hay API de esquemas tipadas como UsdGeomMesh con métodos prácticos. Se trabaja con prims y atributos genéricos. Algunas funciones de composición, como las especializaciones, las reubicaciones y los clips de valores, aún no están implementadas. Las redes de materiales complejas que vayan más allá de un UsdPreviewSurface sencillo requieren un tratamiento personalizado.
Cuándo utilizarlo
Esta opción resulta adecuada si necesitas cargar archivos USD en una aplicación web sin un paso de compilación de WASM, si tu flujo de trabajo produce USDC y no quieres convertirlo a glTF, si estás creando un visor o editor de USD para el navegador, o si quieres inspeccionar la estructura de USD en lugar de limitarte a renderizarla.
Utiliza otra solución si necesitas una compatibilidad total con OpenUSD, incluidos todos los casos extremos; si tus escenas son enormes —más de 100 MB— y requieren rendimiento nativo; o si ya utilizas una compilación WASM y su complejidad es aceptable para tu proyecto.
Recursos
Los tres paquetes son @cinevva/usdjs para el análisis y la composición principales, @cinevva/usdjs-viewer para un visor de navegador basado en Three.js y @cinevva/usdjs-renderer para renderizar archivos PNG sin interfaz gráfica durante las pruebas.
Para consultar la documentación, visita la Referencia de la API de usdjs, la Especificación OpenUSD de Pixar y la Documentación de Three.js.
Si quieres entender cómo se corresponde el analizador de USDC con la implementación de Pixar, consulta src/usdc/PIXAR_PARITY.md en el repositorio de usdjs.
Pruébalo
La forma más rápida de verlo en funcionamiento es visitar la demostración de usdjs-viewer y soltar en ella un archivo USDC.
Para integrarlo en tu propio proyecto, instala el paquete y comienza con los ejemplos de código anteriores:
npm install @cinevva/usdjsUSD en el navegador es posible. No siempre es la opción adecuada, pero, cuando lo necesitas, el análisis basado únicamente en JavaScript supone un paso menos en el proceso de compilación y una dependencia menos de la que preocuparte.
Contenido relacionado
- Fundamentos de WebGL para desarrolladores de juegos — la API de renderizado que impulsa las escenas de Three.js
- Tecnologías para juegos web en 2026 — el lugar que ocupa Three.js en el ecosistema de WebGL, WebGPU y Wasm
- Tecnología para mundos abiertos 3D en el navegador — transmisión de recursos 3D, incluido USD, para mundos abiertos
- Dónde encontrar recursos gratuitos para juegos — fuentes de modelos 3D compatibles con Three.js