Algorithmus zum Packen von Texturen


52

Was ist ein guter Algorithmus zum Packen von Texturen? Technisch ist Verpackung ist NP-schwer , so eine Heuristik ist , was ich wirklich bin nach.


Ich bin davon ausgegangen, dass Sie dies für die Optimierung von UV-Karten verwenden, aber ich bin gespannt, was die Anwendung ist.
Jonathan Fischoff

ftgles ist eine Bibliothek, die OpenGL und Freetype zum Rendern von Schriften verwendet. Jedes Symbol ist jedoch in einer eigenen Textur gespeichert. Ich möchte sie in eine Textur packen.
deft_code

Antworten:


58

Ich habe ein paar Monate bei einem Job verbracht, um einen besseren Algorithmus zum Packen von Texturen zu finden.

Der Algorithmus, mit dem wir begonnen haben, war einfach. Sammle alle eingegebenen Gegenstände. Sortieren Sie sie nach der Anzahl der verbrauchten Pixel (groß bis klein). Legen Sie sie in der Reihenfolge der Scanlinien in Ihrer Textur an, indem Sie lediglich die Inhalte vom obersten bis zum obersten Pixel testen, eine Linie nach unten verschieben und nach jeder erfolgreichen Platzierung das Zurücksetzen auf das oberste Pixel wiederholen.

Sie müssen entweder eine Breite fest codieren oder sich eine andere Heuristik dafür ausdenken. In einem Versuch, die Rechtwinkligkeit beizubehalten, begann unser Algorithmus bei 128 und erhöhte sich dann um 128 Sekunden, bis ein Ergebnis erzielt wurde, das nicht tiefer als breit war.

Also hatten wir diesen Algorithmus und ich entschied mich, ihn zu verbessern. Ich habe ein paar verrückte Heuristiken ausprobiert - ich habe versucht, Objekte zu finden, die zusammenpassen, und eine Menge gewünschter Raumverpackungseigenschaften gewichtet, gedreht und gewendet. Nach drei Monaten Arbeit habe ich 3% Platz gespart.

Ja. 3%.

Und nachdem wir unsere Komprimierungsroutine darüber ausgeführt haben, ist sie tatsächlich größer geworden (was ich immer noch nicht erklären kann), sodass wir das Ganze rausgeworfen und zum alten Algorithmus zurückgekehrt sind.

Sortieren Sie die Elemente und mischen Sie sie in Scanline-Reihenfolge in die Textur. Da ist dein Algorithmus. Es ist einfach zu programmieren, schnell auszuführen und ohne viel Arbeit wird man nicht viel besser. Diese Arbeit lohnt sich nur, wenn Ihr Unternehmen mindestens 50 und wahrscheinlich mehr Mitarbeiter hat.

Alt-Text

Und als Randnotiz habe ich diesen Algorithmus (feste Breite 512 Pixel) für genau die gleiche Anwendung implementiert, die Sie gerade ausführen (keine Ftgles, aber offen gerenderte Freetype-Glyphen.). Hier ist das Ergebnis. Es sieht unscharf aus, weil ich Valves abstandsfeldbasierten Text-Rendering-Algorithmus verwende , der auch den zusätzlichen Abstand zwischen Glyphen berücksichtigt. Offensichtlich bleibt nicht viel Platz übrig, und es macht einen guten Job, Dinge in offene Stellen zu stopfen.

Der gesamte Code dafür ist BSD-lizenziert und bei github erhältlich .


Ich habe mir deine Textur angesehen und dachte mir: "Ich bin sicher, dass unser Texturpacker ein bisschen besser abschneidet". Und dann habe ich es mir angeschaut und festgestellt, dass ich es vor einiger Zeit kaputt gemacht habe und es nicht bemerkt habe (denn sobald es funktioniert, wer schaut sich die ausgegebenen Texturen an?) ... Also danke fürs Posten - hätte es nicht gefunden ansonsten der Fehler :) (wenn ich den Fehler behoben habe, sieht er sehr ähnlich aus - vielleicht ein bisschen besser, aber es ist schwierig, genau zu sagen. "so gut" ist wahrscheinlich die sicherste Beschreibung).
JasonD

@JasonD, ich würde gerne wissen, was Ihr Algorithmus macht, wenn er eine bessere Ausgabe liefert :) Auch wenn er auf andere Weise eine ungefähr gleichwertige Ausgabe liefert.
ZorbaTHut

