Merge Mesh in Unity to generate LOD

In the process of using Unity for project development, the models exported by industrial design software generally have more parts. If they are put into Unity and not processed, the increase in the number of batches will seriously reduce the frame rate and affect the operating efficiency of the application. If you don’t need to change the fineness of the model (reduce the surface), you can merge these small parts into a unified whole. When you need to interact with the parts, you can view the detailed single parts. When looking at the overall equipment, you can merge the equipment into A whole, thus improving operating efficiency to a certain extent.
The effect after merging objects to generate LOD
code show as below

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.Linq;
using System.IO;
using System.Text;

public class AutoMeshBakerEditor : Editor
{
    /// <summary>
    /// 所有有材质的物体
    /// </summary>
    public class AllMeshWithMaterial
    {
        /// <summary>
        /// 物体的Mesh
        /// </summary>
        public Mesh mesh;
        /// <summary>
        /// 物体的材质
        /// </summary>
        public Material material;
        /// <summary>
        /// 
        /// </summary>
        public Matrix4x4 matrix4X4;
    }

    /// <summary>
    /// 有相同材质的Mesh
    /// </summary>
    public class MeshesWithSameMaterial
    {
        public List<Mesh> WaitToMergeMeshes;
        public List<Matrix4x4> WaitToMergeMeshesMatrixList;
        public Material SameMaterial;

        public MeshesWithSameMaterial()
        {
            WaitToMergeMeshes = new List<Mesh>();
            WaitToMergeMeshesMatrixList = new List<Matrix4x4>();
        }
    }


    static List<AllMeshWithMaterial> subMeshesWithMaterialsSplitedList;

    static string BaseSavedPath;



    private static void CheckSelectionName(Transform trans)
    {
        trans.name = CheckFileNameLegality(trans.name, StringType.FileName);
    }



    [MenuItem("Tool/ModelOptimization/MergeWithLODSetting")]
    public static void MergeWithLODSetting()
    {
        //Transform[] transs = Selection.transforms;
        //Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel | SelectionMode.ExcludePrefab);
        Transform[] transforms = Selection.GetTransforms(SelectionMode.TopLevel);
        foreach (var item in transforms)
        {
            CheckSelectionName(item);
            AutoMeshBakerEditorFunction(item);
            LODSettings(item);
        }


    }

    //[MenuItem("Tool/ModelOptimization/MergeWithoutLODSetting")]
    //public static void MergeWithoutLODSetting()
    //{
    //    CheckSelectionName(Selection.activeTransform);
    //    AutoMeshBakerEditorFunction(Selection.activeTransform);
    //}


    //[MenuItem("Tool/ModelOptimization/MergeObjectsWithLODByTag")]
    //public static void MergeObjectsWithLODByTag()
    //{
    //    GameObject[] objects = GameObject.FindGameObjectsWithTag("Merge");
    //    foreach (var item in objects)
    //    {
    //        CheckSelectionName(item.transform);
    //        AutoMeshBakerEditorFunction(item.transform);
    //        LODSettings(item.transform);
    //    }
    //}




    /// <summary>
    /// 调用Mesh合并方法的入口
    /// </summary>
    /// <param name="selectionTrans"></param>
    public static void AutoMeshBakerEditorFunction(Transform selectionTrans)
    {

        GetSelectionMeshAndMaterials(selectionTrans);

        List<MeshesWithSameMaterial> WaitToMergeMeshesWithMaterial = SplitMeshesWithMaterial();

        MergedSubMeshResult(WaitToMergeMeshesWithMaterial, selectionTrans);
    }

