Algorithme d'emballage de texture

52

Qu'est-ce qu'un bon algorithme d'emballage de texture? Techniquement, le bin bin est dur , donc une heuristique est ce que je recherche vraiment.

deft_code
la source
Je supposais que vous utilisiez ceci pour optimiser les cartes UV, mais je suis curieux de savoir quelle est l'application.
Jonathan Fischoff
ftgles est une bibliothèque qui utilise opengl et freetype pour rendre les polices. Cependant, chaque glyphe est stocké dans sa propre texture. Je voudrais les emballer dans une texture.
deft_code

Réponses:

58

Pendant quelques mois, j'ai trouvé un meilleur algorithme d'emballage de texture pour un travail.

L'algorithme avec lequel nous avons commencé était simple. Recueillir tous les éléments d'entrée. Triez-les par le nombre total de pixels consommés, grand à petit. Disposez-les dans votre texture dans l'ordre des lignes, en ne testant que des éléments allant du pixel topleft au pixel vers le haut, en descendant d'une ligne et en répétant, en réinitialisant le pixel topleft après chaque placement réussi.

Vous devez soit coder en dur une largeur, soit proposer une autre heuristique à cet effet. Pour tenter de préserver la perpendicularité, notre algorithme commencerait à 128, puis augmenterait de 128 secondes jusqu'à obtenir un résultat qui ne soit pas plus profond que large.

Nous avions donc cet algorithme et j'ai décidé de l'améliorer. J'ai essayé une série d'heuristiques loufoques - essayer de trouver des objets qui s'emboîtent bien, en pondérant un tas de propriétés de remplissage d'espace souhaitées, en effectuant une rotation et un retournement. Après tout mon travail, littéralement trois mois de travail, j'ai fini par économiser 3% d'espace.

Ouais. 3%.

Et après que nous ayons exécuté notre routine de compression dessus, elle s’est finalement avérée plus volumineuse (ce que je ne peux toujours pas expliquer). Nous avons donc tout jeté à la poubelle et sommes revenus à l’ancien algorithme.

Triez les éléments et collez-les dans la texture dans l'ordre des lignes. Il y a votre algorithme. Il est facile de coder, rapide à exécuter et vous ne vous améliorerez pas beaucoup sans une quantité de travail incroyable. Ce travail ne vaut tout simplement pas la peine, à moins que votre entreprise ne compte au moins 50 personnes et probablement plus.

texte alternatif

Et en guise de remarque, je viens de mettre en œuvre cet algorithme (largeur fixe de 512 pixels) pour littéralement exactement la même application que vous êtes en train de faire (pas de fonction, mais des glyphes de type libre à rendu opengl.) Voici le résultat. Cela semble flou car le mien utilise l' algorithme de rendu de texte basé sur le champ de distance de Valve , qui tient également compte de l'espace supplémentaire entre les glyphes. De toute évidence, il ne reste plus beaucoup d’espace vide et c’est bien de ranger des choses dans des espaces ouverts.

Tout le code est sous licence BSD et disponible sur github .

ZorbaTHut
la source
J'ai regardé votre texture et je me suis dit: "Je suis sûr que notre empaqueteur de texture fait un peu mieux que ça". Et puis je suis allé le regarder, et j'ai réalisé que je l'avais cassé il y a quelque temps et que je ne l'avais pas remarqué (parce que, une fois que ça fonctionne, qui regarde les textures de sortie?) ... le bogue sinon :) (une fois le bogue corrigé, il a l'air très similaire - peut-être un peu mieux, mais c'est difficile de dire exactement. "aussi bon" est probablement la description la plus sûre).
JasonD
@JasonD, j'aimerais savoir ce que fait votre algorithme s'il obtient une meilleure sortie :) Même si la sortie est à peu près équivalente d'une manière différente.
ZorbaTHut
1
Merci pour algo description + l'échec admis + le code source. Très bonne publication.
Calvin1602
1
La raison pour laquelle il est devenu plus grand après la compression est probablement dû à l'algorithme de compression. Étant donné que la compression repose souvent sur le hachage et la recherche de modèles binaires, si l'algorithme peut identifier suffisamment de modèles, il en générera un grand nombre, ce qui peut entraîner une augmentation de la taille. un excellent moyen de le tester: il suffit de re-compresser un fichier à plusieurs reprises et, au bout du compte, il s'agrandira de plus en plus gros à cause du manque de motifs.
Hanna
1
Pour rechercher la dernière version du code d'emballage de ZorbaTHut (font_baker.cpp), vous pouvez trouver ici: github.com/zorbathut/glorp/blob/…
mems
20

La thèse de doctorat d'Andrea Lodi s'intitule Algorithmes pour les problèmes d'emballage et d'affectation de bacs à deux dimensions .
La thèse passe en revue certaines des formes les plus difficiles de ces problèmes. Heureusement, la texture est la version la plus simple. Le meilleur algorithme qu'il a trouvé s'appelle Touching Perimeter .

Pour citer la page 52:

