Unity合并Mesh并保存在本地

这是一个在Unity编辑器中使用的工具,用于合并具有不同材质的Mesh对象。用户可以设置选项如生成光照贴图UV、优化Mesh缓冲区、备份原始对象以及是否添加MeshCollider。该脚本检查顶点数量限制,并提供了32位索引选项以处理大量顶点的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// Easy Mesh Combiner
// Aliyeredon@gmail.com
// Originally written at Jan 2022
// Used the below link for combining algorithm (user @Bunzaga):
// https://answers.unity.com/questions/196649/combinemeshes-with-different-materials.html

#if UNITY_EDITOR
using UnityEngine;
using UnityEditor;
using System;
using System.Collections;
using System.Collections.Generic;

public class Easy_MeshCombineTool : EditorWindow
{

// Options
public bool generateLightmapUV = true;
public bool optimizeMeshBuffer = true;
public bool backUpOriginalOjects = true;
public bool addMeshCollider = false;

// Use 32 bit mesh index for too many vertex models (not optimized for mobile)
public bool use32BitIndex = false;
public string combinedName = "Combined_Models";

// internal variables
GameObject[] selectedOjects;
bool vertexLimitError;
int selectedVertexCounts = 0;
int selectedTrinaglesCount = 0;
public MeshFilter[] mf;

[MenuItem("Window/Easy Mesh Combine Tool")]
static void Init()
{
    // Display window
    Easy_MeshCombineTool window = (Easy_MeshCombineTool)EditorWindow.GetWindow(typeof(Easy_MeshCombineTool));
    window.Show();
}

[MenuItem("Window/SaveMesh")]
static void SaveMesh()
{
    GameObject selectObj = Selection.activeGameObject;
    Mesh ml = selectObj.GetComponent<MeshFilter>().sharedMesh;
    AssetDatabase.CreateAsset(ml, "Assets/"+ml.name+".asset");
}

void OnGUI()
{
    EditorGUILayout.Space();

    // Top label ( window title and version name)
    GUILayout.Label("Easy Mesh Combiner - Update 1.1 Jan 2022", EditorStyles.helpBox);

    EditorGUILayout.Space();

    generateLightmapUV = EditorGUILayout.Toggle("Generate Lightmap UV", generateLightmapUV);

    optimizeMeshBuffer = EditorGUILayout.Toggle("Optimize Mesh Buffer", optimizeMeshBuffer);
    
    EditorGUILayout.Space();

    backUpOriginalOjects = EditorGUILayout.Toggle("Backup Original Objects", backUpOriginalOjects);
    
    addMeshCollider = EditorGUILayout.Toggle("Add Mesh Collider", addMeshCollider);

    EditorGUILayout.Space();
    EditorGUILayout.Space();

    use32BitIndex = EditorGUILayout.Toggle("Use 32 Bit Mesh Index", use32BitIndex);
   
    EditorGUILayout.Space();
   
    combinedName = EditorGUILayout.TextField("Enter Name", combinedName);
    
    EditorGUILayout.Space();
    EditorGUILayout.Space();

    // Show error box when the selected game objects has higher than 65535 vertexs
    if (vertexLimitError)
        EditorGUILayout.HelpBox("Reached to the 16 bit vertex count limit : 65535 \n You can use 32 bit mesh index format \n 16 bit is better for mobile platforms", MessageType.Error);
    else
    {
        if (GUILayout.Button("Make Group and Combine"))
            Combine_Meshes();
    }

    // Actions when selecting game object in the scene/hierarchy
    if (Selection.activeGameObject)
    {

        EditorGUILayout.Space();
        EditorGUILayout.Space();
        EditorGUILayout.Space();

        // Display vertex and triangles count
        EditorGUILayout.HelpBox("Vertex count : " + selectedVertexCounts.ToString(), MessageType.None);
        EditorGUILayout.HelpBox("Trinagles count : " + selectedTrinaglesCount.ToString(), MessageType.None);

        GameObject[] selectedObjects = Selection.gameObjects;
        List<GameObject> allObjects = new List<GameObject>();
        allObjects.Clear();

        // Read all selected game objects and its childs
        for (int a = 0;a< selectedObjects.Length;a++)
        {
            Transform[] childs = selectedObjects[a].GetComponentsInChildren<Transform>();
            for (int c = 0; c < childs.Length; c++)
            { 
                if(childs[c].gameObject.GetComponent<MeshFilter>())
                   allObjects.Add(childs[c].gameObject);
            }
        }

        mf = new MeshFilter[allObjects.Count];

        for (int d = 0; d < allObjects.Count; d++)
        {
            if(allObjects[d].GetComponent<MeshFilter>())
               mf[d] = allObjects[d].GetComponent<MeshFilter>();
        }

        // Display vertex counts
        int vertexCounts = 0;
        int trianglesCount = 0;

        for (int vl = 0; vl < mf.Length; vl++)
        {
            vertexCounts += mf[vl].sharedMesh.vertexCount;
            trianglesCount += mf[vl].sharedMesh.triangles.Length / 3;
        }

        if (vertexCounts > 65535 && !use32BitIndex)
            vertexLimitError = true;
        else
            vertexLimitError = false;

        // Save the vertexs and tringles count
        selectedVertexCounts = vertexCounts;
        selectedTrinaglesCount = trianglesCount;
    }

    
    
}

// Group game objects before combine
void Group_Objects(Transform targetParent)
{
    selectedOjects = Selection.gameObjects;
    for(int a = 0;a< selectedOjects.Length;a++)
    {
        selectedOjects[a].transform.parent = targetParent;

        // Disconnect prefabs before combine
        try
        {
            if (PrefabUtility.IsPartOfAnyPrefab(selectedOjects[a]))
                PrefabUtility.UnpackPrefabInstance(selectedOjects[a], PrefabUnpackMode.Completely, InteractionMode.AutomatedAction);
        }
        catch { }
        }
}

// Main mesh combine function
void Combine_Meshes()
{
    if (vertexLimitError)
        return;

    // Create the group parent
    GameObject target = new GameObject();
    target.name = "Back Up";
    // Group selected game objects before combine
    Group_Objects(target.transform);
    //________________________________________________________________
    // Keep the original model or destroy
    if (backUpOriginalOjects)
    { 
        GameObject keepOriginal;

        keepOriginal = Instantiate(target) as GameObject;

        keepOriginal.name = target.name;
        keepOriginal.transform.position = target.transform.position;
        keepOriginal.transform.rotation = target.transform.rotation;
        keepOriginal.transform.localScale = target.transform.localScale;
        keepOriginal.SetActive(false);                
    }
    //________________________________________________________________
    // Save initialize data
    string originalName = "default";
    originalName = target.GetComponent<Transform>().name;

    //________________________________________________________________
    #region Combine
    // This code is originalay copied from below link and modified by me (user @Bunzaga):
    // https://answers.unity.com/questions/196649/combinemeshes-with-different-materials.html
    ArrayList materials = new ArrayList();
    ArrayList combineInstanceArrays = new ArrayList();
    MeshFilter[] meshFilters = target.GetComponentsInChildren<MeshFilter>();

    foreach (MeshFilter meshFilter in meshFilters)
    {
        MeshRenderer meshRenderer = meshFilter.GetComponent<MeshRenderer>();

        if (!meshRenderer ||
            !meshFilter.sharedMesh ||
            meshRenderer.sharedMaterials.Length != meshFilter.sharedMesh.subMeshCount)
        {
            continue;
        }

        for (int s = 0; s < meshFilter.sharedMesh.subMeshCount; s++)
        {
            int materialArrayIndex = Contains(materials, meshRenderer.sharedMaterials[s].name);
            if (materialArrayIndex == -1)
            {
                materials.Add(meshRenderer.sharedMaterials[s]);
                materialArrayIndex = materials.Count - 1;
            }
            combineInstanceArrays.Add(new ArrayList());

            CombineInstance combineInstance = new CombineInstance();
            combineInstance.transform = meshRenderer.transform.localToWorldMatrix;
            combineInstance.subMeshIndex = s;
            combineInstance.mesh = meshFilter.sharedMesh;
            (combineInstanceArrays[materialArrayIndex] as ArrayList).Add(combineInstance);
        }
    }

    // Get / Create mesh filter & renderer
    MeshFilter meshFilterCombine = target.GetComponent<MeshFilter>();
    if (meshFilterCombine == null)
    {
        meshFilterCombine = target.AddComponent<MeshFilter>();
    }
    MeshRenderer meshRendererCombine = target.GetComponent<MeshRenderer>();
    if (meshRendererCombine == null)
    {
        meshRendererCombine = target.AddComponent<MeshRenderer>();
    }

    
    // Combine by material index into per-material meshes
    // also, Create CombineInstance array for next step
    Mesh[] meshes = new Mesh[materials.Count];
    CombineInstance[] combineInstances = new CombineInstance[materials.Count];

    for (int m = 0; m < materials.Count; m++)
    {
        CombineInstance[] combineInstanceArray = (combineInstanceArrays[m] as ArrayList).ToArray(typeof(CombineInstance)) as CombineInstance[];
        meshes[m] = new Mesh();

        //________________________________________________________________
        // use 32 bit mesh index format for when combined mesh's vertex counts is higher than 65535
        if (use32BitIndex && selectedVertexCounts > 65535)
            meshes[m].indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; // 4 billions vertex counts limit
        else
            meshes[m].indexFormat = UnityEngine.Rendering.IndexFormat.UInt16; // 65535 vertex counts limit

        meshes[m].CombineMeshes(combineInstanceArray, true, true);

        combineInstances[m] = new CombineInstance();
        combineInstances[m].mesh = meshes[m];
        combineInstances[m].subMeshIndex = 0;
    }
            
    // Combine into one
    meshFilterCombine.sharedMesh = new Mesh();
    //________________________________________________________________
    // use 32 bit mesh index format for when combined mesh's vertex counts is higher than 65535
    if (use32BitIndex && selectedVertexCounts > 65535)
        meshFilterCombine.sharedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; // 4 billions vertex counts limit
    else
        meshFilterCombine.sharedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt16; // 65535 vertex counts limit

    meshFilterCombine.sharedMesh.CombineMeshes(combineInstances, false, false);

    // Destroy other meshes
    foreach (Mesh oldMesh in meshes)
    {
        oldMesh.Clear();
        DestroyImmediate(oldMesh);
    }

    // Assign materials
    Material[] materialsArray = materials.ToArray(typeof(Material)) as Material[];
    meshRendererCombine.materials = materialsArray;

    // Destroy original mesh models
    while (target.transform.childCount > 0)
    {
        foreach (Transform child in target.transform)
        {
            DestroyImmediate(child.gameObject);
        }
    }

    #endregion
    //________________________________________________________________
    // Assign the combined game object's name
    target.transform.GetComponent<MeshFilter>().sharedMesh.name = combinedName;
    target.name = combinedName;

    //________________________________________________________________
    // Optimize mesh buffer
    if (optimizeMeshBuffer)
        MeshUtility.Optimize(target.GetComponent<MeshFilter>().sharedMesh);

    // Generate lightmap uv
    if (generateLightmapUV)
        Unwrapping.GenerateSecondaryUVSet(target.transform.GetComponent<MeshFilter>().sharedMesh);

    // Add Mesh Collider
    if(addMeshCollider)
        target.AddComponent<MeshCollider>();

    //________________________________________________________________

    // Use the original position, rotation and scale fro the new combined mesh
    target.transform.position = Vector3.zero;
    target.transform.rotation = Quaternion.identity;
    target.transform.localScale = new Vector3(1,1,1);


}
// Used for materials combination
private int Contains(ArrayList searchList, string searchName)
{
    for (int i = 0; i < searchList.Count; i++)
    {
        if (((Material)searchList[i]).name == searchName)
        {
            return i;
        }
    }
    return -1;
}

}
#endif