    /// <summary>
    /// 获取选中物体的所有Mesh和Material
    /// </summary>
    /// <param name="selectedObject"></param>
    private static void GetSelectionMeshAndMaterials(Transform selectedObject)
    {
        MeshFilter[] meshFilters = selectedObject.GetComponentsInChildren<MeshFilter>();
        List<Mesh> subMeshes = new List<Mesh>();
        List<AllMeshWithMaterial> subMeshWithMaterials = new List<AllMeshWithMaterial>();
        List<Matrix4x4> subMatrix4X4 = new List<Matrix4x4>();

        for (int i = 0; i < meshFilters.Length; i++)
        {
            List<Mesh> meshes = GetAllSubMeshAsIsolatedMeshes(meshFilters[i].sharedMesh);
            subMeshes.AddRange(meshes);
            foreach (var item in meshes)
                subMatrix4X4.Add(meshFilters[i].transform.localToWorldMatrix);
        }
        MeshRenderer[] meshRenderers = selectedObject.GetComponentsInChildren<MeshRenderer>();
        List<Material> subMaterials = new List<Material>();
        foreach (var meshRenderer in meshRenderers)
        {
            subMaterials.AddRange(meshRenderer.sharedMaterials);
        }

        if (subMeshes.Count != subMaterials.Count || subMeshes.Count != subMatrix4X4.Count || subMatrix4X4.Count != subMaterials.Count)
        {
            return;
        }

        subMeshesWithMaterialsSplitedList = new List<AllMeshWithMaterial>();

        foreach (var item in subMeshes)
        {
            AllMeshWithMaterial allMeshWithMaterial = new AllMeshWithMaterial();
            allMeshWithMaterial.mesh = item;
            allMeshWithMaterial.material = subMaterials[subMeshes.IndexOf(item)];
            allMeshWithMaterial.matrix4X4 = subMatrix4X4[subMeshes.IndexOf(item)];
            subMeshesWithMaterialsSplitedList.Add(allMeshWithMaterial);
        }


    }




    /// <summary>
    /// 将Mesh根据不同的Material分割开来
    /// </summary>
    /// <returns></returns>
    private static List<MeshesWithSameMaterial> SplitMeshesWithMaterial()
    {
        List<MeshesWithSameMaterial> meshesWithSameMaterialsSplitResult = new List<MeshesWithSameMaterial>();

        if (subMeshesWithMaterialsSplitedList == null)
        {
            return null;
        }

        IEnumerable<IGrouping<Material, AllMeshWithMaterial>> groupResult = subMeshesWithMaterialsSplitedList.GroupBy(c => c.material);


        foreach (IGrouping<Material, AllMeshWithMaterial> group in groupResult)
        {
            MeshesWithSameMaterial meshesWithSameMaterial = new MeshesWithSameMaterial();
            List<Mesh> meshes = new List<Mesh>();
            List<Matrix4x4> matrix4X4s = new List<Matrix4x4>();
            foreach (var item in group)
            {
                meshes.Add(item.mesh);
                matrix4X4s.Add(item.matrix4X4);
            }
            meshesWithSameMaterial.WaitToMergeMeshes = meshes;

            meshesWithSameMaterial.SameMaterial = group.Key;

            meshesWithSameMaterial.WaitToMergeMeshesMatrixList = matrix4X4s;

            meshesWithSameMaterialsSplitResult.Add(meshesWithSameMaterial);
        }

        return meshesWithSameMaterialsSplitResult;

    }