L'algorithme, appelé Touching Perimeter (TPRF), commence par trier les éléments en fonction de la zone non croissante (rupture des liens en augmentant les valeurs min {wj, hj}), puis en les orientant horizontalement. Une borne inférieure L sur la valeur de solution optimale est ensuite calculée et L bacs vides sont initialisés. (La limite inférieure continue L0 définie dans la section précédente est évidemment valable pour 2BP | R | F également; de meilleures limites sont proposées par Dell'Amico, Martello et Vigo [56].) L'algorithme compile un élément à la fois, soit dans un bac existant, ou en initialisant un nouveau. Le premier article emballé dans une corbeille est toujours placé dans le coin inférieur gauche. Chaque article suivant est emballé dans une position dite normale (voir Christofix et Whitlock [41]), à savoir:
Le choix du bac et de la position d'emballage se fait en évaluant un score, défini comme le pourcentage du périmètre de l'article qui touche le bac et les autres articles déjà emballés. Cette stratégie favorise les modèles dans lesquels les éléments emballés ne «capturent» pas de petites zones, qui peuvent être difficiles à utiliser pour d'autres placements. Le score est évalué deux fois pour chaque position d’emballage candidate, pour les deux orientations d’item (si les deux sont réalisables), et la valeur la plus élevée est sélectionnée. Les égalités de score sont rompues en choisissant le bac ayant la zone la plus remplie. L'algorithme global est comme suit.

touching_perimeter:
  sort the items by nonincreaseing w,h values, and horizontally orient them;
  comment: Phase 1;
  compute a lower bound L on the optimal solution value, and open L empty bins;
  comment: Phase 2;
  for j := 1 to n do
     score := 0;
     for each normal packing position in an open bin do
        let score1 and score2 be scores with tow orientations;
        score := max{score,score1,score2};
     end for;
     if score > 0 then
        pack item j in the bin, position and orientation corresponding to score;
     else
        open a new bin and horizontally pack item j into i;
     end if;
  end for;
end;

Le document décrit également un algorithme permettant de déterminer la taille d’une texture optimale. Cela serait utile pour déterminer s’il est même possible de regrouper toutes les textures dans un atlas 1024x1024.

deft_code
la source
Cet algorithme suppose que les textures ont une forme rectangulaire, n'est-ce pas?
user1767754 le
17

Si quelqu'un est toujours intéressé, j'ai complètement réécrit la bibliothèque rectpack2D pour la rendre plus efficace.

Cela fonctionne en conservant un nombre std::vectord'espaces vides dans l'atlas, en commençant par une taille maximale initiale (généralement, la taille de texture maximale autorisée sur un GPU particulier), en scindant le premier espace vide viable et en enregistrant les divisions dans le vecteur.

La percée en matière de performances a consisté simplement à utiliser un vecteur, au lieu de conserver un arbre entier, comme auparavant.

La procédure est décrite en détail dans le fichier README .

La bibliothèque est sous MIT, donc je suis heureux pour vous si vous le trouvez utile!

Exemple de résultats:

Les tests ont été effectués sur un processeur Intel (R) Core (TM) i7-4770K à 3,50 GHz. Le binaire a été construit avec clang 6.0.0, en utilisant un commutateur -03.

Sprites de jeu arbitraires + glyphes japonais: 3264 sujets au total.

