D’où viennent les trous dans les maillages générés
Par Oleg Sidorkin, CTO et cofondateur de Cinevva

J’ai généré un oiseau avec TRELLIS.2, je lui ai appliqué un rig, et dès que l’animation s’est lancée, des trous sont apparus sous les deux ailes. De fines ouvertures lumineuses permettaient de voir directement à travers le corps. Nous avons d’abord accusé le système de rigging, qui avait bien son propre problème, mais les trous étaient déjà présents dans le maillage avant même qu’un os ne le touche. Cet article retrace l’enquête qui a suivi : ce que sont réellement ces trous, les neuf solutions qui n’ont pas fonctionné, pourquoi elles ont échoué et celle qui a réussi. J’inclus volontairement les fausses pistes. La plupart sont les solutions évidentes que l’on essaierait en premier, et chacune nous a appris quelque chose dont dépendait le correctif final.
Première étape : se mettre d’accord sur ce qu’est un trou
Notre vérificateur de maillage signalait zéro arête frontière, et un test de parité des rayons indiquait que la surface était fermée. J’ai donc dit à l’équipe que le maillage était propre. Ce n’était pas le cas, et la personne qui avait entouré les fentes en rouge avait raison d’insister. Un maillage peut être fermé tout en comportant des tunnels. La seule mesure qui les détecte est la caractéristique d’Euler, V moins E plus F, qui donne le nombre d’anses par composante connexe. L’oiseau en avait 69. Chaque paroi de l’export était doublée : une fente dans la paroi extérieure reliée à la cavité située derrière constitue donc une anse, et non une ouverture. Aucun outil recherchant des arêtes ouvertes ne pourra jamais la détecter.
Deuxième leçon, plus embarrassante : j’ai passé un certain temps à sonder les pixels sombres pour trouver les trous, parce que les trous sont sombres, jusqu’à ce qu’on me dise de commencer par regarder l’image. Les trous étaient lumineux. C’étaient de fines portions de la texture verte de la paroi opposée, visibles à travers un pli gris. Un algorithme que vous n’avez pas dirigé vers la bonne chose ne vous apprend rien, et le moyen le moins coûteux de bien le diriger reste de regarder.
Deuxième étape : trouver l’étape qui les crée
TRELLIS.2 construit un maillage en trois étapes. Un décodeur produit une surface brute à une résolution de 1024 au cube. Une passe de dual contouring à bande étroite la remaille. Un décimateur la réduit ensuite à cent mille faces, puis la texture est précalculée. Nous avons exporté la géométrie après chaque étape et projeté à chaque fois le même faisceau de rayons à travers l’emplanture de l’aile.
Tout commence dans la surface brute. C’est un ensemble de feuilles ouvertes : 266 242 boucles frontières, dont la plupart correspondent à un seul quadrilatère manquant, et 65 371 arêtes non-manifold. Au niveau de l’emplanture de l’aile, un tiers des rayons de test la traversaient directement. Le remplissage de trous exécuté ensuite ne referme presque rien : 144 faces sur cinq millions. Le remaillage travaille sur un champ de distance non signé, parce qu’il n’a pas le choix. Il ne dispose d’aucune notion d’intérieur ou d’extérieur. Il enveloppe donc chaque feuille d’une fine coque décalée. C’est pourquoi chaque paroi est doublée, avec un espacement d’environ quatre voxels, et c’est ainsi que chaque bande poreuse devient un pli traversé d’une fente. La décimation aggrave ensuite le problème de manière aléatoire : le même maillage brut, décimé deux fois, a produit respectivement 69 et 107 tunnels.