### 合并 Mesh 组件的最佳实践 #### 使用 Combine 方法实现静态网格合并 为了有效地减少批处理次数,提高渲染效率,在 Unity 中可以采用 `CombineInstance` 数组来批量组合多个子物体的网格数据。这不仅简化了场景管理还能够显著降低 Draw Call 的数量。 ```csharp using UnityEngine; public class MeshCombiner : MonoBehaviour { void Start() { // 创建用于保存要被合并的对象的信息列表 CombineInstance[] combineInstances = new CombineInstance[transform.childCount]; int index = 0; foreach (Transform child in transform) { if (!child.gameObject.activeInHierarchy || !child.GetComponent<MeshFilter>()) continue; combineInstances[index].mesh = child.GetComponent<MeshFilter>().sharedMesh; combineInstances[index].transform = child.localToWorldMatrix; ++index; } Array.Resize(ref combineInstances, index); // 新建一个空的游戏对象作为承载最终结果的父亲节点 GameObject combinedObject = new GameObject("CombinedMesh"); combinedObject.transform.position = transform.position; // 添加必要的组件 var mf = combinedObject.AddComponent<MeshFilter>(); mf.mesh = new Mesh(); mf.mesh.CombineMeshes(combineInstances); combinedObject.AddComponent<MeshRenderer>(); // 可选:销毁原始子物件以节省内存资源 foreach(Transform child in transform){ Destroy(child.gameObject); } } } ``` 此段代码展示了如何遍历指定父级下的所有子游戏对象,将其各自的 `MeshFilter` 下的网格信息收集起来进行一次性合并操作[^3]。 #### 利用第三方插件 MeshBaker 提升材质利用率 除了上述基础做法之外,对于更复杂的项目而言,还可以借助像 MeshBaker 这样的工具进一步优化材料分配逻辑。通过合理规划共享材质的应用范围,可以在不影响视觉效果的前提下大幅削减所需批次的数量,从而达到更好的性能表现[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值