Durée: 4 millisecondes
Pixels perdus: 15538 (0,31% - équivalent d'un carré de 125 x 125)

Sortie (2116 x 2382):

3

En couleur:
(le noir est de l'espace perdu)

4

Glyphes japonais + quelques sprites GUI: 3122 sujets.

Durée: 3,5 - 7 ms
Pixels gâchés: 9288 (1,23% - équivalent d'un carré de 96 x 96)

Sortie (866 x 871):

5

En couleur:
(le noir est de l'espace perdu)

6

Patryk Czachurski
la source
2
J'ai téléchargé votre code. Il suffit de lire les définitions de structure: QUELLE EST CETTE MONSTROSITÉ?! Cela ressemble à du code golf.
akaltar
3
Pourtant, ça marche, et ça a aidé, alors merci. Je ne voulais pas être impoli.
akaltar
Je ne sais pas trop pourquoi je saute cette réponse car elle est encore plus rapide et plus performante que mon propre algorithme O_O merci
GameDeveloper
@akaltar J'imagine que j'étais encore en train d'apprendre la langue pendant ce temps :)
Patryk Czachurski
Approche assez simple, rapide à mettre en œuvre et qui donne de bons résultats, merci :)
FlintZA
5

Un bon algorithme heuristique peut être trouvé ici . Lorsque j’essayais quelque chose de similaire récemment, j’ai trouvé cette référence comme point de départ de la plupart des implémentations que j’ai vues.

Fonctionne particulièrement bien avec beaucoup d'objets de forme similaire de forme régulière ou avec un bon mélange d'images de petite taille et moins grandes. Le meilleur conseil pour obtenir de bons résultats est de ne pas oublier de trier vos entrées en fonction de la taille de l'image, puis de la classer du plus grand au plus petit, car les plus petites images se retrouveront dans l'espace situé autour des plus grandes. Comment vous faites ce tri pour vous et peut dépendre de vos objectifs. J'ai utilisé le périmètre plutôt que la surface comme approximation de 1er ordre car j'ai estimé que les images hautes + minces / courtes + larges (qui auraient une surface basse) sont en réalité très difficiles à placer plus tard dans un paquet, donc en utilisant le périmètre vous ces formes étranges vers l'avant de l'ordre.

Voici un exemple de visualisation de la sortie de mon packer sur un ensemble aléatoire d'images du répertoire de vidage d'images de mon site Web :). Sortie de l'emballeur

Les nombres dans les carrés sont les identifiants des blocs dans l’arbre, alors donnez-vous une idée de l’ordre des insertions. Le premier est l'ID "3" car c'est le premier nœud feuille (seules les feuilles contiennent des images) et a donc 2 parents).

        Root[0]
       /      \
   Child[1]  Child[2]
      |
    Leaf[3]
xan
la source
3

Personnellement, je viens d'utiliser un système avide de plus gros blocs qui s'adapte en premier. Ce n'est pas optimal, mais ça marche OK.

Notez que, si vous avez une quantité raisonnable de blocs de texture, vous pouvez rechercher de manière exhaustive le meilleur ordre, même si le problème est NP.

drxzcl
la source
3

Une chose que j’ai utilisée et qui fonctionne bien même pour les cartes UV irrégulières consiste à transformer le patch UV en masque bitmap et à conserver un masque pour la texture elle-même, en recherchant la première position du patch UV. J'ordonne les blocs en fonction d'une simple heuristique (hauteur, largeur, taille, peu importe), et j'autorise les rotations des blocs pour minimiser ou maximiser l'heuristique choisie. Cela donne un espace de recherche gérable pour la force brute.

Si vous pouvez alors effectuer plusieurs itérations, essayez plusieurs heuristiques et / ou appliquez un facteur aléatoire dans le choix de l'ordre et effectuez une itération jusqu'à l'expiration du délai imparti.

Avec ce schéma, vous obtiendrez de petits îlots UV dans les trous créés par les plus grands, et même dans les trous laissés dans les patchs UV eux-mêmes.

JasonD
la source
1

Nous avons récemment publié un script python qui compilera les textures dans plusieurs fichiers image d’une taille donnée.

Cité de notre blog:

"Bien que de nombreux conditionneurs soient disponibles en ligne, notre difficulté a été de trouver ceux qui pouvaient gérer un grand nombre d'images dans plusieurs annuaires. C'est ainsi que notre propre emballeur d'atlas est né!

Tel quel, notre petit script commencera dans le répertoire de base et chargera tous les fichiers .PNG dans un atlas. Si cet atlas est rempli, il en crée un nouveau. Ensuite, il essaiera d’intégrer le reste des images dans tous les atlas précédents avant de trouver une place dans le nouvel. De cette façon, chaque atlas est emballé aussi serré que possible. Les atlas sont nommés en fonction du dossier d'où proviennent leurs images.

Vous pouvez modifier assez facilement la taille de l’atlas (ligne 65), le format des images que vous voulez emballer (ligne 67), le répertoire de chargement (ligne 10) et le répertoire de sauvegarde (ligne 13), sans aucune expérience de Python. Petit désaveu, cela a été fusionné en quelques jours pour fonctionner spécifiquement avec notre moteur. Je vous encourage à demander des fonctionnalités, à commenter avec vos propres variations et à signaler toute erreur, mais toute modification du script aura lieu pendant mon temps libre. "

N'hésitez pas à consulter le code source complet ici: http://www.retroaffect.com/blog/159/Image_Atlas_Packer/#b

dcarrigg
la source
1

Il est assez facile de compresser les polices car toutes les textures de glyphes (ou la grande majorité d'entre elles) ont presque la même taille. Faites ce qui vous semble le plus simple et ce sera très proche de l’optimum.

L'intelligence devient plus importante lorsque vous stockez des images de tailles très différentes. Ensuite, vous voulez pouvoir compacter des espaces vides, etc. Même dans ce cas, un algorithme simple, tel que la recherche de l'ordre de la ligne de balayage décrite précédemment, produira des résultats très raisonnables.

Aucun des algues avancées n'est magique. Ils ne seront pas 50% plus efficaces qu'un simpel algo, et vous n'en obtiendrez pas d'avantages cohérents à moins d'avoir un nombre impressionnant de feuilles de texture. c'est parce que les petites améliorations apportées par de meilleurs algorithmes ne seront visibles que globalement.

Allez simple et passez à quelque chose où vos efforts seront mieux récompensés


la source
0

Si c'est spécifiquement pour les textures de polices, alors vous faites probablement quelque chose de non optimal mais simple et agréable:

Trier les caractères par taille, le plus grand en premier

Commencez à 0,0 Placez le premier caractère aux coordonnées actuelles, avancez de X, placez le suivant, répétez-le jusqu'à ce que nous ne puissions plus nous adapter

Remettez X à 0, avancez Y de la hauteur du plus grand caractère de la ligne et remplissez une autre ligne.

Répétez l'opération jusqu'à ce que nous n'ayons plus de caractères ou que nous ne puissions plus nous trouver dans une autre rangée.

bluescrn
la source