1
Danke für die Algo-Beschreibung + das zugegebene Versagen + den Quellcode. Guter Eintrag.
Calvin1602

1
Der Grund, warum es nach der Komprimierung größer wurde, ist wahrscheinlich der Komprimierungsalgorithmus. Da die Komprimierung häufig auf Hashing und der Suche nach binären Mustern beruht, generiert der Algorithmus eine ganze Reihe von Mustern, die zu einer Vergrößerung der Größe führen können. Eine großartige Möglichkeit, dies zu testen, um eine Datei einfach immer wieder neu zu komprimieren, und schließlich wird sie aufgrund fehlender Muster wieder größer.
Hanna

1
Die Suche nach der neuesten Version von ZorbaTHuts Verpackungscode (font_baker.cpp) finden Sie hier: github.com/zorbathut/glorp/blob/…
mems

20

Die Dissertation von Andrea Lodi trägt den Titel Algorithmen für zweidimensionale Behälterpackungs- und Zuordnungsprobleme .
Die Dissertation behandelt einige der schwierigeren Formen dieser Probleme. Zum Glück ist das Packen von Texturen die einfachste Variante. Der beste Algorithmus, den er fand, hieß Touching Perimeter .

Zitat aus Seite 52:

Der Algorithmus mit der Bezeichnung Touching Perimeter (TPRF) sortiert die Elemente zunächst nach nicht ansteigendem Bereich (Aufbrechen der Bindungen durch nicht ansteigende Mindestwerte (wj, hj)) und richtet sie horizontal aus. Eine Untergrenze L für den optimalen Lösungswert wird dann berechnet und L leere Behälter werden initialisiert. (Die im vorherigen Abschnitt definierte kontinuierliche Untergrenze L0 gilt natürlich auch für 2BP | R | F; bessere Grenzen werden von Dell'Amico, Martello und Vigo vorgeschlagen [56].) Der Algorithmus packt jeweils einen Gegenstand in einem vorhandenen Fach oder durch Initialisieren eines neuen Fachs. Der erste Artikel, der in einem Behälter verpackt ist, wird immer in die linke untere Ecke gestellt. Jeder nachfolgende Gegenstand ist in einer sogenannten normalen Position verpackt (siehe Christodes und Whitlock [41]), dh
Die Wahl des Behälters und der Verpackungsposition erfolgt durch Auswertung einer Punktzahl, die als Prozentsatz des Artikelumfangs definiert ist, der den Behälter und andere bereits verpackte Artikel berührt. Diese Strategie bevorzugt Muster, bei denen die verpackten Gegenstände keine kleinen Bereiche „einklemmen“, was für weitere Platzierungen schwierig sein kann. Für jede in Frage kommende Verpackungsposition wird die Punktzahl zweimal für die beiden Artikelorientierungen (sofern beide machbar sind) ausgewertet und der höchste Wert ausgewählt. Punktebindungen werden unterbrochen, indem der Behälter mit der maximalen Packfläche ausgewählt wird. Der Gesamtalgorithmus ist wie folgt.

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;

Das Papier beschreibt auch einen Algorithmus zum Bestimmen der Größe einer optimal gepackten Texturabbildung. Das wäre nützlich, um festzustellen, ob es überhaupt möglich ist, alle Texturen in einen 1024x1024-Atlas einzupassen.


Dieser Algorithmus geht davon aus, dass Texturen eine rechteckige Form haben, oder?
User1767754

17

Wenn sich noch jemand dafür interessiert, habe ich die rectpack2D- Bibliothek komplett neu geschrieben, damit sie wesentlich effizienter ist.

Es funktioniert, indem std::vectorLeerzeichen im Atlas beibehalten werden, beginnend mit einer anfänglichen Maximalgröße (normalerweise die maximal zulässige Texturgröße auf einer bestimmten GPU), der erste funktionsfähige Leerraum aufgeteilt und die Teilungen zurück in den Vektor gespeichert werden.

Der Durchbruch bei der Leistung war, dass nur ein Vektor verwendet wurde, anstatt einen ganzen Baum zu behalten, wie es zuvor getan wurde.

Die Vorgehensweise ist in der README ausführlich beschrieben .

Die Bibliothek steht unter MIT. Ich freue mich für Sie, wenn Sie sie nützlich finden!

Beispielergebnisse:

Die Tests wurden mit einer Intel (R) Core (TM) i7-4770K-CPU mit 3,50 GHz durchgeführt. Die Binärdatei wurde mit clang 6.0.0 unter Verwendung eines -03-Schalters erstellt.

Beliebige Spielsprites + japanische Glyphen: insgesamt 3264 Probanden.

Laufzeit: 4 Millisekunden
Vergeudete Pixel: 15538 (0,31% - entspricht einem Quadrat von 125 x 125)

Ausgabe (2116 x 2382):

3

In Farbe:
(Schwarz ist Platzverschwendung)

4

Japanische Glyphen + einige GUI-Sprites: 3122 Probanden.

Laufzeit: 3,5 - 7 ms
Vergeudete Pixel: 9288 (1,23% - entspricht einem Quadrat von 96 x 96)

Ausgabe (866 x 871):

5

In Farbe:
(Schwarz ist Platzverschwendung)

6


2
Ich habe Ihren Code heruntergeladen. Lesen Sie einfach die Strukturdefinitionen: WAS IST DIESE MONSTROSITÄT ?! Es sieht aus wie Code Golf.
Akaltar

3
Trotzdem funktioniert es und es hat geholfen, also danke. Ich wollte nicht unhöflich sein.
Altar

Ich bin mir nicht sicher, warum ich diese Antwort übersprungen habe, da sie noch schneller und besser als mein eigener Algorithmus ist. O_O danke
GameDeveloper

@akaltar Ich kann mir vorstellen, dass ich die Sprache während der Zeit noch gelernt habe :)
Patryk Czachurski

