Search Unity

Mesh Combine Wizard (Free Unity Tool & Source Code)

Discussion in 'Assets and Asset Store' started by Gru, Dec 5, 2016.

  1. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    Link to public repository: https://github.com/sirgru/MeshCombineWizard.

    What does it do?
    Running the wizard will combine all the meshes on the chosen Game Object and its children that share the same material. If there is more than a single material in all sub-objects, sub-objects of result will be created so that each corresponds to a single material. A prefab will be created from the combined Game Object in the root of the Assets folder, with all the newly created merged meshes. The original will be set inactive in the scene and the combined Game Object will be put in its position.

    How does it work?
    Put the provided script in any Editor folder. In the Menu Bar a new entry will appear ("E.S. Tools/Mesh Combine Wizard"). Picking this option will show the wizard dialog. The parent of objects to be combined should be assigned to a filed called combine Parent, which may be auto-assigned based on existing editor selection.

    Benefits?
    • Lowers the amount of draw calls, usually dramatically.
    • Does not need to draw all objects in the same batch regardless of whether they are on screen or not, as opposed to static batching.
    • Does not compromise workflow - no need to merge modular pieces outside Unity in a 3D tool. Quick iteration time when changing the combined design.
    • Works with combined objects that have more than 64k verts.
    • Does not compromise the original object's pivot point.
    Known limitations:
    • Does not support objects with multiple materials on submeshes. Such meshes should be split in an external 3D tool, so that there is only a single material per mesh.
    • Does not migrate components from the original other than MeshFilter and MeshRenderer.
    • The default setting is using 32 bit indexes. From https://docs.unity3d.com/ScriptReference/Rendering.IndexFormat.UInt32.html (Note that GPU support for 32 bit indices is not guaranteed on all platforms; for example Android devices with Mali-400 GPU do not support them. When using 32 bit indices on such a platform, a warning message will be logged and mesh will not render.) If the combined mesh has less than 65535 vertices, it is safe to uncheck the 32-bit option in the window (otherwise meshes will be garbled).

    If you're finding this tool helpful, buy all my games and tell all your friends :)
     
    Last edited: May 18, 2020
  2. b4c5p4c3

    b4c5p4c3

    Joined:
    Jan 4, 2013
    Posts:
    537
    Very nice, thank you
     
  3. CDUnityDev

    CDUnityDev

    Joined:
    Jan 31, 2017
    Posts:
    25
    Thank you for sharing this excellent tool.
     
  4. snacktime

    snacktime

    Joined:
    Apr 15, 2013
    Posts:
    3,356
    Just found this today. Nice work, doesn't have the gotcha's of most other mesh combine tools.
     
  5. Doctor06

    Doctor06

    Joined:
    Nov 1, 2014
    Posts:
    31
    This is amazing! spent a whole day looking for this! thank you so much for sharing.
     
  6. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    No problem, please consider my asset Dialogical on Unity Asset store for your game dialogues.
     
  7. teknic

    teknic

    Joined:
    Oct 18, 2012
    Posts:
    32
    Thanks for this. I modified lines 55 and 57 to include the mesh instance ID. This solves the mesh data overwrite problem when combining multiple object groups which all share the same material:

    Code (CSharp):
    1.             AssetDatabase.CreateAsset(combinedMesh, "Assets/CombinedMeshes_" + combinedMesh.GetInstanceID().ToString() + "_" + materialName + ".asset");
    2.  
    3.             string goName = (materialToMeshFilterList.Count > 1) ? "CombinedMeshes_" + combinedMesh.GetInstanceID().ToString() + "_" + materialName : "CombinedMeshes_" + parentOfObjectsToCombine.name;
     
  8. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    No problem.
    Yes, the overwrite can happen if the asset with that name already exists in root Assets folder. I remember purposefully leaving it like that since I never leave meshes in the project root folder. I've updated the script with a similar solution.
     
  9. peardez

    peardez

    Joined:
    Jun 27, 2017
    Posts:
    10
    Hi there. I've used your tool in my project and it has been really useful but now aparently is provoking some kind of trouble that prevents compiling. I was hoping if you could suggest somesolution for me. I'm using unity 2017.3.0f3
    Thanks in advance.

    PS: The log error says:
    Assets/MeshCombineWizard.cs(2,7): error CS0246: The type or namespace name `UnityEditor' could not be found. Are you missing an assembly reference?

    Assets/MeshCombineWizard.cs(5,34): error CS0246: The type or namespace name `ScriptableWizard' could not be found. Are you missing an assembly reference?

    Build completed with a result of 'Failed'
    UnityEngine.GUIUtility:processEvent(Int32, IntPtr)

    UnityEditor.BuildPlayerWindow+BuildMethodException: 3 errors
    at UnityEditor.BuildPlayerWindow+DefaultBuildMethods.BuildPlayer (BuildPlayerOptions options) [0x0020e] in C:\buildslave\unity\build\Editor\Mono\BuildPlayerWindowBuildMethods.cs:181
    at UnityEditor.BuildPlayerWindow.CallBuildMethods (Boolean askForBuildLocation, BuildOptions defaultBuildOptions) [0x00065] in C:\buildslave\unity\build\Editor\Mono\BuildPlayerWindowBuildMethods.cs:88
    UnityEngine.GUIUtility:processEvent(Int32, IntPtr)
     
  10. peardez

    peardez

    Joined:
    Jun 27, 2017
    Posts:
    10
    Never mind. Simply deleting the tool from the editor fixed the issue. Keep the good work. Thanks!
     
  11. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    Hi @peardez , it seems you haven't placed the script in a folder called Editor. Either that or you're using the new feature called Assembly Definitions https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html and something is wrong with the setup on your end.

    I have added the instructions at the top to clarify.

    Just wanted to point out you'd get this error for any sort of editor script that is not in an Editor folder, because Unity will try to include all non-editor scripts in the build, and UnityEditor assemblies cannot be included in the build.

    Thanks for reporting!
    If you're finding this article helpful, consider my asset Dialogical on for your game dialogues.
     
    twobob likes this.
  12. zero_null

    zero_null

    Joined:
    Mar 11, 2014
    Posts:
    159
    Hello, Honestly the tool is amazing as it's free (Can't complain) but can you please tell what really happens when I select allot of objects?
    The tool creates mesh in a manner that it's connecting like thorough a line.
     
  13. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    Hi zero_null,
    Not quite sure I understand the question, but it seems you're getting some kind of artifact. The combining part is handled by the call to https://docs.unity3d.com/ScriptReference/Mesh.CombineMeshes.html so this part is handled by the engine. I suggest other techniques for lowering draw calls, as this can be something connected to the specific model (I see you have other non-problematic merges).
     
  14. Dreamcube017

    Dreamcube017

    Joined:
    Dec 20, 2009
    Posts:
    253
    I tried this out and it seemed like it'd be useful, but upon running the script, it mangled the mesh.

    Before:
    upload_2018-4-30_3-51-51.png

    After:
    upload_2018-4-30_3-53-40.png

    I don't think I did anything wrong. However, I am using Unity 2017.2. In Blender this is all one Mesh, but Unity sees it as many which is why this script could be really useful.

    Thanks in advance!
     
  15. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    Seems like the same problem from above. Didn't run into it in earlier versions of Unity. It is probable that there is some kind of graphics-related reason I don't know about makes Unity see it as different objects, so this way of combining is inappropriate. I guess you could try to merge submeshes in Blender.
     
  16. omid66

    omid66

    Joined:
    Aug 28, 2017
    Posts:
    2
    @grubarec Thanks so much, it worked like a charm.
     
    Gru likes this.
  17. bigdaddio

    bigdaddio

    Joined:
    May 18, 2009
    Posts:
    220
    This works great. If you have some issue where the meshes are getting mangled or making weird connections, split it up. I have things like I made a combined mesh for some intricate columns. Then I put all my columns in and combine them. So do it in steps and eventually have one mesh.
     
    Gru and twobob like this.
  18. dienat

    dienat

    Joined:
    May 27, 2016
    Posts:
    417
    I tried it using several spheres and they appear connected by a mesh instead just showing the spheres without being connected
    upload_2018-9-18_18-38-5.png
     
  19. Worldmy

    Worldmy

    Joined:
    Jan 12, 2017
    Posts:
    1
    Awesome !! Thanks of tons.:)
     
    Gru likes this.
  20. voxelholic

    voxelholic

    Joined:
    Feb 2, 2016
    Posts:
    6
    I was just about to write the same thing but I found this and it worked perfectly! Thank you for sharing.
     
    Gru likes this.
  21. Revisia

    Revisia

    Joined:
    Jun 7, 2019
    Posts:
    3
    Hello, this looks like a great tool. But it isn't letting me create mesh colliders from the combined mesh.
     
  22. is3d_1

    is3d_1

    Joined:
    Mar 11, 2020
    Posts:
    2
    My contribution :
    1- The source code has moved to github : https://github.com/sirgru/MeshCombineWizard
    2- There have been a number of post mentioning merging problems that resulted in mangled meshes. This situation is caused by combined meshes that contain more than 65536 vertices, as pointed out here https://stackoverflow.com/questions/51804799/combine-meshes-in-unity-at-runtime.
    3- I have modified the original script to manage this limit. The following version works correctly in Unity 2019.3
    Code (CSharp):
    1.  
    2. using UnityEngine;
    3. using UnityEditor;
    4. using System.Collections.Generic;
    5. using System.Linq;
    6.  
    7. public class MeshCombineWizard : ScriptableWizard
    8. {
    9.     public GameObject parentOfObjectsToCombine;
    10.  
    11.     [MenuItem("E.S. Tools/Mesh Combine Wizard")]
    12.     static void CreateWizard()
    13.     {
    14.         ScriptableWizard.DisplayWizard<MeshCombineWizard>("Mesh Combine Wizard");
    15.     }
    16.  
    17.     void OnWizardCreate()
    18.     {
    19.         if (parentOfObjectsToCombine == null) return;
    20.  
    21.         Vector3 originalPosition = parentOfObjectsToCombine.transform.position;
    22.         parentOfObjectsToCombine.transform.position = Vector3.zero;
    23.  
    24.         MeshFilter[] meshFilters = parentOfObjectsToCombine.GetComponentsInChildren<MeshFilter>();
    25.         Dictionary<Material, List<MeshFilter>> materialToMeshFilterList = new Dictionary<Material, List<MeshFilter>>();
    26.         List<GameObject> combinedObjects = new List<GameObject>();
    27.  
    28.         for (int i = 0; i < meshFilters.Length; i++)
    29.         {
    30.             var materials = meshFilters[i].GetComponent<MeshRenderer>().sharedMaterials;
    31.             if (materials == null) continue;
    32.             if (materials.Length > 1)
    33.             {
    34.                 parentOfObjectsToCombine.transform.position = originalPosition;
    35.                 Debug.LogError("Objects with multiple materials on the same mesh are not supported. Create multiple meshes from this object's sub-meshes in an external 3D tool and assign separate materials to each.");
    36.                 return;
    37.             }
    38.             var material = materials[0];
    39.             if (materialToMeshFilterList.ContainsKey(material)) materialToMeshFilterList[material].Add(meshFilters[i]);
    40.             else materialToMeshFilterList.Add(material, new List<MeshFilter>() { meshFilters[i] });
    41.         }
    42.  
    43.  
    44.         // NC 03/2020 changes the loop to create meshes smaller than 65536 vertices
    45.         foreach (var entry in materialToMeshFilterList)
    46.         {
    47.             // get list of each meshes order by number of vertices
    48.             List<MeshFilter> meshesWithSameMaterial = entry.Value.OrderByDescending(mf => mf.sharedMesh.vertexCount).ToList();
    49.             // split into bins of 65536 vertices max
    50.             while (meshesWithSameMaterial.Count > 0)
    51.             {
    52.                 string materialName = entry.Key.ToString().Split(' ')[0];
    53.                 List<MeshFilter> meshBin = new List<MeshFilter>();
    54.                 meshBin.Add(meshesWithSameMaterial[0]);
    55.                 meshesWithSameMaterial.RemoveAt(0);
    56.                 // add meshes in bin until 65536 vertices is reached
    57.                 for (int i = 0; i < meshesWithSameMaterial.Count; i++)
    58.                 {
    59.                     if (meshBin.Sum(mf => mf.sharedMesh.vertexCount) + meshesWithSameMaterial[i].sharedMesh.vertexCount < 65536)
    60.                     {
    61.                         meshBin.Add(meshesWithSameMaterial[i]);
    62.                         meshesWithSameMaterial.RemoveAt(i);
    63.                         i--;
    64.                     }
    65.                 }
    66.  
    67.                 // merge this bin
    68.                 CombineInstance[] combine = new CombineInstance[meshBin.Count];
    69.                 for (int i = 0; i < meshBin.Count; i++)
    70.                 {
    71.                     combine[i].mesh = meshBin[i].sharedMesh;
    72.                     combine[i].transform = meshBin[i].transform.localToWorldMatrix;
    73.                 }
    74.                 Mesh combinedMesh = new Mesh();
    75.                 combinedMesh.CombineMeshes(combine);
    76.  
    77.                 // save the mesh
    78.                 materialName += "_" + combinedMesh.GetInstanceID();
    79.                 if (meshBin.Count > 1)
    80.                 {
    81.                     AssetDatabase.CreateAsset(combinedMesh, "Assets/CombinedMeshes_" + materialName + ".asset"); ;
    82.                 }
    83.  
    84.                 // assign the mesh to a new go
    85.                 string goName = (materialToMeshFilterList.Count > 1) ? "CombinedMeshes_" + materialName : "CombinedMeshes_" + parentOfObjectsToCombine.name;
    86.                 GameObject combinedObject = new GameObject(goName);
    87.                 var filter = combinedObject.AddComponent<MeshFilter>();
    88.                 if (meshBin.Count > 1)
    89.                 {
    90.                     filter.sharedMesh = combinedMesh;
    91.                 }
    92.                 else
    93.                 {
    94.                     filter.sharedMesh = meshBin[0].sharedMesh; // the original mesh
    95.                 }
    96.                 var renderer = combinedObject.AddComponent<MeshRenderer>();
    97.                 renderer.sharedMaterial = entry.Key;
    98.                 combinedObjects.Add(combinedObject);
    99.             }
    100.         }
    101.  
    102.         GameObject resultGO = null;
    103.         if (combinedObjects.Count > 1)
    104.         {
    105.             resultGO = new GameObject("CombinedMeshes_" + parentOfObjectsToCombine.name);
    106.             foreach (var combinedObject in combinedObjects) combinedObject.transform.parent = resultGO.transform;
    107.         }
    108.         else
    109.         {
    110.             resultGO = combinedObjects[0];
    111.         }
    112.  
    113.         Object prefab = PrefabUtility.CreateEmptyPrefab("Assets/" + resultGO.name + ".prefab");
    114.         PrefabUtility.ReplacePrefab(resultGO, prefab, ReplacePrefabOptions.ConnectToPrefab);
    115.  
    116.         parentOfObjectsToCombine.SetActive(false);
    117.         parentOfObjectsToCombine.transform.position = originalPosition;
    118.         resultGO.transform.position = originalPosition;
    119.     }
    120. }
    121.  
     
    Last edited: Mar 11, 2020
    DotusX and heXzoN like this.
  23. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    @is3d_1 I would integrate this into the main repo, but I would like to form a better testing procedure and make it part of the repo. Do you have any links at hand to publicly available 3D assets with over 65k verts?
     
  24. is3d_1

    is3d_1

    Joined:
    Mar 11, 2020
    Posts:
    2
    @Gru I have discovered that you can use a 32 bit index buffer format and combine the meshes into a single one if you set this parameter:
    Code (CSharp):
    1.  
    2. Mesh m = new Mesh { indexFormat = UnityEngine.Rendering.IndexFormat.UInt32 };
    3. ...
    4. m.CombineMeshes(...);
    5.  
     
    Gru likes this.
  25. Gru

    Gru

    Joined:
    Dec 23, 2012
    Posts:
    142
    @is3d_1 Thanks for the tip, that's the simplest solution.

    I have updated the script a bit and fixed the said issue, which I can confirm was the cause of garbled meshes. However, there are slight cautions with using this technique. I will paste the documentation from https://docs.unity3d.com/2017.4/Documentation/ScriptReference/Mesh-indexFormat.html

    Index buffer can either be 16 bit (supports up to 65535 vertices in a mesh), or 32 bit (supports up to 4 billion vertices). Default index format is 16 bit, since that takes less memory and bandwidth.

    Note that GPU support for 32 bit indices is not guaranteed on all platforms; for example Android devices with Mali-400 GPU do not support them. When using 32 bit indices on such a platform, a warning message will be logged and mesh will not render.

    The script offers a Boolean switch to select the format of the result.
     
  26. barge9

    barge9

    Joined:
    May 13, 2015
    Posts:
    12
    Thank you very much, it worked great for me too!!! :)
     
  27. GamesbyJP

    GamesbyJP

    Joined:
    May 20, 2018
    Posts:
    62
    Hey, great tool!
    Gotta ask, is it possible to make lightmaps work if there are multiple instances of the same object before the combine?
    I'm currently in a situation where the lightbake is the same for every instance of an object.
     
  28. Cyclon183

    Cyclon183

    Joined:
    May 4, 2020
    Posts:
    1
    Bruh thank you soooooo much
    I've been trying to figure out how to do this for hours before coming across this
    Great tool thank you
     
  29. SOSolacex

    SOSolacex

    Joined:
    Jan 10, 2018
    Posts:
    121
    This is a great tool. Would love it if it didn't overwrite a previously combined mesh with the same name though. For example if you have multiple houses and start combining everything under the gameobject called "Roof" then do the same thing for another house, the latter will overwrite the former and thus the former will disappear.

    If possible, please have it rename things and count upwards like unity does.
    Either way, the tool is still great, but yeah be careful that you don't lose your stuff.. unfortunately it took me too long to realize haha
     
  30. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    This should be built into Unity as a prefab building option, they you could add elements to make a prefab and have Unity combine them into a single mesh with a combined material and textures at the click of a button. Or on the fly so you develop and design prefabs that are then combined prior to a run or build.
     
  31. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    Question would it be possible to combine textures and shaders?
     
  32. nicolemadadair357

    nicolemadadair357

    Joined:
    Jun 10, 2020
    Posts:
    1
    This tool was exactly what I was looking for, so THANK YOU!! However, as an Unreal user who must use Unity for a client, I have to say, this is one of the many reasons why Unity is just never going to be at the level of Unreal. I have to create a script just to merge a cube and sphere?!? In Unreal I can click a button and have a whole host of parameters to nuance the ways I merge those objects. Seriously, thank you, but also I just don't get Unity...
     
  33. sleepy_noa

    sleepy_noa

    Joined:
    Dec 14, 2022
    Posts:
    1
    I wish there was a tutorial on how to add this tool into unity and how to use it
     
  34. Zvande92

    Zvande92

    Joined:
    Oct 19, 2022
    Posts:
    1
    You create a folder in your project's "Assets" folder called "Editor" and drop the "MeshCombineWizard" script into it. It should then show in the menu bar at the top under the "Ennoble Tools" dropdown.

    Use of the tool is extremely straightforward after that. Select the parent object containing the child objects you want to combine, and leave the 32-bit option off unless it screws up the output mesh; this means you have more than 65k vertices or whatever and need to use 32-bit. The default has better performance, afaik.

    You do not need to specify a folder, but you should place your outputs in an appropriate asset folder afterwards if you do not.