Comment exporter des modèles 3D en USDZ avec le USDZExporter de Three.js

Guide étape par étape pour convertir des scènes GLB/GLTF en USDZ avec le USDZExporter de Three.js — de l’export dans le navigateur jusqu’aux workflows AR Quick Look et Apple Vision Pro.

Comment exporter des modèles 3D en USDZ avec le USDZExporter de Three.js

Comment exporter des modèles 3D en USDZ avec Three.js USDZExporter

USDZ est le format 3D officiel d’Apple spécialement conçu pour la Réalité Augmentée. Il alimente AR Quick Look sur iPhone et iPad et sert également de format principal pour RealityKit sur Apple Vision Pro.

Ce guide complet, étape par étape, vous montrera comment convertir des modèles GLB en fichiers .usdz optimisés directement dans le navigateur grâce au USDZExporter officiel de Three.js, sans avoir besoin d’installer de logiciel desktop.

Étape 1 : Installation

Commencez par installer Three.js dans votre projet. C’est la seule dépendance principale nécessaire pour la conversion :

npm install three

Ensuite, importez les modules nécessaires dans votre fichier React :

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { USDZExporter } from 'three/addons/exporters/USDZExporter.js';

Étape 2 : Préparation de la scène (Correction des matériaux et textures)

USDZ prend uniquement en charge MeshStandardMaterial. La fonction suivante parcourt votre modèle afin de convertir les matériaux et corriger les cartes ORM (Occlusion/Roughness/Metalness), qui représentent la cause la plus fréquente des modèles noirs ou incorrects après l’exportation.

const prepareSceneForUSDZ = async (scene) => {
  scene.traverse((obj) => {
    if (!obj.isMesh) return;

    let materials = obj.material;
    if (!Array.isArray(materials)) materials = [materials];

    materials.forEach((mat, index) => {
      if (!(mat instanceof THREE.MeshStandardMaterial)) {
        const oldMat = mat;
        const newMat = new THREE.MeshStandardMaterial({
          color: oldMat.color || 0xffffff,
          map: oldMat.map,
          normalMap: oldMat.normalMap,
          roughnessMap: oldMat.roughnessMap,
          metalnessMap: oldMat.metalnessMap,
          emissiveMap: oldMat.emissiveMap,
          alphaMap: oldMat.alphaMap,
          transparent: !!oldMat.transparent,
          opacity: oldMat.opacity ?? 1,
          side: oldMat.side || THREE.FrontSide,
        });

        if (Array.isArray(obj.material)) {
          obj.material[index] = newMat;
        } else {
          obj.material = newMat;
        }
      }
    });

    // Correction des cartes ORM (Occlusion/Roughness/Metalness)
    const mat = Array.isArray(obj.material) ? obj.material[0] : obj.material;
    if (mat.metalnessMap) {
      mat.metalness = 1;
      if (!mat.roughnessMap) mat.roughnessMap = mat.metalnessMap;
    }
    if (mat.roughnessMap) mat.roughness = 1;
  });
};

Étape 3 : Gestion de l’upload et mise à l’échelle automatique

Cette logique charge le fichier GLB, le centre à l’origine et normalise son échelle (entre 0,3 m et 2,0 m) afin de le préparer pour les environnements AR.

const [downloadInfo, setDownloadInfo] = useState(null);

const handleFileUpload = async (event) => {
  const file = event.target.files[0];
  if (!file) return;

  const url = URL.createObjectURL(file);
  const loader = new GLTFLoader();
  
  loader.load(url, async (gltf) => {
    const scene = new THREE.Scene();
    const model = gltf.scene;
    scene.add(model);

    // === Centrage et normalisation de l’échelle ===
    const box = new THREE.Box3().setFromObject(model);
    const center = box.getCenter(new THREE.Vector3());
    const size = box.getSize(new THREE.Vector3());
    model.position.sub(center);

    const maxDim = Math.max(size.x, size.y, size.z);
    if (maxDim > 2 || maxDim < 0.3) {
      const targetSize = maxDim > 2 ? 2 : 0.8;
      model.scale.setScalar(targetSize / maxDim);
    }

    // === Préparation et exportation ===
    const usdzFileName = file.name.replace(/\.(glb|gltf)$/, '') + '.usdz';
    await prepareSceneForUSDZ(scene);
    
    const exporter = new USDZExporter();
    const arrayBuffer = await exporter.parseAsync(scene, {
      maxTextureSize: 1024,
      quickLookCompatible: true,
      onlyVisible: true,
      includeAnchoringProperties: true,
    });

    // === Création de l’URL de téléchargement et mise à jour du state ===
    const blob = new Blob([arrayBuffer], { type: 'model/vnd.usdz+zip' });
    setDownloadInfo({
      url: URL.createObjectURL(blob),
      fileName: usdzFileName
    });
    
    URL.revokeObjectURL(url);
  });
};

Étape 4 : Interface utilisateur minimaliste (UI)

Voici la structure React utilisée pour l’outil de conversion. Elle utilise des éléments HTML standards ainsi qu’un input fichier caché déclenché via un événement click.

return (
  <div className="converter-card">
    <header>
      <h1>GLB to USDZ</h1>
    </header>

    <main>
      <div className="upload-zone" onClick={() => fileInputRef.current.click()}>
        <input 
          type="file" 
          ref={fileInputRef} 
          onChange={handleFileUpload} 
          accept=".glb,.gltf" 
          hidden 
        />
        <div className="status-content">
          <p>Click to upload GLB or GLTF file</p>
        </div>
      </div>

      {downloadInfo && (
        <div className="result-section">
          <a href={downloadInfo.url} download={downloadInfo.fileName} className="action-button primary">
             Download {downloadInfo.fileName}
          </a>
        </div>
      )}
    </main>
  </div>
);

Problèmes courants et solutions

Problème Solution
Le modèle est invisible après l’exportationAssurez-vous que les matériaux sont convertis en MeshStandardMaterial et utilisez quickLookCompatible: true
Le modèle apparaît trop grand ou trop petitCentrez toujours le modèle et normalisez son échelle (0,3 m à 2 m) avant l’exportation.
Les textures paraissent sombres ou incorrectesExécutez l’étape de parcours afin de corriger les valeurs de metalness et roughness avant de lancer l’exportation.
La taille du fichier est trop importanteRéduisez maxTextureSize à 768 ou 512 dans les options de l’exporteur.

Conclusion : Grâce à ce workflow, vous disposez désormais d’un pipeline minimal et prêt pour la production afin de convertir des modèles GLB en USDZ directement dans votre application web.

Interface Finale

Une fois le code terminé et le CSS ajouté, votre interface ressemblera à ceci :

Convertisseur GLB vers USDZ - Interface Finale