Search Unity

Pack textures on mesh with multiple submeshes

Discussion in 'Scripting' started by Rustam-Ganeyev, Mar 5, 2013.

  1. Rustam-Ganeyev

    Rustam-Ganeyev

    Joined:
    Jul 18, 2011
    Posts:
    29
    Hi. I'm doing some optimization stuff and trying to pack all materials to one material. Here's my problem: I have mesh with 5 submeshes(5 materials with different textures). I can pack all textures to one atlas and use just that texture. Problem is I don't know how to recalculate new uvs to my new atlas. Here's the code:

    Code (csharp):
    1.  
    2. private void CreateMeshTexture ()
    3.  {  
    4.        //get all textures
    5.        var rend = renderer;
    6.        var atlasMaterial = new Material (Shader.Find ("Mobile/Diffuse"));
    7.        var meshFilter = GetComponent<MeshFilter> ();
    8.        var textures = new List<Texture2D> ();
    9.  
    10.        foreach (var mat in rend.materials) {
    11.          textures.Add ((Texture2D)mat.mainTexture);
    12.        }
    13.  
    14.  
    15.        //create texture atlas
    16.        textureAtlas = new Texture2D (maxSquareTextureSize, maxSquareTextureSize);
    17.        Rect[] coords = textureAtlas.PackTextures (textures.ToArray (), 0, maxSquareTextureSize);  
    18.        atlasMaterial.mainTexture = textureAtlas;
    19.  
    20.        //update materials
    21.        rend.material = atlasMaterial;
    22.        for (int i = 0; i < rend.materials.Length; ++i) {
    23.          rend.materials[i] = atlasMaterial;
    24.        }
    25.  
    26.        //correct uvs
    27.        Vector2[] oldUV, newUV;
    28.        oldUV = meshFilter.mesh.uv;
    29.        newUV = new Vector2[oldUV.Length];
    30.  
    31.        //how to set new uvs?
    32.  
    33.  }
    34.  
    How to recalculate uvs?
     
  2. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,657
    You need to use Mesh.GetTriangles() to retrieve the vertex indices used by each submesh in turn. Then you can go through those indices and update the corresponding UVs to be relative to the calculated rectangle.
     
  3. kit

    kit

    Joined:
    Jun 16, 2011
    Posts:
    87
    It depends on how you creating atlas texture. Let's create some example of created atlas.
    Well, imaging very simple atlas texture, it's created from 4 texture. 2 textures on each side;



    So, full atlas texture uvs are spread from 0(uv0) to 1(uv1) by width, and from 0(uv0) to 1(uv1).
    You only need to set new uvs meshes. e.g. for mesh0 (that uses Tex0) you need to set texture uv from "uv0" to "a" by width, and from "uv0" to "b" by height.

    Of course it's very simple example, but you only need to understand how do you place your textures
     

    Attached Files:

    Last edited: Mar 5, 2013
  4. Rustam-Ganeyev

    Rustam-Ganeyev

    Joined:
    Jul 18, 2011
    Posts:
    29
    Yeah, I knew that. Problem was I couldn't know from which texture uv was taken. I've solved my problem by dividing all submeshes to different meshes and combining them later. Here's the code:

    Code (csharp):
    1.  
    2. private Vector2 UVwithOffset(Vector2 oldUV, Rect rect)
    3.     {
    4.         var newUV = new Vector2();
    5.         newUV.x = Mathf.Lerp(rect.xMin, rect.xMax, oldUV.x);
    6.         newUV.y = Mathf.Lerp(rect.yMin, rect.yMax, oldUV.y);
    7.         return newUV;
    8.     }
    9.  
    10.  
    11.     private Mesh CreateMesh(Mesh oldMesh, int subIndex, Rect uvOffset)
    12.     {
    13.         var newMesh = new Mesh();
    14.  
    15.         var triangles = new List<int>();
    16.         triangles.AddRange(oldMesh.GetTriangles(subIndex)); // the triangles of the sub mesh
    17.         var trianglesHashSet = new HashSet<int>(triangles);
    18.  
    19.         var newVertices = new List<Vector3>();
    20.         var newUvs = new List<Vector2>();
    21.  
    22.         var oldToNewIndices = new Dictionary<int, int>();
    23.         int newIndex = 0;
    24.  
    25.         // Collect the vertices and uvs
    26.         for (int i = 0; i < oldMesh.vertices.Length; i++) {
    27.             if (!trianglesHashSet.Contains(i)) continue;
    28.             newVertices.Add(oldMesh.vertices[i]);
    29.             newUvs.Add(UVwithOffset(oldMesh.uv[i], uvOffset));
    30.             oldToNewIndices.Add(i, newIndex);
    31.             ++newIndex;
    32.         }
    33.  
    34.         var newTriangles = new int[triangles.Count];
    35.         for (int i = 0; i < newTriangles.Length; i++) {
    36.             newTriangles[i] = oldToNewIndices[triangles[i]];
    37.         }
    38.         newMesh.vertices = newVertices.ToArray();
    39.         newMesh.uv = newUvs.ToArray();
    40.         newMesh.triangles = newTriangles;
    41.         newMesh.RecalculateBounds();
    42.         newMesh.RecalculateNormals();
    43.  
    44.         return newMesh;
    45.     }
    Thank you for help.
     
  5. kit

    kit

    Joined:
    Jun 16, 2011
    Posts:
    87
    Well done!

    And what about performance in result scene? It's ok?

    >Problem was I couldn't know from which texture uv was taken.
    Maybe solution is to store a dictionary such as <texture_atlas_region_uv, texture>. Or something like that?

    But, I like your solution :)
     
  6. Rustam-Ganeyev

    Rustam-Ganeyev

    Joined:
    Jul 18, 2011
    Posts:
    29
    You're absolutely right. Moreover, dividing submeshes into meshes isn't nessecary, I can just update uvs in mesh and then combine only itself to merge their submeshes to single submesh.

    I didn't write it yet, maybe in a weekend I'll finish with that and share with community.

    Thank you very much.

    p.s. How I can change topic's title? I want to put [SOLVED] tag