Ziemlich einfacher Ansatz, der sich schnell umsetzen lässt und gute Ergebnisse erzielt, danke :)
FlintZA

5

Einen guten heuristischen Algorithmus finden Sie hier . Als ich kürzlich etwas Ähnliches ausprobierte, stellte ich fest, dass dies der grundlegende Ausgangspunkt für die meisten Implementierungen ist, die ich gesehen habe.

Funktioniert besonders gut mit vielen regelmäßig geformten Gegenständen ähnlicher Größe oder mit einer guten Mischung aus kleinen und weniger großen Bildern. Der beste Rat, um gute Ergebnisse zu erzielen, ist, sich daran zu erinnern, Ihre Eingaben nach der Bildgröße zu sortieren und dann von der größten zur kleinsten zu packen, da die kleineren Bilder in den Raum um die größeren Bilder gepackt werden. Wie Sie dies tun, hängt möglicherweise von Ihren Zielen ab. Ich habe den Umfang und nicht den Bereich als Näherungswert erster Ordnung verwendet, da ich der Ansicht war, dass große, dünne, kurze und breite Bilder (die einen geringen Bereich haben würden) später in einem Paket nur sehr schwer zu platzieren sind. Wenn Sie also den Umfang verwenden, drücken Sie diese merkwürdigen Formen in Richtung der Vorderseite des Ordens.

Hier ist eine Beispielvisualisierung der Ausgabe für meinen Packer auf einem zufälligen Satz von Bildern aus dem Image-Dump-Verzeichnis meiner Website :). Packer-Ausgabe

Die Zahlen in den Quadraten sind die IDs der enthaltenen Blöcke im Baum. Geben Sie also eine Vorstellung von der Reihenfolge der Einfügungen. Die erste ist die ID "3", da es sich um den ersten Blattknoten handelt (nur Blätter enthalten Bilder) und folglich zwei Elternteile hat.

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

3

Persönlich verwende ich nur einen gierigen größten Block, der zum ersten System passt. Es ist nicht optimal, aber es macht den Trick OK.

Beachten Sie, dass Sie bei einer angemessenen Anzahl von Texturblöcken die beste Reihenfolge erschöpfend suchen können, auch wenn das Problem selbst NP ist.


3

Ich habe das UV-Patch auch für unregelmäßige UV-Maps verwendet, um es in eine Bitmap-Maske zu verwandeln und eine Maske für die Textur selbst zu erstellen. Ich ordne die Blöcke nach einer einfachen Heuristik (Höhe, Breite, Größe, was auch immer) und erlaube Rotationen der Blöcke, um die gewählte Heuristik zu minimieren oder zu maximieren. Das gibt einen überschaubaren Suchraum für Brute Force.

