51.存储手动合并的网格

51.性能优化-CPU-图形-存储合并的网络


51.1 知识点

存储合并的网格

上节课是运行时合并网格。实际开发中,有时需要在编辑阶段就合并网格并存储下来,以减少运行时合并开销。
可以基于上节课的思路改造,把合并后的网格保存为资源文件,再决定如何使用。

主要流程

要把合并网格做成编辑器功能,核心流程是:

  1. 添加功能入口(菜单项)。
  2. 获取当前选中的对象。
  3. 合并选中对象子物体的网格。
  4. 存储合并后的网格资源。

注意事项

该功能需要用到 Unity 编辑器扩展相关知识(UnityEditor)。

实现示例(编辑器工具)

步骤 1:声明工具入口,添加菜单命令

using UnityEditor;
using UnityEngine;

public class CombineMeshTool : MonoBehaviour
{
    [MenuItem("Tools/Mesh/合并所选对象子物体网格")]
    public static void CombineMesh()
    {
    }
}

步骤 2:获取选中对象并做校验

GameObject gameObject = Selection.activeGameObject;
if (gameObject == null)
{
    Debug.LogError("你没有选择任何对象");
    return;
}

步骤 3:收集子物体网格并构建 CombineInstance

MeshFilter[] meshFilterArray = gameObject.GetComponentsInChildren<MeshFilter>();
if (meshFilterArray.Length == 0)
{
    Debug.LogError("你选择的对象的子对象中并没有网格数据");
    return;
}

CombineInstance[] combineInstanceArray = new CombineInstance[meshFilterArray.Length];
for (int index = 0; index < meshFilterArray.Length; index++)
{
    // 得到想要合并的网格信息
    combineInstanceArray[index].mesh = meshFilterArray[index].sharedMesh;

    // 用于将子网格的顶点位置从当前本地空间变换到父对象的本地空间
    combineInstanceArray[index].transform =
        gameObject.transform.worldToLocalMatrix * meshFilterArray[index].transform.localToWorldMatrix;
}

步骤 4:创建新网格并设置顶点索引格式

Mesh mesh = new Mesh();
// 默认是 UInt16,最多支持 65535 个顶点
// 如果合并后的顶点数超过该值,需要改为 UInt32
int totalVertices = 0;
foreach (var item in combineInstanceArray)
{
    // 累加想要合并的小网格的顶点数
    totalVertices += item.mesh.vertexCount;
}
if (totalVertices > 65535)
{
    mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
}

步骤 5:合并网格并写入资源

// 合并网格
mesh.CombineMeshes(combineInstanceArray);

// 重新计算一些数据
mesh.RecalculateBounds();

// 网格合并完成,可以存储为资源文件
AssetDatabase.CreateAsset(mesh, "Assets/MergedMesh.asset");

运行效果

运行逻辑可以看到存储的网格

现在可以创建空物体,添加网格过滤器和渲染器。可以看到效果,还可以添加不同材质。

更多思考

  1. 碰撞器问题
    拿到网格后可以不直接销毁 gameObject,而是只移除过滤器或渲染器,保留碰撞效果。
  2. 预设体问题
    把合并后的对象做成预设体,方便后续复用。

51.2 知识点代码

Lesson51_性能优化_CPU_图形_存储合并的网络.cs

public class Lesson51_性能优化_CPU_图形_存储合并的网络
{
    #region 知识点一 存储合并的网络

    //我们上节课是在运行时合并网格
    //有时候在实际开发中,我们有时可能需要在开发时就合并网格,并将其存储下来
    //这样可以减小在运行时合并的开销
    //因此我们可以基于上节课的代码进行修改
    //将合并后的网格直接作为资源文件存储下来
    //然后自行决定如何使用

    //主要流程:
    //要将合并网格功能作为编辑器功能
    //1.添加功能入口
    //2.获取选中对象
    //3.合并选中对象的子对象中的网格
    //4.存储网格

    //注意:
    //需要用到Unity编辑器拓展相关知识

    #endregion

    #region 知识点二 更多思考

    //1.碰撞器问题 
    // 拿到网格后可以不直接销毁gameObject,而是只移除过滤器或者渲染器。保留碰撞效果
    
    //2.预设体问题
    //把合并后的对象做成预设体

    #endregion
}

CombineMeshTool.cs

using UnityEditor;
using UnityEngine;

public class CombineMeshTool : MonoBehaviour
{
    [MenuItem("Tools/Mesh/合并所选对象子物体网格")]
    public static void CombineMesh()
    {
        //获取当前我在窗口中选择的GameObject对象
        GameObject gameObject = Selection.activeGameObject;
        if (gameObject == null)
        {
            Debug.LogError("你没有选择任何对象");
            return;
        }

        //1.获取子对象中所有的网格组件
        MeshFilter[] meshFilterArray = gameObject.GetComponentsInChildren<MeshFilter>();
        if (meshFilterArray.Length == 0)
        {
            Debug.LogError("你选择的对象的子对象中并没有网格数据");
            return;
        }

        //2.实例化 对应的C9ombineInstance结构体对象数组
        CombineInstance[] combineInstanceArray = new CombineInstance[meshFilterArray.Length];
        for (int index = 0; index < meshFilterArray.Length; index++)
        {
            //得到想要合并的网格信息
            combineInstanceArray[index].mesh = meshFilterArray[index].sharedMesh;
            
            //用于将子网格的顶点位置从当前本地空间变换到 父对象的本地空间
            combineInstanceArray[index].transform =
                gameObject.transform.worldToLocalMatrix * meshFilterArray[index].transform.localToWorldMatrix;

            //利用完了想要合并的网格 我们可以自行处理(销毁、失活)
            //Destroy(meshFilters[i].gameObject);
        }

        //3.创建新网格
        Mesh mesh = new Mesh();
        //该参数默认是Uint16,代表最多支持65535个顶点
        //如果我们的合并的顶点数超过这个值 那么一定要将该参数修改为
        //UInt32,代表最多支持42亿个顶点
        //应该判断合并的顶点是不是超过了 UInt16的限制
        //如果超过了 应该设置为UInt32
        int totalVertices = 0;
        foreach (var item in combineInstanceArray)
        {
            //累加想要合并的小网格的顶点数
            totalVertices += item.mesh.vertexCount;
        }
        if (totalVertices > 65535)
            mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
        
        //4.合并网格
        mesh.CombineMeshes(combineInstanceArray);

        //5.重新计算一些数据
        mesh.RecalculateBounds();

        //网格合并完成 可以存储
        //将合并好的网格 存储到本地 作为网格资源文件
        AssetDatabase.CreateAsset(mesh, "Assets/MergedMesh.asset");
    }
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