51.性能优化-CPU-图形-存储合并的网络
51.1 知识点
存储合并的网格
上节课是运行时合并网格。实际开发中,有时需要在编辑阶段就合并网格并存储下来,以减少运行时合并开销。
可以基于上节课的思路改造,把合并后的网格保存为资源文件,再决定如何使用。
主要流程
要把合并网格做成编辑器功能,核心流程是:
- 添加功能入口(菜单项)。
- 获取当前选中的对象。
- 合并选中对象子物体的网格。
- 存储合并后的网格资源。
注意事项
该功能需要用到 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");
运行效果
运行逻辑可以看到存储的网格

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

更多思考
- 碰撞器问题
拿到网格后可以不直接销毁gameObject,而是只移除过滤器或渲染器,保留碰撞效果。 - 预设体问题
把合并后的对象做成预设体,方便后续复用。
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