Wenn Sie es dann wiederholen können, indem Sie mehrere Heuristiken ausprobieren, und / oder einen Zufallsfaktor bei der Auswahl der Reihenfolge anwenden und iterieren, bis eine bestimmte Zeitspanne abgelaufen ist.

Mit diesem Schema erhalten Sie kleine UV-Inseln, die in die Lücken von großen Inseln gepackt sind, und sogar in Löcher, die innerhalb einzelner UV-Flecken selbst verbleiben.


1

Wir haben kürzlich ein Python-Skript veröffentlicht, das Texturen in mehrere Bilddateien einer bestimmten Größe packt.

Zitiert aus unserem Blog:

"Obwohl es zahlreiche Packer gibt, die online zu finden sind, bestand unsere Schwierigkeit darin, solche zu finden, die eine große Anzahl von Bildern in mehreren Verzeichnissen verarbeiten können. So wurde unser eigener Atlas-Packer geboren!

Wie es ist, startet unser kleines Skript im Basisverzeichnis und lädt alle .PNGs in einen Atlas. Wenn dieser Atlas gefüllt ist, wird ein neuer erstellt. Dann wird versucht, den Rest der Bilder in allen vorherigen Atlanten anzupassen, bevor ein Platz im neuen gefunden wird. Auf diese Weise wird jeder Atlas so eng wie möglich gepackt. Die Namen der Atlanten richten sich nach dem Ordner, aus dem ihre Bilder stammen.

Sie können die Größe des Atlas (Zeile 65), das Format der zu packenden Bilder (Zeile 67), das Ladeverzeichnis (Zeile 10) und das Speicherverzeichnis (Zeile 13) ziemlich einfach ändern, ohne Erfahrung mit Python. Als kleiner Haftungsausschluss wurde dies in ein paar Tagen zusammengepeitscht, um speziell mit unserem Motor zu arbeiten. Ich empfehle Ihnen, Features anzufordern, mit Ihren eigenen Variationen zu kommentieren und Fehler zu melden, aber alle Änderungen am Skript werden in meiner Freizeit vorgenommen. "

Den vollständigen Quellcode finden Sie hier: http://www.retroaffect.com/blog/159/Image_Atlas_Packer/#b


1

Es ist ziemlich einfach, Schriftarten zu packen, da alle (oder die große Mehrheit) der Glyphen-Texturen fast die gleiche Größe haben. Tun Sie das Einfachste, was Ihnen in den Sinn kommt und es wird sehr nahe am Optimum sein.

Klugheit wird wichtiger, wenn Sie Bilder in sehr unterschiedlichen Größen verpacken. Dann möchten Sie in der Lage sein, Lücken usw. zu schließen. Auch dann liefert ein einfacher Algorithmus wie die zuvor beschriebene Suche nach Scanlinienreihenfolgen sehr vernünftige Ergebnisse.

Keiner der fortgeschrittenen Algen ist magisch. Sie sind nicht 50% effektiver als ein Simpel-Algo, und Sie werden keine beständigen Vorteile daraus ziehen, es sei denn, Sie haben eine erstaunliche Anzahl von Texturblättern. Das liegt daran, dass die kleinen Verbesserungen, die bessere Algorithmen bewirken, nur insgesamt sichtbar werden.

Gehen Sie einfach und gehen Sie zu etwas über, bei dem Ihre Bemühungen besser belohnt werden


0

Wenn es speziell für Font-Texturen ist, dann tun Sie wahrscheinlich etwas nicht Optimales, aber nett und einfach:

Sortieren Sie die Zeichen nach ihrer Höhe, die höchsten zuerst

Beginnen Sie mit 0,0. Platzieren Sie das erste Zeichen an der aktuellen Koordinate, rücken Sie X vor, platzieren Sie das nächste und wiederholen Sie den Vorgang, bis wir kein weiteres Zeichen mehr einfügen können

Setzen Sie X auf 0 zurück, bewegen Sie Y um die Höhe des höchsten Zeichens in der Zeile nach unten und füllen Sie eine weitere Zeile

Wiederholen, bis wir keine Zeichen mehr haben oder keine weitere Zeile mehr passen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.