Cette carte a constitué notre premier véritable indice. La porosité n’est pas aléatoire. Elle apparaît à chaque jonction entre deux parties, et nulle part ailleurs.
Les solutions qui n’ont pas fonctionné
Nous les avons essayées à peu près dans l’ordre de leur évidence.
| tentative | résultat |
|---|---|
| résolution de 1536 au lieu de 1024 | pire sur trois des cinq modèles de test. Une résolution supérieure restitue davantage de contacts fins, chacun créant une nouvelle jonction |
| la chaîne de nettoyage intégrée | davantage d’arêtes non-manifold, pas moins |
| remplissage de chaque petit trou avec MeshLib | 266 000 boucles remplies, emplanture de l’aile inchangée. La peau n’y avait jamais été générée, il n’y avait donc aucune boucle à remplir |
| remaillage signé à partir d’un nombre d’enroulement | fait éclater le maillage en milliers de composantes. La sortie brute ne possède aucun intérieur auquel attribuer un signe |
| bande de contouring plus large, de 2 et 3 voxels | moins de tunnels au total, mais la fente s’élargit au lieu de se rétrécir. Les petits tunnels fusionnent pour en former de grands |
| fermeture morphologique de la structure voxelisée | solidifie la coque ; le décodeur produit ensuite une surface dans chaque voxel intérieur, quatre fois plus de faces et 969 tunnels |
| remaillage voxel global du maillage final | genre 0 et fente refermée. Mais aussi toutes les arêtes de plumes adoucies. Rejeté au premier regard, à juste titre |
| modification du seuil de croisement du décodeur | de 65 000 à 58 000 arêtes ouvertes. Du bruit |
| ajout des voxels voisins manquants | 560 cas dans tout l’oiseau. Du bruit |

La fermeture globale mérite qu’on s’y attarde, car c’est la première solution qu’un modeleur essaierait : effectuer un remaillage voxel à une taille légèrement supérieure à celle de l’interstice. Topologiquement, cela fonctionne. Mais c’est aussi une représentation mensongère du modèle. Le générateur a produit des bords de plumes nets, que nous sacrifierions pour obtenir une emplanture d’aile fermée. Deux des neuf tentatives ont réduit le nombre de tunnels, et toutes deux ont été rejetées dès que quelqu’un a regardé l’oiseau plutôt que le chiffre.
Lecture du code
À ce stade, la seule démarche honnête consistait à arrêter de traiter le décodeur comme une boîte noire et à lire comment sa sortie était transformée en maillage. TRELLIS.2 utilise une représentation appelée O-Voxel, et le code d’extraction est court. Chaque voxel actif contient un sommet dual et trois logits, un par arête de grille partant de son coin, chacun signifiant « la surface traverse cette arête ». Un logit supérieur à zéro produit un quadrilatère reliant les quatre voxels autour de cette arête. Si l’un des quatre manque, le quadrilatère est silencieusement supprimé. C’est tout. Il n’existe aucun champ de signes, aucune occupation, aucune notion d’intérieur ou d’extérieur dans le modèle.
Nous avons modifié le décodeur pour extraire ces logits avant l’application du seuil, sur le même oiseau, puis nous les avons comptés.
Voisins manquants : 560 sur 2,7 millions de voxels. Logits proches de zéro : 8 600 sur 8 millions. Ni les uns ni les autres ne sont à l’origine du problème. Celui-ci apparaît lorsque l’on examine les quatre arêtes entourant chaque face de la grille. Une surface fermée traverse la frontière d’une face un nombre pair de fois. Sur 126 542 faces, soit 2,3 % d’entre elles, les quatre indicateurs sont incohérents, et chaque face impaire constitue par définition une arête ouverte ou non-manifold. Et cette incohérence est exprimée avec assurance. L’indicateur isolé d’une face ne comptant qu’un seul croisement possède un logit de +10, tandis que ses voisins sont à -4. Le coût médian pour inverser un indicateur et restaurer la parité est de 2,3 logits. Il ne s’agit pas d’un seuil légèrement décalé. Le modèle est certain de l’existence d’une surface qui ne peut pas se fermer.