    /// <summary>
    /// 合并Mesh
    /// </summary>
    /// <param name="waitToMergeMeshesWithMaterial"></param>
    /// <param name="selectionTrans">当前选中的物体</param>
    private static void MergedSubMeshResult(List<MeshesWithSameMaterial> waitToMergeMeshesWithMaterial, Transform selectionTrans)
    {
        List<AllMeshWithMaterial> mergedSubMeshWithMaterials = new List<AllMeshWithMaterial>();

        Material[] sharedMaterials = new Material[waitToMergeMeshesWithMaterial.Count];
        for (int i = 0; i < sharedMaterials.Length; i++)
        {
            sharedMaterials[i] = waitToMergeMeshesWithMaterial[i].SameMaterial;
        }

        CombineInstance[] FinalCombines = new CombineInstance[waitToMergeMeshesWithMaterial.Count];

        CombineInstance[] combine;

        foreach (var item in waitToMergeMeshesWithMaterial)
        {
            combine = new CombineInstance[item.WaitToMergeMeshes.Count];

            foreach (var mesh in item.WaitToMergeMeshes)
            {
                combine[item.WaitToMergeMeshes.IndexOf(mesh)].mesh = mesh;
                combine[item.WaitToMergeMeshes.IndexOf(mesh)].transform = waitToMergeMeshesWithMaterial[waitToMergeMeshesWithMaterial.IndexOf(item)].WaitToMergeMeshesMatrixList[item.WaitToMergeMeshes.IndexOf(mesh)];
            }
            Mesh TempMesh = new Mesh();
            TempMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
            TempMesh.CombineMeshes(combine, true);

            FinalCombines[waitToMergeMeshesWithMaterial.IndexOf(item)].mesh = TempMesh;

            GameObject obj1 = new GameObject();
            MeshFilter mf1 = obj1.AddComponent<MeshFilter>();
            MeshRenderer mr1 = obj1.AddComponent<MeshRenderer>();


            mf1.sharedMesh = TempMesh;
            FinalCombines[waitToMergeMeshesWithMaterial.IndexOf(item)].transform = mf1.transform.localToWorldMatrix;

            DestroyImmediate(obj1);
        }

        //为合并后的Mesh创建物体
        GameObject obj = new GameObject();
        MeshFilter mf = obj.AddComponent<MeshFilter>();
        MeshRenderer mr = obj.AddComponent<MeshRenderer>();

        Mesh FinalMesh = new Mesh();
        FinalMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
        FinalMesh.CombineMeshes(FinalCombines, false);

        FinalMesh.RecalculateBounds();
        FinalMesh.RecalculateNormals();
        FinalMesh.RecalculateTangents();

        //检查文件名是否合法
        string selectionTransName = CheckFileNameLegality(selectionTrans.name + "_LOD_1", StringType.FileName);


        obj.name = selectionTransName;

        string parentPath = GetCurrentMergedObjectParentPath(selectionTrans.gameObject);

        //保存生成的Mesh到Asset中
        SaveSourceMeshToAsset(FinalMesh, parentPath, obj.name, ".asset");

        mf.sharedMesh = FinalMesh;
        mr.sharedMaterials = sharedMaterials;

    }

    /// <summary>
    /// 文件名合法问题有待解决
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    private static string GetCurrentMergedObjectParentPath(GameObject obj)
    {
        string parentPath = "/";
        Transform parent = obj.transform;
        while (parent != null)
        {
            parentPath = "/" + CheckFileNameLegality(parent.name, StringType.FileName) + parentPath;
            parent = parent.parent;
        }
        parentPath = CheckFileNameLegality(parentPath, StringType.PathName);

        return parentPath;
    }


    private enum StringType
    {
        FileName,
        PathName,
    }


    /// <summary>
    /// 检查文件名是否合法
    /// </summary>
    /// <param name="name"></param>
    /// <param name="stringType"></param>
    /// <returns></returns>
    private static string CheckFileNameLegality(string name, StringType stringType)
    {
        string result = name;
        switch (stringType)
        {
            case StringType.FileName:
                foreach (char rInvalidChar in Path.GetInvalidFileNameChars())
                    result = result.Replace(rInvalidChar, '+');
                break;
            case StringType.PathName:
                foreach (char rInvalidChar in Path.GetInvalidPathChars())
                    result = result.Replace(rInvalidChar, '+');
                while (result.IndexOf(" /") != -1)
                {
                    result = result.Replace(" /", "/");
                }
                break;
        }
        return result;
    }


    /// <summary>
    /// 将Mesh数据保存到Asset中
    /// </summary>
    /// <param name="source"></param>
    /// <param name="parentPath"></param>
    /// <param name="fileName"></param>
    /// <param name="extentionName"></param>
    private static void SaveSourceMeshToAsset(Mesh source, string parentPath, string fileName, string extentionName)
    {
        BaseSavedPath = Application.dataPath + "/" + "MergeResult";

        string finalFileFullPath = FolderPathChecker(BaseSavedPath + parentPath, fileName, extentionName);

        finalFileFullPath = finalFileFullPath.Replace(Application.dataPath, "Assets");

        AssetDatabase.CreateAsset(source, finalFileFullPath);

        AssetDatabase.Refresh();
    }


