Three.js + USDC dans le navigateur
Vous avez donc un fichier .usdc provenant de Maya ou Houdini et vous voulez l'afficher dans une scène Three.js. Je suis passé par là. Jusqu'à récemment, les options n'étaient pas très intéressantes. Vous pouviez le convertir hors ligne en glTF, mettre en place une version WASM d'OpenUSD, ou simplement abandonner et utiliser un autre format.
Ce guide vous montre comment analyser directement des fichiers USDC en JavaScript avec @cinevva/usdjs, puis intégrer ces données dans Three.js.
Qu'est-ce que l'USDC ?
Les fichiers USD existent sous trois formes. USDA est la version texte, lisible par les humains et facile à déboguer, mais verbeuse. USDC, parfois appelé « Crate », est le format binaire réellement utilisé par les pipelines de production, car il est compact et se charge rapidement dans les outils natifs. USDZ est une archive zip contenant un fichier USDC ainsi que des textures, utilisée par Apple pour AR Quick Look.
Voici le problème : USDC est un format binaire propriétaire. Pixar a écrit l'implémentation de référence en C++ et, jusqu'à présent, il n'existait aucun moyen de le lire en JavaScript pur. Cela commence à changer : en décembre 2025, l'Alliance for OpenUSD a publié la Core Specification 1.0, la première norme ratifiée documentant la manière dont les données de scène OpenUSD sont structurées, composées et échangées, tandis qu'une révision 1.1 est déjà en préparation. L'implémentation de référence se trouve cependant toujours dans la base de code C++ de Pixar ; un lecteur distinct reste donc le moyen le plus pratique d'accéder à ce format depuis JavaScript.
Pourquoi cela peut vous intéresser
Si vous développez des applications web 3D qui doivent exploiter du contenu issu de pipelines de cinéma ou d'effets visuels, vous rencontrerez forcément USD. Les artistes exportent depuis Maya, Houdini ou Blender, et ces exports sont souvent au format USDC.
Avant @cinevva/usdjs, vous aviez trois possibilités. Vous pouviez exécuter usdcat pour convertir l'USDC au format texte, ce qui ajoutait une étape de build et faisait perdre la compacité du format binaire. Vous pouviez compiler OpenUSD ou TinyUSDZ en WebAssembly, ce qui ajoutait plusieurs mégaoctets à votre bundle et nécessitait des en-têtes serveur particuliers pour le multithreading. Ou vous pouviez utiliser l'USDLoader intégré à Three.js, qui prend en charge USDZ, mais dont la prise en charge d'USDC et de la composition reste limitée.
Il existe désormais une quatrième possibilité : analyser l'USDC nativement en JavaScript.
Fonctionnement
La bibliothèque @cinevva/usdjs réimplémente les fonctionnalités essentielles d'USD en TypeScript. J'ai développé l'analyseur USDC en étudiant le code source C++ de Pixar et en transposant la spécification du format Crate en JavaScript. Lorsque le comportement est ambigu, nous reproduisons celui d'OpenUSD.
En pratique, cela ressemble à ceci :
import { parseUsdcToLayer } from '@cinevva/usdjs';
// Récupérer le fichier USDC
const response = await fetch('/model.usdc');
const buffer = await response.arrayBuffer();
// L'analyser
const layer = parseUsdcToLayer(buffer, { identifier: 'model.usdc' });
// Vous disposez maintenant d'un layer structuré avec des prims, des propriétés et des métadonnées
console.log(layer.pseudoRoot.children);Pas de WASM. Pas de code natif. Seulement du JavaScript qui lit des octets et construit une représentation structurée.
L'intégrer à Three.js
L'analyse d'USD ne représente que la moitié du problème. Vous devez également convertir les concepts USD — prims, transformations et schémas de maillage — en objets Three.js.
Voici un exemple minimal qui charge un fichier USDC et crée des maillages Three.js :
import * as THREE from 'three';
import { UsdStage, parseUsdcToLayer } from '@cinevva/usdjs';
async function loadUsdcToThree(url: string, scene: THREE.Scene) {
// 1. Récupérer et analyser
const buffer = await fetch(url).then(r => r.arrayBuffer());
const layer = parseUsdcToLayer(buffer, { identifier: url });
// 2. Créer un stage (gère la composition s'il existe des références)
const stage = UsdStage.open(layer);
// 3. Parcourir l'arborescence des prims
for (const prim of stage.traverse()) {
// Ignorer les éléments sans géométrie
if (prim.typeName !== 'Mesh') continue;
// Récupérer les données géométriques
const points = prim.getAttribute('points')?.value;
const faceVertexIndices = prim.getAttribute('faceVertexIndices')?.value;
const faceVertexCounts = prim.getAttribute('faceVertexCounts')?.value;
if (!points || !faceVertexIndices || !faceVertexCounts) continue;
// Convertir en géométrie Three.js
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position',
new THREE.Float32BufferAttribute(points.flat(), 3)
);
// USD utilise faceVertexCounts + faceVertexIndices, Three.js attend un tableau d'indices à plat
// Pour les maillages triangulés, c'est simple :
geometry.setIndex(Array.from(faceVertexIndices));
geometry.computeVertexNormals();
// Créer le maillage
const material = new THREE.MeshStandardMaterial({ color: 0x888888 });
const mesh = new THREE.Mesh(geometry, material);
// Appliquer la transformation
const xform = getWorldTransform(prim);
mesh.matrix.fromArray(xform);
mesh.matrixAutoUpdate = false;
scene.add(mesh);
}
}
function getWorldTransform(prim): number[] {
// Version simplifiée : en pratique, il faudrait composer les transformations parentes
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];
}Cet exemple est simplifié. Le code réel doit gérer l'empilement des transformations — les prims USD peuvent avoir plusieurs attributs xformOp qui se composent —, les surfaces de subdivision — les maillages utilisant la subdivision catmullClark nécessitent une véritable subdivision —, les matériaux — les paramètres UsdPreviewSurface sont associés à MeshStandardMaterial de Three.js —, les textures — les chemins de ressources doivent être résolus et chargés — et l'animation squelettique — les liaisons UsdSkel doivent être converties en SkinnedMesh.
Si vous voulez voir comment tous ces éléments fonctionnent ensemble, consultez @cinevva/usdjs-viewer. Il s'agit d'une implémentation de référence complète.
La composition est plus importante qu'on ne le pense
L'analyse d'un seul fichier USDC est la partie facile. Les véritables scènes USD utilisent la composition, ce qui implique que les sous-layers, les références, les payloads et les variantes fonctionnent tous ensemble.
Imaginez un modèle de voiture. La carrosserie référence body.usdc, les roues référencent wheel.usdc, et il existe des variantes pour les configurations « sport » et « berline ». La scène finale est assemblée à partir de tous ces éléments lors de l'exécution.
Voici comment gérer cela :
import { UsdStage, parseUsdcToLayer, FetchResolver } from '@cinevva/usdjs';
// Créer un résolveur capable de récupérer les fichiers référencés
const resolver = new FetchResolver({
baseUrl: '/assets/',
});
// Ouvrir avec la composition
const stage = UsdStage.open(rootLayer, { resolver });
// Sélectionner une variante
stage.setVariantSelection('/Car', 'bodyStyle', 'sport');
// Parcourir maintenant la scène composée
for (const prim of stage.traverse()) {
// Vous verrez les références résolues et la variante sélectionnée
}Le résolveur récupère à la demande les fichiers USDC référencés. La composition s'effectue en JavaScript et produit la scène aplatie que vous obtiendriez avec usdcat --flatten.
Performances
Soyons honnêtes : analyser un fichier USDC en JavaScript est plus lent qu'en C++ natif. C'est le compromis à accepter pour éviter WASM et les étapes de build.
En pratique, pour les usages web courants avec des modèles de moins de 10 Mo et de moins de 100 000 triangles, l'analyse prend entre 50 et 200 ms sur du matériel moderne. Cela convient au chargement initial si vous affichez un indicateur de progression.
Vous pouvez accélérer le processus. Ne chargez pas toute la scène dès le départ. Chargez le layer racine, affichez ce qui est disponible, puis récupérez les payloads à la demande. Déplacez l'analyse dans un Web Worker afin que l'interface reste réactive. Mettez les layers analysés en cache dans IndexedDB pour que les visites suivantes soient instantanées. Affichez un aperçu en basse résolution pendant le chargement progressif de la scène complète.
Voici une configuration avec un Web Worker :
// worker.ts
import { parseUsdcToLayer } from '@cinevva/usdjs';
self.onmessage = async (e) => {
const { buffer, identifier } = e.data;
const layer = parseUsdcToLayer(buffer, { identifier });
// Sérialiser les données du layer (pas les méthodes)
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;
// Construire la scène Three.js à partir de layerData
};Ce qui fonctionne et ce qui ne fonctionne pas
La plupart des fonctionnalités courantes fonctionnent. La géométrie — maillages, points et courbes — est correctement analysée, y compris les tableaux de sommets compressés. Les transformations fonctionnent avec l'empilement complet des xformOp. Les matériaux sont convertis de UsdPreviewSurface vers le rendu PBR. La composition gère les sous-layers, les références, les payloads, les variantes et les héritages. Les textures sont résolues et chargées si vous fournissez un résolveur. L'animation squelettique de base fonctionne pour les rigs de personnages courants.
Il reste toutefois des lacunes. Il ne s'agit pas d'une implémentation d'Hydra : vous devez donc convertir vous-même les données USD pour le moteur de rendu que vous utilisez. Il n'existe pas d'API de schéma typée comme UsdGeomMesh avec des méthodes pratiques. Vous travaillez avec des prims et des attributs génériques. Certaines fonctionnalités de composition, comme les spécialisations, les relocations et les value clips, ne sont pas encore implémentées. Les réseaux de matériaux complexes allant au-delà d'un simple UsdPreviewSurface nécessitent un traitement personnalisé.
Quand l'utiliser
Cette solution est pertinente lorsque vous devez charger des fichiers USD dans une application web sans étape de build WASM, lorsque votre pipeline produit des fichiers USDC et que vous ne souhaitez pas les convertir en glTF, lorsque vous développez un visualiseur ou un éditeur USD pour le navigateur, ou lorsque vous voulez inspecter la structure USD plutôt que simplement l'afficher.
Utilisez une autre solution si vous avez besoin d'une compatibilité totale avec OpenUSD jusque dans les cas limites, si vos scènes sont très volumineuses — plus de 100 Mo — et nécessitent des performances natives, ou si vous utilisez déjà une version WASM et que sa complexité reste acceptable pour votre projet.
Ressources
Les trois packages sont @cinevva/usdjs pour l'analyse et la composition de base, @cinevva/usdjs-viewer pour un visualiseur web basé sur Three.js, et @cinevva/usdjs-renderer pour le rendu PNG headless dans les tests.
Pour la documentation, consultez la référence de l'API usdjs, la spécification OpenUSD de Pixar et la documentation de Three.js.
Si vous voulez comprendre comment l'analyseur USDC correspond à l'implémentation de Pixar, consultez src/usdc/PIXAR_PARITY.md dans le dépôt usdjs.
Essayez-le
Le moyen le plus rapide de voir cette solution en action consiste à ouvrir la démo d'usdjs-viewer et à y déposer un fichier USDC.
Pour l'intégrer à votre propre projet, installez le package et commencez par les exemples de code ci-dessus :
npm install @cinevva/usdjsUSD dans le navigateur, c'est possible. Ce n'est pas toujours le bon choix, mais lorsque vous en avez besoin, une analyse en JavaScript pur signifie une étape de build et une dépendance de moins à gérer.
Articles associés
- Fondamentaux de WebGL pour les développeurs de jeux — l'API de rendu qui alimente les scènes Three.js
- Stack technique des jeux web en 2026 — la place de Three.js dans l'écosystème WebGL/WebGPU/Wasm
- Technologies des mondes ouverts 3D dans le navigateur — le streaming de ressources 3D, notamment USD, pour les mondes ouverts
- Où trouver des ressources de jeu gratuites — des sources de modèles 3D compatibles avec Three.js