Unity中合并Mesh生成LOD

在使用Unity进行项目开发过程中,通过工业设计软件导出的模型一般都具有较多的零件,如果放入Unity中不进行处理,由于Batch数量的增加会严重降低帧率,影响应用的运行效率,如果不需要改变模型精细度(减面),可以将这些小零件合并成统一的整体,当需要对零件进行交互时,查看详细的单体零件,当看整体设备时,则可以将设备合并为一个整体,从而在一定程度上提高运行效率。
合并物体生成LOD后的效果
代码如下

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;
    }
}

使用时,直接选中需要合并的物体,然后点击菜单中的“Tool/ModelOptimization/MergeWithLODSetting”即可实现合并和生成LOD的功能。

也可以直接用Mesh Baker插件来完成。

猜你喜欢

转载自blog.csdn.net/beihuanlihe130/article/details/96029900