Nous avons ensuite mesuré la bande elle-même, exactement au point de la surface que notre caméra fixe observait depuis une semaine. Dans un rayon de douze voxels autour de ce point, il y a 2,1 fois plus de voxels actifs que n’en nécessiterait une seule feuille plane. Sur le poitrail, à titre de comparaison, ce rapport est de 1,2. Le dessous de l’aile et le corps sont deux peaux qui occupent la même couche d’un voxel. La représentation ne dispose que d’un sommet par voxel et ne peut donc pas contenir les deux. Pendant l’entraînement, la vérité terrain d’un tel voxel est une moyenne des moindres carrés des deux peaux, accompagnée d’indicateurs « n’importe quel croisement », ce qui est intrinsèquement incohérent. Le modèle a donc fidèlement appris à produire un peigne à cet endroit. Le remaillage enveloppe ce peigne dans une plaque, et cette plaque comporte des tunnels.
La largeur de la bande découle de la géométrie : environ un voxel divisé par la tangente de l’angle de contact. Deux surfaces qui se rencontrent à quatre-vingt-dix degrés produisent un pli d’un voxel et ne posent aucun problème. Une aile plaquée contre un corps, plume contre plume, des doigts de patte posés au sol : toutes ces configurations produisent une bande large de dix à quinze voxels. Voilà l’explication de la carte de porosité.
J’ai bien essayé la réparation théoriquement correcte : déduire pour chaque coin un signe intérieur/extérieur expliquant au mieux les indicateurs, puis recalculer ceux-ci. Cela ne peut pas fonctionner. Le champ de signes n’existe que sur la coque d’un voxel produite par le décodeur, et toute extension au-delà de cette coque diverge. Douze itérations ont doublé le nombre de voxels en produisant des données inutilisables.
Ce que ferait un modeleur
Un modeleur ne laisserait jamais deux peaux séparées par un interstice infime. Il fusionnerait l’aile avec le corps et conserverait un pli. La fermeture globale fait cela partout, et c’est précisément le problème. Il faut donc ne le faire qu’aux endroits où se trouvent les deux peaux.
Le détecteur repose sur une idée simple. Comptez les voisins actifs de chaque voxel dans un rayon de quatre voxels. Sur une feuille unique, ce nombre est identique partout et correspond à la médiane. Lorsque deux feuilles occupent la même couche, il est environ deux fois plus élevé. Marquez tout ce qui dépasse 1,5 fois la médiane, étendez la zone marquée d’un voxel, puis placez un cube unitaire à chaque emplacement. L’espace entre les peaux devient solide, la passe de contouring ne voit plus qu’une paroi et rien d’autre sur le modèle n’est modifié.

Nous avons d’abord testé cette approche sur une zone recadrée, avec MeshLib à la place du contouring GPU. Cinquante-trois composantes et dix-neuf anses sont devenues deux composantes et cinq anses. Nous l’avons ensuite intégrée au véritable pipeline, entre le décodeur et l’export, puis avons fait passer le même oiseau par le véritable système de remaillage et le véritable précalcul de texture.

| tunnels | arêtes non-manifold | rabat à l’emplanture de l’aile | trou dans le dos | |
|---|---|---|---|---|
| tel quel | 79.5 | 318 | présent | présent |
| remplissage, rayon 3 | 84.5 | 97 | disparu | disparu |
| remplissage, rayon 4 | 72 | 38 | disparu | disparu |
Le rabat a disparu. Un deuxième trou dans le dos de l’oiseau, au niveau de la même jonction vue de derrière, que je ne cherchais même pas à corriger, a lui aussi disparu. Les plumes restent séparées, les doigts de patte restent séparés et la texture est précalculée normalement, car les cubes se trouvent à l’intérieur de la coque, là où le volume d’attributs contient déjà des données. Le traitement ajoute six secondes à une tâche de quatre minutes. Il est maintenant activé par défaut, avec un rayon de quatre.

Ce que ce correctif ne résout pas, et ce qu’il nous apprend
Le nombre de tunnels a à peine changé, et je tiens à expliquer honnêtement pourquoi. Les tunnels restants se trouvent à l’extrémité des plumes et des doigts de patte, là où une partie fine se termine et où la double paroi se pince pour se refermer. Ils sont petits, invisibles et résultent d’un mécanisme différent. Le remplissage cible les contacts entre les différentes parties, précisément là où se trouvaient les trous visibles. La vraie correction doit se faire en amont. Un décodeur qui prédit les signes par sommet au lieu d'indicateurs par arête serait cohérent en matière de parité par construction. C'est ainsi que fonctionnait la génération précédente de ce modèle, et c'est pourquoi ses maillages étaient étanches. Cela nécessite de modifier l'entraînement, ce n'est pas quelque chose que nous pouvons corriger lors de l'inférence. En attendant, deux enseignements pratiques découlent de la formule des bandes. Si vous générez un personnage, une pose où les membres sont écartés du corps produira un maillage plus propre qu'une pose où ils sont plaqués contre lui. Et si vous développez à partir d'un générateur de ce type, évaluez vos maillages à l'aide de la caractéristique d'Euler et examinez le résultat de vos propres yeux avant de vous fier à un chiffre. Le chiffre disait que tout était propre. Il s'est trompé deux fois.
Le décodeur instrumenté, les exports des différentes étapes, l'analyse de parité et le rebouchage font désormais tous partie de notre pipeline. Ainsi, le prochain modèle qui produira des trous recevra le même traitement en quelques minutes plutôt qu'en une semaine.