Construire un monde ouvert dans le navigateur, partie 17 : des animations qui n'avaient pas besoin de reciblage et une recherche de ressources en direct
Par Oleg Sidorkin, CTO et cofondateur de Cinevva
Vous découvrez la série ? Consultez le guide de la série. Il explique ce qu'est un spike et renvoie vers toutes les parties.
La partie 16 nous a donné un monde dans lequel placer des objets. Cette partie offre au joueur de meilleures choses à y faire : un ensemble d'animations digne d'un jeu de combat et un moyen d'importer dans le monde n'importe lequel des quelque mille modèles CC0 en saisissant une recherche.
262 clips, un squelette, trois corrections de reciblage
Ouvrir le spike 35 dans un nouvel onglet ↗ · Voir le code source
Le squelette du joueur est un squelette 3MIKE de style CC4 comportant 213 os, dont un rig facial de plus de 80 os et toutes les articulations des doigts. Les sources d'animation ne lui correspondent pas. La première version reciblait des clips de combat Mixamo sélectionnés à la main, mais le choix disponible était limité et la locomotion était maladroitement répartie entre Mixamo et quelques animations d'attente Kimodo au format BVH. La véritable avancée est arrivée lorsque nous avons remplacé la bibliothèque source par les deux packs Universal Animation Library de Quaternius : 262 clips au total sur un même mannequin cohérent de style UE5 à 65 os, avec des enchaînements à l'épée, du tir à l'arc, de l'escalade, des courses murales, des esquives, des réactions aux coups, des emotes et un ensemble de locomotion bien plus propre.
Il faut donc passer d'une source à 65 os à une cible à 213 os, sans noms d'os, orientations de pose en T ni proportions de membres en commun. Chaque clip est remappé en trois étapes : une table de correspondance des noms d'os, un alignement de la pose de référence afin que les deux rigs partagent une orientation de référence, puis une mise à l'échelle des pistes de position pour éviter que le rig source, plus petit, n'enfonce le personnage jusqu'aux genoux dans le sol. Pour y parvenir, nous avons dû traquer une série de bugs, chacun nous apprenant quelque chose de précis.
Le personnage ressortait excessivement tordu, chaque épaule et chaque coude pivotant d'environ 30° de trop. La cause était une différence de pose : la source UAL est fournie dans une véritable pose en T, tandis que la pose de référence de CC4 est une pose en A. Le système de reciblage supposait que les deux rigs partageaient la même pose de référence, de sorte que la différence entre les poses A et T s'ajoutait à chaque delta calculé image par image. La correction force la chaîne des bras de CC4 dans une véritable pose en T pour capturer la pose de référence avant d'effectuer le reciblage, ce qui permet de conserver de petits deltas.
Ensuite, le corps a cessé de se déplacer. Lors des projections en arrière, le haut du corps reculait, mais les pieds restaient plantés au sol. UAL place le déplacement sur l'os root, pas sur le bassin ; lire la position du bassin produisait donc un mouvement presque nul. La correction additionne root.position et pelvis.position, met à l'échelle la composante horizontale et écrit une seule piste de position des hanches. Ce faisant, nous avons découvert que les décalages verticaux comportaient une erreur d'environ 12 %, car nous avions utilisé le rapport global des proportions des membres pour l'axe Y, alors que la hauteur du bassin nécessite le rapport entre les hanches et le sol. Deux rapports pour deux axes : RL_BoneRoot.position provenait du mappage du root d'UAL, situé à
La plus petite correction était aussi la plus satisfaisante à regarder. Le personnage tenait l'épée avec des doigts mous, figés dans la pose de référence, car la table de correspondance ne contenait aucune entrée pour les doigts et que le système de reciblage n'écrit des pistes que pour les os mappés. L'ajout de 30 os de doigts — cinq doigts, trois segments, deux mains, en laissant de côté le quatrième os auxiliaire de l'extrémité fourni par UAL, qui ne déforme pas le maillage — a permis à la main de se refermer sur la poignée et de s'ouvrir lorsqu'elle la lâche.
Valider 262 clips sans en regarder 262
Il est impossible de vérifier visuellement une bibliothèque de 262 clips. Nous avons donc créé un test hors ligne de parité des trajectoires : un script Node sans interface graphique charge chaque pack, échantillonne le squelette source à 60 Hz, exécute le pipeline de reciblage, puis compare les positions et rotations de chaque os dans l'espace global à celles de la source après mise à l'échelle. La dérive maximale du bassin sur l'axe Y n'était que de 0,003 m. Les mains présentaient un décalage constant de 2,5°, d'abord interprété comme un problème de suivi des doigts. Mais un décalage constant correspond au delta de la pose de référence entre la main plate d'UAL et la pose de référence légèrement incurvée de CC4, et il reste inchangé pendant tout le clip. Les véritables erreurs d'animation apparaissent sous forme de dérives qui varient d'une image à l'autre. Une fois ce point compris, le test est devenu un contrôle de régression en une seule passe : si la dérive d'un clip cesse de respecter cette valeur de référence constante, une modification récente a cassé le reciblage.
Un mannequin de référence affiché côte à côte a rendu la partie visuelle du débogage décisive. Appuyer sur la barre oblique inverse affiche à côté du joueur le rig source auquel appartient le clip actuel. La question « cette épaule est-elle tordue ? » devient alors « la torsion existe-t-elle dans la source ou le reciblage l'a-t-il ajoutée ? ». Les tests numériques détectent les régressions, la référence visuelle révèle les erreurs de pose de référence que les chiffres ne font pas apparaître et, ensemble, ils ont mis fin aux suppositions.
Une fois le pipeline fiabilisé, le passage de la base WASD/saut/nage de Mixamo à UAL s'est résumé à une petite table d'alias : la machine à états finis continue d'utiliser des noms d'états génériques tels que idle et walk, qui sont associés aux noms des clips UAL lors de la lecture. La nage nécessitait des décalages de rig propres à chaque clip, car les poses de crawl et de nage sur place ancrent le bassin à des hauteurs anatomiques différentes. Nous immergeons donc le rig d'un demi-mètre pour la nage active et plus profondément pour la nage sur place, avec une transition fluide entre les deux à 5 Hz.
Saisissez un mot, obtenez un modèle
Ouvrir le spike 36 dans un nouvel onglet ↗ · Voir le code source
Le spike 34 nous a pris une journée pour constituer manuellement un pack CC0. La solution à long terme est un champ de recherche. Polyhaven publie environ 1 100 modèles CC0 derrière une API JSON permissive et un CDN déterministe. Ce spike relie l'ensemble du parcours — requête, miniatures, chargement et rendu — depuis une page statique sans étape de build, en environ 300 lignes de JavaScript natif auxquelles s'ajoute three.js.
Au démarrage, la page récupère une fois l'intégralité du catalogue, soit environ 600 Ko. La recherche repose entièrement sur un score calculé côté client — le nom prime sur l'identifiant, qui prime sur la catégorie, qui prime sur l'étiquette — avec un délai anti-rebond de 120 ms, puis affiche les 60 meilleures cartes. Les miniatures sont chargées à la demande au moyen d'un IntersectionObserver, afin que la saisie ne déclenche pas 60 requêtes simultanées. La partie intéressante est le chargement. Le point d'accès aux fichiers de Polyhaven expose du glTF multifichier, et non du GLB, avec des textures partagées entre les résolutions et réparties dans des fichiers séparés. Il renvoie également une table include associant chaque chemin relatif à une URL absolue du CDN. Plutôt que de télécharger et modifier nous-mêmes le JSON, nous transmettons cette table à LoadingManager.setURLModifier, appelé pour chaque dépendance requise par le chargeur — le fichier .bin et chaque texture — afin de la résoudre via le CDN. Un clic, un seul fichier en apparence. L'API comme le CDN définissent des règles CORS permissives, vérifiées avec curl avant d'écrire le moindre code client : aucun proxy n'est donc nécessaire. Les matériaux PBR s'affichent correctement avec RoomEnvironment et les valeurs par défaut du tone mapping ACES, sans correction propre à chaque ressource. Les textures 1k maintiennent la taille d'un modèle courant entre 2 et 5 Mo, au lieu de 20 à 40 Mo en 4k.
Une passe WASM de meshoptimizer complète l'ensemble avec un réducteur non destructif : chaque maillage conserve un clone de sa géométrie d'origine, et toute modification du ratio reconstruit un tampon d'indices à partir de ce clone au lieu d'appliquer des simplifications cumulatives. Les géométries à plusieurs matériaux sont simplifiées groupe par groupe, puis geometry.groups est reconstruit afin que les emplacements de matériaux ne soient pas fusionnés. Un fauteuil passe ainsi de 5 626 triangles en qualité maximale à 2 812 à mi-résolution.
Technologies abordées dans ce chapitre
Reciblage de squelette avec alignement de la pose de référence. Transférer une animation d'un squelette à un autre lorsque les noms des os, les proportions et les poses de référence diffèrent nécessite trois corrections : une table de correspondance des noms d'os, un alignement de la pose de référence afin que les deux rigs partagent la même orientation de référence — en forçant la chaîne des bras de la pose en A de la cible à adopter la pose en T de la source — et une mise à l'échelle des pistes de position. Une différence de pose ajoute la rotation entre A et T à chaque delta calculé image par image, ce qui double la rotation des articulations. Les os auxiliaires non mappés doivent être ignorés, car un os racine situé à
Deux rapports d'échelle pour un seul rig. Le déplacement horizontal utilise le rapport global des proportions des membres — c'est-à-dire la taille générale du squelette — tandis que le décalage vertical du bassin utilise le rapport entre les hanches et le sol root, et non sur le bassin. Les deux doivent donc être additionnés dans une seule piste de position des hanches afin de préserver le mouvement dans les clips de projection, d'escalade et de locomotion.
Test hors ligne de parité des trajectoires. Un script sans interface graphique échantillonne le squelette source à 60 Hz, exécute le pipeline de reciblage, puis compare les transformations globales de chaque os à celles de la source. Un décalage constant d'une image à l'autre est le delta inoffensif de la pose de référence, tandis qu'une dérive variable indique une véritable erreur. Le test devient ainsi un contrôle de régression qui se déclenche lorsqu'une modification supprime ou déforme une piste. L'isolation des GLB pack par pack — un nouveau chargement libéré avant de passer au suivant — évite les conflits de cache pendant l'ensemble du test.
LoadingManager.setURLModifier pour les graphes glTF d'un CDN. Lorsqu'un CDN fournit un glTF sous la forme d'un graphe d'URI relatives accompagné d'une table d'inclusion — chemin relatif vers URL absolue — setURLModifier résout via le CDN chaque dépendance demandée par le chargeur sans réécrire le JSON. Une distribution multifichier et multirésolution devient ainsi un chargement en un seul clic. Libérer la géométrie, les matériaux et les textures de chaque modèle précédent avant de charger le suivant évite l'accumulation de centaines de Mo de mémoire GPU au cours d'une session de navigation.
Simplification non destructive des maillages. Conserver un clone de la géométrie d'origine de chaque maillage et reconstruire uniquement le tampon d'indices pour chaque ratio de simplification permet des modifications rapides tout en évitant les dégâts cumulatifs provoqués par des simplifications répétées. Traiter séparément chaque portion de geometry.groups, puis reconstruire les groupes, préserve l'affectation des différents matériaux. Consultez LOD et meshoptimizer pour découvrir comment cette méthode alimente le LOD fondé sur la distance.
Partie 17 sur 29. Précédent : Partie 16 — Une structure pour un monde qui ne cesse de grandir Suivant : Partie 18 — Un pinceau de dispersion au placement digne d'une IA Guide de la série : /fr/blog/2026-02-25-open-world-browser-series-guide