Algoritmo de empaque de textura

52

¿Qué es un buen algoritmo de empaque de textura? Técnicamente, el embalaje del contenedor es NP-duro , por lo que una heurística es lo que realmente busco.

código_deft
fuente
Supuse que está usando esto para optimizar mapas uv, pero tengo curiosidad por saber cuál es la aplicación.
Jonathan Fischoff
ftgles es una biblioteca que utiliza opengl y freetype para representar fuentes. Sin embargo, cada glifo se almacena en su propia textura. Me gustaría empacarlos en una textura.
deft_code

Respuestas:

58

Pasé unos meses en un trabajo con un mejor algoritmo de empaque de texturas.

El algoritmo con el que comenzamos fue simple. Recoge todos los elementos de entrada. Ordénelos por píxeles totales consumidos, de grande a pequeño. Extiéndalos en su textura en el orden de la línea de exploración, solo probando cosas desde el píxel superior al píxel vertical, moviéndose hacia abajo en una línea y repitiendo, restableciendo el píxel superior después de cada colocación exitosa.

Usted necesita codificar un ancho o crear otra heurística para esto. En un intento por preservar la cuadratura, nuestro algoritmo comenzaría en 128, luego aumentaría en 128s hasta que se obtuviera un resultado que no fuera más profundo que ancho.

Entonces, teníamos ese algoritmo y decidí mejorarlo. Probé un montón de heurísticas extrañas, tratando de encontrar objetos que encajaran, ponderando sobre un conjunto de propiedades deseadas de empaquetamiento espacial, girando y volteando. Después de todo mi trabajo, literalmente tres meses de trabajo, terminé ahorrando un 3% de espacio.

Sí. 3%

Y después de ejecutar nuestra rutina de compresión, en realidad terminó siendo más grande (lo que todavía no puedo explicar), así que descartamos todo y volvimos al antiguo algoritmo.

Ordenar elementos, atascar en textura en el orden de la línea de escaneo. Ahí está tu algoritmo. Es fácil de codificar, rápido de ejecutar y no mejorará mucho sin una increíble cantidad de trabajo. Ese trabajo simplemente no vale la pena a menos que su empresa tenga al menos 50 personas y probablemente más.

texto alternativo

Y como nota al margen, acabo de implementar este algoritmo (ancho fijo de 512 píxeles) para, literalmente, exactamente la misma aplicación que estás haciendo (sin complicaciones, pero con glifos de tipo libre renderizados por opengl). Aquí está el resultado. Parece borroso porque el mío está utilizando el algoritmo de representación de texto basado en el campo de distancia de Valve , que también representa el espacio adicional entre los glifos. Obviamente, no queda mucho espacio vacío, y hace un buen trabajo al agrupar las cosas en espacios abiertos.

Todo el código para esto tiene licencia BSD y está disponible en github .

ZorbaTHut
fuente
Miré tu textura y pensé: "Estoy seguro de que nuestro empacador de texturas funciona un poco mejor que eso". Y luego fui y lo miré, y me di cuenta de que lo rompí hace algún tiempo y no me di cuenta (porque una vez que funciona, ¿quién mira las texturas de salida?) ... Así que gracias por publicar, no habría encontrado el error de lo contrario :) (una vez que solucioné el error, se ve muy similar, quizás un poco mejor, pero es difícil de decir exactamente. "tan bueno" es probablemente la descripción más segura).
JasonD
@JasonD, me encantaría saber qué hace su algoritmo, si obtiene una mejor salida :) Incluso si obtiene una salida más o menos equivalente de una manera diferente.
ZorbaTHut
1
Gracias por la descripción de algo + el error admitido + el código fuente. Buena publicación.
Calvin1602
1
La razón por la que se hizo más grande después de la compresión probablemente se deba al algoritmo de compresión. Dado que la compresión a menudo se basa en el hash y en la búsqueda de patrones binarios, si el algoritmo puede identificar suficientes patrones, generará un montón de ellos que pueden hacer que el tamaño se expanda. Una excelente manera de probarlo es simplemente volver a comprimir un archivo una y otra vez y, finalmente, comenzará a crecer de nuevo debido a la falta de patrones.
Hanna
1
Para ver cómo buscar la última versión del código de empaque de ZorbaTHut (font_baker.cpp), puede encontrar aquí: github.com/zorbathut/glorp/blob/…
mems
20

La tesis doctoral de Andrea Lodi se titula Algoritmos para problemas de asignación y empaque bidimensional de contenedores .
La tesis analiza algunas de las formas más difíciles de estos problemas. Afortunadamente, el embalaje de texturas es la versión más fácil. El mejor algoritmo que encontró se llamaba Touching Perimeter .

Para citar de la página 52:

El algoritmo, llamado Touching Perimeter (TPRF), comienza ordenando los elementos de acuerdo con el área que no aumenta (rompiendo los lazos por valores min {wj, hj} que no aumentan) y orientándolos horizontalmente. Luego se calcula un límite inferior L en el valor óptimo de la solución, y se inicializan L contenedores vacíos. (El límite inferior continuo L0 definido en la sección anterior también es obviamente válido para 2BP | R | F; Dell'Amico, Martello y Vigo [56] proponen mejores límites). El algoritmo incluye un elemento a la vez, en un contenedor existente, o inicializando uno nuevo. El primer artículo embalado en un contenedor siempre se coloca en la esquina inferior izquierda. Cada artículo posterior se empaqueta en una posición denominada normal (ver Christo fi y Whitlock [41]), es decir,
La elección del contenedor y de la posición de empaque se realiza mediante la evaluación de una puntuación, definida como el porcentaje del perímetro del artículo que toca el contenedor y otros artículos ya empacados. Esta estrategia favorece los patrones en los que los artículos empacados no "atrapan" áreas pequeñas, que pueden ser difíciles de usar para futuras ubicaciones. Para cada puesto de empaquetamiento candidato, la puntuación se evalúa dos veces, para las dos orientaciones de ítems (si ambas son factibles), y se selecciona el valor más alto. Los lazos de puntuación se rompen al elegir el contenedor que tiene el área de embalaje máxima. El algoritmo general es el siguiente.

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;

También de interés, el documento describe un algoritmo para determinar el tamaño de un mapa de textura óptimamente empaquetado. Sería útil determinar si es posible ajustar todas las texturas en un atlas de 1024x1024.

código_deft
fuente
Este algoritmo supone que las texturas son de forma rectangular, ¿es así?
user1767754
17

Si alguien todavía está interesado, he reescrito completamente la biblioteca rectpack2D para que sea mucho más eficiente.

Funciona manteniendo un std::vectorespacio vacío en el atlas, comenzando con un tamaño máximo inicial (típicamente, el tamaño de textura máximo permitido en una GPU particular), dividiendo el primer espacio vacío viable y guardando las divisiones de nuevo en el vector.

El avance en el rendimiento vino con solo usar un vector, en lugar de mantener un árbol completo, como se hizo anteriormente.

El procedimiento se describe en detalle en el archivo README .

La biblioteca está bajo MIT, así que me alegro por ti si te resulta útil.

Resultados de ejemplo:

Las pruebas se realizaron en una CPU Intel (R) Core (TM) i7-4770K a 3.50GHz. El binario fue construido con clang 6.0.0, usando un interruptor -03.

Sprites arbitrarios del juego + glifos japoneses: 3264 sujetos en total.

Tiempo de ejecución: 4 milisegundos
Píxeles desperdiciados: 15538 (0.31% - equivalente a un cuadrado de 125 x 125)

Salida (2116 x 2382):

3

En color:
(el negro es espacio desperdiciado)

4 4

Glifos japoneses + algunos sprites GUI: 3122 sujetos.

Tiempo de ejecución: 3.5 - 7 ms
Píxeles desperdiciados: 9288 (1.23% - equivalente a un cuadrado de 96 x 96)

Salida (866 x 871):

5 5

En color:
(el negro es espacio desperdiciado)

6 6

Patryk Czachurski
fuente
2
He descargado tu código. Simplemente leyendo las definiciones de estructura: ¡¿QUÉ ES ESTA MONSTROSIDAD ?! Parece un código de golf.
akaltar
3
Aún así, funciona y ayudó, así que gracias. No quería ser grosero.
akaltar
No estoy seguro de por qué omití esta respuesta, ya que es aún más rápido y mejor empacador que mi propio algoritmo O_O gracias
GameDeveloper
@akaltar Me lo imagino, todavía estaba aprendiendo el idioma durante el tiempo :)
Patryk Czachurski
Enfoque bastante simple que se implementa rápidamente y obtiene buenos resultados, gracias :)
FlintZA
5

Un buen algoritmo heurístico se puede encontrar aquí . Cuando estaba probando algo similar recientemente, encontré esto como el punto de partida básico para la mayoría de las implementaciones que vi.

Funciona particularmente bien con muchos elementos de formas regulares y de tamaño similar, o con una buena combinación de imágenes pequeñas y menos grandes. El mejor consejo para lograr buenos resultados es recordar clasificar su entrada en términos de tamaño de imagen, luego empaquetar de mayor a menor, ya que las imágenes más pequeñas se empaquetarán en el espacio alrededor de las imágenes más grandes. La forma en que haces esto, dependiendo de tus objetivos. Usé el perímetro en lugar del área como una aproximación de primer orden, ya que tomé la vista de que las imágenes altas + delgadas / cortas + anchas (que tendrían un área más baja) en realidad son muy difíciles de colocar más adelante en un paquete, por lo que al usar el perímetro empujas estas formas extrañas hacia el frente de la orden.