    /// <summary>
    /// 获取所有的子Mesh
    /// </summary>
    /// <param name="mesh"></param>
    /// <returns></returns>
    private static List<Mesh> GetAllSubMeshAsIsolatedMeshes(Mesh mesh)
    {
        List<Mesh> meshesToReturn = new List<Mesh>();
        if (!mesh)
        {
            Debug.LogError("No mesh passed into GetAllSubMeshAsIsolatedMeshes!");
            return meshesToReturn;
        }
        int submeshCount = mesh.subMeshCount;

        Mesh m1;
        for (int i = 0; i < submeshCount; i++)
        {
            m1 = new Mesh();
            //设置Mesh为32位,默认为16位,当Mesh面数的的时候会出错
            m1.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
            m1 = mesh.GetSubmesh(i);
            meshesToReturn.Add(m1);
        }
        return meshesToReturn;
    }





    /// <summary>
    /// 生成LOD模型
    /// </summary>
    /// <param name="trans"></param>
    private static void LODSettings(Transform trans)
    {
        GameObject obj = new GameObject(trans.name + "_Full");
        if (trans.parent != null)
            obj.transform.parent = trans.parent;
        obj.transform.position = trans.position;
        obj.transform.rotation = trans.rotation;
        obj.transform.localScale = trans.localScale;

        trans.SetParent(obj.transform);


        Transform LODTrans = GameObject.Find(trans.name + "_LOD_1").transform;

        LODTrans.SetParent(obj.transform);

        LODGroup lODGroup = obj.AddComponent<LODGroup>();

        MeshRenderer[] lodObjectMRs;
        lodObjectMRs = obj.transform.Find(trans.name + "_LOD_1").GetComponents<MeshRenderer>();
        //设置LOD参数
        LOD[] lODs = new LOD[2] { new LOD(0.8f, trans.GetComponentsInChildren<MeshRenderer>()), new LOD(0.25f, lodObjectMRs) };
        lODGroup.SetLODs(lODs);

    }



    /// <summary>
    /// 检查文件路径
    /// </summary>
    /// <param name="path"></param>
    /// <param name="fileName"></param>
    /// <param name="extentionName"></param>
    /// <returns></returns>
    private static string FolderPathChecker(string path, string fileName, string extentionName)
    {
        CheckFolderExist(path);
        string finalFileName = CheckFileExist(path + fileName, extentionName);
        return finalFileName;
    }

    /// <summary>
    /// 判断文件路径是否存在
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    private static bool CheckFolderExist(string path)
    {
        if (!Directory.Exists(path))//如果不存在就创建file文件夹                               
            Directory.CreateDirectory(path);//创建该文件夹              
        return true;
    }

    /// <summary>
    /// 判断文件是否存在
    /// </summary>
    /// <param name="pathAndFileName"></param>
    /// <param name="extentionName"></param>
    /// <returns></returns>
    private static string CheckFileExist(string pathAndFileName, string extentionName)
    {
        string finalFileName = pathAndFileName + extentionName;
        int count = 0;
        while (File.Exists(finalFileName))
        {
            count++;
            finalFileName = pathAndFileName + count.ToString("_0000") + extentionName;
        }
        return finalFileName;
    }


}



/// <summary>
/// Mesh相关参数
/// </summary>
public static class MeshExtension
{
    private class Vertices
    {
        /// <summary>
        /// 顶点
        /// </summary>
        List<Vector3> verts = null;

        List<Vector2> uv1 = null;
        List<Vector2> uv2 = null;
        List<Vector2> uv3 = null;
        List<Vector2> uv4 = null;

