📍 Article principal de la série : Three.js, React Three Fiber et WebGPU en 2026 : 3D temps réel sur le web
Introduction
Un projet 3D web sérieux finit toujours par charger des modèles externes : un personnage exporté de Blender, un produit modélisé par un freelance, un asset acheté sur Sketchfab. Le format de référence en 2026 reste le glTF 2.0, défini par le Khronos Group et standardisé par ISO/IEC 12113:2022. Mais charger un fichier glTF brut, c’est s’exposer à des téléchargements de plusieurs dizaines de mégaoctets sur des connexions médiocres. Ce tutoriel montre comment réduire un modèle de 50 Mo à moins de 3 Mo en combinant la compression Draco pour la géométrie et l’extension Meshopt pour le streaming, puis comment charger ce modèle optimisé dans React Three Fiber via le hook useGLTF de Drei. La cible est un Time To First Mesh (le délai entre la première requête et l’affichage du modèle) inférieur à deux secondes sur une connexion 4G typique.
Prérequis
- Un projet R3F v9 fonctionnel
- Node.js 22 LTS pour faire tourner les outils CLI de compression
- Un modèle glTF ou GLB de test (un free asset depuis Sketchfab ou les samples Khronos suffit)
- Notions de base sur les formats de fichier 3D et la compression
- Temps estimé : 40 à 60 minutes
Étape 1 — Comprendre la différence entre glTF, GLB, Draco et Meshopt
Avant de toucher la moindre commande, il faut clarifier quatre concepts qu’on entend souvent mélanger. glTF est le format de scène : un fichier .gltf JSON qui décrit la hiérarchie d’objets, les matériaux, les animations, plus des fichiers binaires .bin contenant les buffers de géométrie, plus les textures externes. GLB est la version concaténée : tout est packé dans un seul fichier binaire, ce qui simplifie le déploiement.
Draco est un codec de compression de géométrie développé par Google. Il s’applique aux buffers de positions, normales, UV et indices, et réduit typiquement la géométrie de 5 à 15 fois. Le décodage se fait côté client via un module WebAssembly. Meshopt est une autre approche, développée par Arseny Kapoulkine : elle compresse moins agressivement que Draco mais autorise un streaming progressif et un décodage plus rapide. Les deux sont compatibles avec glTF via des extensions standard KHR_draco_mesh_compression et EXT_meshopt_compression, documentées dans le registre d’extensions glTF.
En pratique, la règle de choix est simple : Draco pour les modèles statiques où la taille de fichier prime, Meshopt pour les modèles animés ou très détaillés où le décodage doit être rapide. Beaucoup de pipelines combinent les deux selon le type d’asset.
Étape 2 — Installer gltf-transform et les outils CLI
L’outil de référence pour manipuler des fichiers glTF en ligne de commande est gltf-transform, maintenu par Don McCurdy. Il expose un CLI complet et une API JavaScript pour automatiser les pipelines de build. On l’installe globalement pour pouvoir l’invoquer depuis n’importe où.
npm install -g @gltf-transform/cli
gltf-transform --help
La commande --help liste les sous-commandes disponibles : inspect pour analyser un modèle, draco pour appliquer la compression Draco, meshopt pour Meshopt, uastc et etc1s pour les textures KTX2, et bien d’autres. Si la commande retourne une erreur de PATH, c’est que le dossier global de npm n’est pas dans la variable d’environnement ; npm config get prefix donne le chemin à ajouter. On peut aussi utiliser npx @gltf-transform/cli pour éviter l’installation globale.
Étape 3 — Inspecter le modèle d’origine
Avant toute compression, on mesure ce qu’on a. La sous-commande inspect produit un rapport détaillé : nombre de meshes, de matériaux, de vertices, taille des buffers, taille des textures. C’est la base de référence pour évaluer le gain après compression.
gltf-transform inspect modele-original.glb > rapport-avant.txt
ls -lh modele-original.glb
La sortie contient une table par catégorie : scenes, nodes, meshes, materials, textures, animations. La colonne size indique la taille des buffers associés. Pour un modèle de personnage typique, on voit souvent 60% du poids dans les textures, 30% dans la géométrie, et le reste dans les animations et la structure. Cette répartition oriente la stratégie : si les textures dominent, on prioritise KTX2 ; si la géométrie domine, Draco ; si les deux, on combine.
Étape 4 — Appliquer la compression Draco
La compression Draco se fait avec une seule commande qui prend le fichier source et produit un fichier optimisé. On ajuste deux paramètres : --method edgebreaker (l’algorithme par défaut, le plus efficace pour la plupart des cas) et --quantize-position 14 (la précision en bits des positions ; 14 est un bon compromis qualité/taille).
gltf-transform draco modele-original.glb modele-draco.glb \
--method edgebreaker \
--quantize-position 14 \
--quantize-normal 10 \
--quantize-color 8 \
--quantize-texcoord 12
Sur un modèle de personnage de 12 Mo, on obtient typiquement un fichier de 2 à 3 Mo après Draco. La commande ls -lh modele-draco.glb confirme la taille. Une vérification visuelle dans gltf-viewer ou gltf.report permet de s’assurer qu’aucune surface n’a été dégradée — un quantize trop agressif sur les normales produit des artefacts visibles dans les zones de spéculaire.
Étape 5 — Combiner avec Meshopt pour le streaming
Pour les modèles très lourds qu’on veut afficher progressivement, Meshopt complète Draco. On l’applique après ou à la place selon la stratégie. Pour un asset critique chargé à la demande, l’enchaînement Draco + textures KTX2 suffit. Pour un asset volumineux qu’on veut streamer, Meshopt seul est souvent préférable parce qu’il décode plus vite.
gltf-transform meshopt modele-original.glb modele-meshopt.glb \
--level high
Le niveau high applique la compression maximale ; medium et low sont plus légers et plus rapides à décoder. La taille obtenue est typiquement 1.5 à 2 fois plus grande qu’avec Draco mais le décodage côté navigateur est environ 3 fois plus rapide. Pour départager les deux dans un cas concret, on mesure le Time To First Mesh sur la cible de production réelle, pas en local.
Étape 6 — Optimiser les textures avec KTX2
KTX2 est le format de texture moderne supporté par Three.js. Il combine une compression Basis Universal côté fichier avec un transcodage à la volée vers le format GPU natif (BC7 sur Windows, ASTC sur ARM, ETC2 sur Android). Le résultat est un fichier 5 à 10 fois plus léger qu’un PNG, et qui s’upload sur le GPU sans conversion CPU intermédiaire.
gltf-transform uastc modele-draco.glb modele-final.glb \
--level 4 --rdo 4 --zstd 18
Cette commande encode toutes les textures du modèle en UASTC (la variante haute qualité de KTX2/Basis), puis applique une seconde passe de compression Zstandard. Sur un modèle dont les textures pèsent 8 Mo en PNG, on descend typiquement à 1.2 Mo. La qualité visuelle reste excellente pour les textures de couleur ; pour les normal maps et roughness maps, on préfère parfois le mode ETC1S (gltf-transform etc1s) qui est plus tolérant aux artefacts dans ces canaux non-perceptifs.
Étape 7 — Charger le modèle compressé dans R3F
Côté application, le hook useGLTF de Drei gère automatiquement le décodage Draco et Meshopt si on lui fournit les chemins des décodeurs WASM. La pratique courante en 2026 est d’utiliser les copies hébergées par Three.js sur jsDelivr, ce qui évite de bundler 200 Ko de WASM par décodeur dans son propre app.
// src/components/Model.tsx
import { useGLTF } from '@react-three/drei'
useGLTF.preload('/models/modele-final.glb')
export default function Model() {
const { scene } = useGLTF('/models/modele-final.glb')
return <primitive object={scene} />
}
L’appel preload démarre le téléchargement et le décodage dès que le fichier est importé, avant même que le composant ne soit monté. Quand React rend le composant, le modèle est typiquement déjà prêt et l’apparition est instantanée. Si le navigateur n’a pas eu le temps de finir, R3F suspend via React Suspense, ce qui demande d’envelopper la scène dans un <Suspense fallback={...}> pour afficher un loader pendant l’attente.
Étape 8 — Configurer les décodeurs Draco et Meshopt
Drei utilise des décodeurs par défaut hébergés sur jsDelivr, mais on peut les surcharger pour pointer vers des copies locales et améliorer la latence ou éviter les requêtes externes en environnement entreprise. La configuration se fait via useGLTF.setDecoderPath au démarrage de l’application.
// src/main.tsx ou un fichier d'init
import { useGLTF } from '@react-three/drei'
useGLTF.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.7/')
Cette ligne pointe Drei vers les décodeurs Draco hébergés par Google. Pour Meshopt, le module WASM est bundlé directement avec gltf-transform et n’a pas besoin de configuration. On vérifie le bon fonctionnement en ouvrant le panneau Network des DevTools : on doit voir le téléchargement du draco_decoder.wasm au moment du chargement du premier modèle Draco. Sa taille est d’environ 200 Ko, mais elle est mise en cache navigateur dès la première visite, donc transparent pour l’utilisateur récurrent.
Étape 9 — Mesurer le Time To First Mesh
L’optimisation n’a de sens que si on la mesure. On instrumente l’apparition du modèle pour connaître son délai exact depuis le début du chargement de la page. Cette mesure devient l’indicateur de référence pour valider les optimisations futures.
import { useGLTF } from '@react-three/drei'
import { useEffect } from 'react'
export default function ModelTimed({ url }: { url: string }) {
const { scene } = useGLTF(url)
useEffect(() => {
const t = performance.now()
console.log(`[ttfm] ${url} ready at ${t.toFixed(0)}ms`)
}, [url, scene])
return <primitive object={scene} />
}
Le useEffect ne s’exécute qu’une fois que scene est défini, c’est-à-dire après le décodage complet du modèle. La valeur loggée est le temps écoulé depuis le démarrage du JavaScript de la page. On compare cette valeur entre le modèle non compressé, le modèle Draco, et le modèle Draco + KTX2 : le gain doit être substantiel à chaque étape, et négligeable seulement quand on a atteint la limite réseau.
Erreurs fréquentes
| Erreur | Cause | Solution |
|---|---|---|
DRACOLoader: No DRACOLoader instance provided |
Décodeur non configuré | Appeler useGLTF.setDecoderPath au démarrage |
| Modèle invisible mais pas d’erreur | Échelle ou position hors de la frustum caméra | Inspecter scene.scale et scene.position, recadrer la caméra |
| Textures apparaissent comme des blocs gris | KTX2 sans le décodeur Basis configuré | Charger KTX2Loader avec le path WASM Basis |
| Animations skeletales déformées après Draco | Quantification trop agressive sur les attributs génériques (poids de skinning, joints) | Augmenter --quantize-generic (par défaut 12) ou retirer la compression Draco pour les meshes skinnés et préférer Meshopt |
| Surcharge mémoire après plusieurs chargements | Modèles non disposés au démontage | Appeler useGLTF.clear(url) dans le cleanup |
Tutoriels frères
- Démarrer React Three Fiber v9 avec Vite et TypeScript
- Déployer une scène 3D : KTX2, glTF compressé et CDN statique
Sur un angle proche
- 🔝 Retour au guide principal : Three.js, React Three Fiber et WebGPU en 2026
- gltf-transform — documentation officielle
- glTF — registre d’extensions Khronos
- Drei — useGLTF
- meshoptimizer — code source et benchmarks
FAQ
Quelle taille viser pour un modèle 3D web ?
Tout dépend du contexte. Pour un produit affiché dans une fiche e-commerce, viser sous 2 Mo total (géométrie + textures). Pour un visualiseur architectural, on peut monter à 10-15 Mo en streamant progressivement. Pour un jeu web, c’est souvent une dizaine de Mo par scène, chargés en arrière-plan pendant qu’une intro joue.
Draco ou Meshopt, lequel choisir par défaut ?
Draco pour les assets statiques pesés au gramme (e-commerce, configurateur produit). Meshopt pour les assets animés ou les très grandes scènes streamées. Le combo Draco + KTX2 reste la combinaison la plus universelle pour démarrer.
Faut-il optimiser dans Blender avant export ?
Oui pour la topologie et les normales (decimate, smooth shading), non pour la compression — c’est gltf-transform qui s’en charge. Le pipeline propre est : modélisation Blender → export glTF → optimisation gltf-transform → fichier final.
Le décodage Draco est-il lourd pour le CPU ?
Modérément. Sur un modèle de 3 Mo Draco, le décodage prend typiquement 100 à 300 ms sur un mobile mid-range. C’est invisible pour l’utilisateur si on déclenche le chargement avant l’affichage. Sur des très gros modèles ou des appareils faibles, Meshopt est préférable.
Comment versionner les modèles compressés dans le dépôt Git ?
Stocker les sources non compressées hors Git (Git LFS, S3, Drive d’équipe) et placer dans le dépôt seulement les versions optimisées prêtes à servir. Le pipeline de compression vit dans un script npm run build:assets qu’on lance à la demande quand un asset change. Cette discipline évite que le dépôt explose en taille.
Three.js supporte-t-il les fichiers FBX, OBJ, USDZ ?
Oui via des loaders dédiés (FBXLoader, OBJLoader, USDZLoader). En production web, glTF reste massivement préférable parce qu’il est conçu pour le web : compact, streamable, supporté nativement par les outils. FBX et OBJ sont utiles surtout pour ingérer des assets venus d’autres pipelines.
Comment automatiser la compression dans la CI ?
On scripte la commande gltf-transform dans un fichier scripts/optimize-assets.mjs qui parcourt un dossier raw-assets/ et produit le résultat dans public/models/. Le script s’invoque via npm run build:assets avant npm run build. Sur un runner GitHub Actions Ubuntu, l’installation des outils prend une dizaine de secondes, la compression elle-même quelques secondes par modèle. On met en cache le dossier raw-assets/ par hash pour éviter de recompresser à chaque commit.
Comment gérer les modèles soumis par des utilisateurs finaux ?
Quand l’application accepte des modèles uploadés (configurateur, marketplace), la compression doit s’exécuter côté serveur après upload, pas dans le navigateur du visiteur. Une queue de jobs Node qui traite chaque upload via gltf-transform, vérifie la taille maximale, valide l’absence d’extensions douteuses, puis stocke le résultat dans un bucket. Cette discipline protège les autres visiteurs contre des modèles mal optimisés et garantit une qualité de service homogène quelle que soit la source des assets.