Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question Why is Texture2D.PackTextures packing so inefficiently?

Discussion in 'Scripting' started by skoteskote, Jan 24, 2021.

  1. skoteskote

    skoteskote

    Joined:
    Feb 15, 2017
    Posts:
    87
    So, if I have 16 textures of size 512x512, and I want to pack these onto one texture size 2048x2048, then the most reasonable organization would be to organize the textures 4x4, right? Texture2D.PackTextures disagrees:
    Screenshot 2021-01-24 at 21.35.54.png

    This is an awkward packing, that anyone who has ever moved house would frown at. It also means I need to do a lot of custom stuff to get the right textures from the atlas. Anyone know a workaround, or have a custom GetPixels32/SetPixels32 solutions they would like to share?

    Code (CSharp):
    1.  
    2. [SerializeField] bool displayTestResult = false;
    3. SpriteRenderer sr;
    4. public Texture2D[] atlasTextures;
    5.  
    6. void Start()
    7. {
    8.     if (displayTestResult)
    9.         sr = gameObject.AddComponent<SpriteRenderer>();
    10.  
    11.     PackImages(atlasTextures, 512);
    12. }
    13.  
    14. public void PackImages(Texture2D[] textures, int scaleWidth = 0, bool save = false, string name = "textureatlas")
    15. {
    16.     if (scaleWidth != 0)
    17.         textures = textures.ScaleTextures(scaleWidth, scaleWidth);
    18.  
    19.     int size;
    20.     Texture2D[] _texs = textures;
    21.     Texture2D atlas = new Texture2D(2048, 2048);
    22.     atlas.PackTextures(_texs, 1, 2048, !save);
    23.  
    24.     if (save)
    25.         System.IO.File.WriteAllBytes(Application.dataPath + "/../Atlases/" + name + ".png", atlas.EncodeToPNG());
    26.     if (displayTestResult)
    27.         sr.sprite = Sprite.Create(atlas, new Rect(0, 0, atlas.width, atlas.height), new Vector2(0.5f, 0.5f), 100.0f);
    28. }
    29.  
     
    Last edited: Jan 24, 2021
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,888
    skoteskote likes this.
  3. skoteskote

    skoteskote

    Joined:
    Feb 15, 2017
    Posts:
    87
    I'm using the texture atlas as a flipbook for vfx graph, and the only option afaik to get a specific texture from an atlas in vfx graph is to use the
    Set Tex Index
    block, which is only an int. I don't really know how I would use the rect in the vfx graph?
     
  4. StaggartCreations

    StaggartCreations

    Joined:
    Feb 18, 2015
    Posts:
    2,237
    That's as good as it gets for PackTextures. Unfortunately. AFAIK it's not really used in any core features, which is probably why it hasn't seen any improvements.

    HDRP uses a superior bin packing algorithm and so does the Sprite Packer, but it's pretty well incorporated into those systems and can't be repurposed.

    If you're specifically looking to create flipbooks, check out unity's VFX Toolbox on GitHub, which is perfect for converting a sequence into a VFX flipbook.
     
    skoteskote and PraetorBlue like this.
  5. skoteskote

    skoteskote

    Joined:
    Feb 15, 2017
    Posts:
    87
    Ah, thanks for your reply, that's a great resource!

    I need to be able to do it at runtime though so I ended up writing my own thing. Posting here in case anyone would need something similar:
    Code (CSharp):
    1. public static class TextureExtensions
    2. {
    3.     public static Texture2D BuildAtlas(this Texture2D[] textures, int width, int height, out int size)
    4.     {
    5.         size = Mathf.CeilToInt(Mathf.Sqrt(textures.Length));
    6.  
    7.         textures = textures.ScaleTextures(width / size, height / size);
    8.         textures = textures.FillGrid((int)Mathf.Pow(size, 2));
    9.  
    10.         int originalWidth = textures[0].width;
    11.         int originalHeight = textures[0].height;
    12.         int countx = 0;
    13.         int county = 0;
    14.  
    15.         Texture2D texture = new Texture2D(width, height, TextureFormat.ARGB32, false, false)
    16.         {
    17.             filterMode = FilterMode.Point
    18.         };
    19.         for (int i = 0; i < textures.Length; i++)
    20.         {
    21.             Color32[] tex = textures[i].GetPixels32();
    22.             texture.SetPixels32(countx, county, originalWidth, originalHeight, tex);
    23.  
    24.             countx += originalWidth;
    25.             if (countx > width - originalWidth)
    26.                 countx = 0;
    27.             if (i != 0 && (i + 1) % size == 0)
    28.                 county += originalHeight;
    29.         }
    30.         texture.Apply();
    31.  
    32.         return texture;
    33.     }
    34.  
    35.     public static Texture2D[] ScaleTextures(this Texture2D[] textures, int scaleWidth = 0, int scaleHeight = 0)
    36.     {
    37.         for (int i = 0; i < textures.Length; i++)
    38.         {
    39.             int originalWidth = textures[i].width;
    40.             int originalHeight = textures[i].height;
    41.             Color32[] tex = textures[i].GetPixels32();
    42.             textures[i] = new Texture2D(originalWidth, originalHeight, TextureFormat.ARGB32, false, false)
    43.             {
    44.                 filterMode = FilterMode.Point
    45.             };
    46.             textures[i].SetPixels32(tex);
    47.             textures[i].Apply();
    48.  
    49.             if (scaleWidth == 0) scaleWidth = originalWidth;
    50.             if (scaleHeight == 0)
    51.             {
    52.                 float scaleFactor = (float)scaleWidth / (float)originalWidth;
    53.                 float h = scaleFactor * originalHeight;
    54.                 scaleHeight = (int)h;
    55.             }
    56.  
    57.             TextureScale.Bilinear(textures[i], scaleWidth, scaleHeight);
    58.         }
    59.         return textures;
    60.     }
    61.  
    62.     public static Texture2D[] FillGrid(this Texture2D[] textures, int targetCount)
    63.     {
    64.         int originalCount = textures.Length;
    65.  
    66.         Texture2D[] filledGrid = new Texture2D[targetCount];
    67.  
    68.         for (int i = 0; i < targetCount; i++)
    69.         {
    70.             if (i < originalCount)
    71.                 filledGrid[i] = textures[i];
    72.             else
    73.             {
    74.                 Texture2D tex = new Texture2D(textures[i - originalCount].width, textures[i - originalCount].height, TextureFormat.ARGB32, false, false);
    75.                 Graphics.CopyTexture(textures[i - originalCount], tex);
    76.                 filledGrid[i] = tex;
    77.             }
    78.         }
    79.  
    80.         return filledGrid;
    81.     }
    82. }