        /// <summary>
        /// 法线
        /// </summary>
        List<Vector3> normals = null;
        /// <summary>
        /// 切线
        /// </summary>
        List<Vector4> tangents = null;
        /// <summary>
        /// 颜色
        /// </summary>
        List<Color32> colors = null;
        /// <summary>
        /// 骨骼权重
        /// </summary>
        List<BoneWeight> boneWeights = null;

        public Vertices()
        {
            verts = new List<Vector3>();
        }
        public Vertices(Mesh aMesh)
        {
            verts = CreateList(aMesh.vertices);
            uv1 = CreateList(aMesh.uv);
            uv2 = CreateList(aMesh.uv2);
            uv3 = CreateList(aMesh.uv3);
            uv4 = CreateList(aMesh.uv4);
            normals = CreateList(aMesh.normals);
            tangents = CreateList(aMesh.tangents);
            colors = CreateList(aMesh.colors32);
            boneWeights = CreateList(aMesh.boneWeights);
        }

        private List<T> CreateList<T>(T[] aSource)
        {
            if (aSource == null || aSource.Length == 0)
                return null;
            return new List<T>(aSource);
        }
        private void Copy<T>(ref List<T> aDest, List<T> aSource, int aIndex)
        {
            if (aSource == null)
                return;
            if (aDest == null)
                aDest = new List<T>();
            aDest.Add(aSource[aIndex]);
        }
        public int Add(Vertices aOther, int aIndex)
        {
            int i = verts.Count;
            Copy(ref verts, aOther.verts, aIndex);
            Copy(ref uv1, aOther.uv1, aIndex);
            Copy(ref uv2, aOther.uv2, aIndex);
            Copy(ref uv3, aOther.uv3, aIndex);
            Copy(ref uv4, aOther.uv4, aIndex);
            Copy(ref normals, aOther.normals, aIndex);
            Copy(ref tangents, aOther.tangents, aIndex);
            Copy(ref colors, aOther.colors, aIndex);
            Copy(ref boneWeights, aOther.boneWeights, aIndex);
            return i;
        }
        public void AssignTo(Mesh aTarget)
        {
            aTarget.SetVertices(verts);
            if (uv1 != null) aTarget.SetUVs(0, uv1);
            if (uv2 != null) aTarget.SetUVs(1, uv2);
            if (uv3 != null) aTarget.SetUVs(2, uv3);
            if (uv4 != null) aTarget.SetUVs(3, uv4);
            if (normals != null) aTarget.SetNormals(normals);
            if (tangents != null) aTarget.SetTangents(tangents);
            if (colors != null) aTarget.SetColors(colors);
            if (boneWeights != null) aTarget.boneWeights = boneWeights.ToArray();
        }
    }


    /// <summary>
    /// 获取Mesh相关属性
    /// </summary>
    /// <param name="aMesh"></param>
    /// <param name="aSubMeshIndex"></param>
    /// <returns></returns>
    public static Mesh GetSubmesh(this Mesh aMesh, int aSubMeshIndex)
    {
        if (aSubMeshIndex < 0 || aSubMeshIndex >= aMesh.subMeshCount)
            return null;
        int[] indices = aMesh.GetTriangles(aSubMeshIndex);
        Vertices source = new Vertices(aMesh);
        Vertices dest = new Vertices();
        Dictionary<int, int> map = new Dictionary<int, int>();
        int[] newIndices = new int[indices.Length];
        for (int i = 0; i < indices.Length; i++)
        {
            int o = indices[i];
            int n;
            if (!map.TryGetValue(o, out n))
            {
                n = dest.Add(source, o);
                map.Add(o, n);
            }
            newIndices[i] = n;
        }
        Mesh m = new Mesh();
        //提高顶点数
        m.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
        dest.AssignTo(m);
        m.triangles = newIndices;
        return m;
    }
}

When using, directly select the objects to be merged, and then click "Tool/ModelOptimization/MergeWithLODSetting" in the menu to realize the function of merging and generating LOD.

It can also be done directly with the Mesh Baker plugin.

Guess you like

Origin blog.csdn.net/beihuanlihe130/article/details/96029900