Aquí hay una muestra de visualización de la salida para mi empacador en un conjunto aleatorio de imágenes del directorio de volcado de imágenes de mi sitio web :). Salida del empacador

Los números en los cuadrados son las identificaciones de los bloques que contienen en el árbol, así que le dará una idea del orden de las inserciones. El primero es el ID "3" porque es el primer nodo de hoja (solo las hojas contienen imágenes) y, en consecuencia, tiene 2 padres).

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

Personalmente, solo uso un codicioso primer bloque más grande que encaja. No es óptimo, pero funciona bien.

Tenga en cuenta que, si tiene una cantidad razonable de bloques de textura, puede buscar exhaustivamente el mejor orden incluso si el problema en sí es NP.

drxzcl
fuente
3

Algo que he usado, que funciona bien incluso para mapas UV irregulares, es convertir el parche UV en una máscara de mapa de bits y mantener una máscara para la textura misma, buscando la primera posición en la que se ajustará el parche UV. Ordeno los bloques de acuerdo con algunas heurísticas simples (altura, ancho, tamaño, lo que sea), y permito que las rotaciones de los bloques minimicen o maximicen la heurística elegida. Eso proporciona un espacio de búsqueda manejable para la fuerza bruta.

Si puede repetir eso probando varias heurísticas, y / o aplicar un factor aleatorio al elegir el orden e iterar hasta que se agote el límite de tiempo.

Con este esquema, obtendrá pequeñas islas UV empaquetadas en los huecos hechos por las grandes, e incluso en los agujeros que quedan dentro de los mismos parches UV.

JasonD
fuente
1

Recientemente lanzamos un script de Python que empaquetará texturas en múltiples archivos de imagen de un tamaño determinado.

Citado de nuestro blog:

"Si bien hay numerosos empacadores que se pueden encontrar en línea, nuestra dificultad fue encontrar cualquiera que pudiera manejar grandes cantidades de imágenes en múltiples directorios. ¡Así nació nuestro propio atlas packer!

Tal como está, nuestro pequeño script comenzará en el directorio base y cargará todos los .PNG en un atlas. Si ese atlas está lleno, crea uno nuevo. Luego, intentará ajustar el resto de las imágenes en todos los atlas anteriores antes de encontrar un lugar en el nuevo. De esa manera, cada atlas está empaquetado lo más ajustado posible. Los atlas se nombran según la carpeta de la que provienen sus imágenes.

Puede cambiar el tamaño del atlas (línea 65), el formato de las imágenes que desea empaquetar (línea 67), el directorio de carga (línea 10) y el directorio de guardar (línea 13) con bastante facilidad sin experiencia en Python. Como un pequeño descargo de responsabilidad, esto se combinó en unos días para trabajar específicamente con nuestro motor. Le recomiendo que solicite funciones, comente con sus propias variaciones e informe cualquier error, pero cualquier cambio en el script ocurrirá en mi tiempo libre ".

No dude en consultar el código fuente completo aquí: http://www.retroaffect.com/blog/159/Image_Atlas_Packer/#b

dcarrigg
fuente
1

Es bastante fácil empaquetar fuentes porque todas (o la gran mayoría) de las texturas de glifos son casi del mismo tamaño. Haz lo más simple que se te ocurra y estará muy cerca de lo óptimo.

La inteligencia se vuelve más importante cuando empaca imágenes de tamaños muy diferentes. Entonces desea poder empacar en huecos, etc. Aun así, sin embargo, un algoritmo simple como la búsqueda de orden de exploración analizada anteriormente producirá resultados muy razonables.

Ninguno de los algos avanzados es mágico. No serán un 50% más eficientes que un simpel algo, y no obtendrá beneficios consistentes de ellos a menos que tenga una asombrosa cantidad de hojas de textura. Esto se debe a que las pequeñas mejoras que hacen los mejores algoritmos solo se verán en conjunto.

Vaya simple y avance a algo donde sus esfuerzos serán mejor recompensados


fuente
0

Si es específicamente para texturas de fuente, entonces probablemente haga algo no óptimo pero agradable y simple:

Ordenar los caracteres por altura, los más altos primero

Comience en 0,0 Coloque el primer carácter en las coordenadas actuales, avance X, coloque el siguiente, repita hasta que no quepamos con otro

Restablezca X a 0, avance Y hacia abajo por la altura del carácter más alto de la fila y llene otra fila

Repita hasta que nos quedemos sin caracteres o no podamos encajar en otra fila.

bluescrn
fuente