• Unity 动态创建Mesh 基础方法与高级方法


    最近在做项目优化,注意到动态创建Mesh时,Unity提供了一套高级方法用于快速创建模型,特此记录学习一下。

    前言

    关于Mesh的基本概念再次不在阐述,可以参考Unity Mesh 官方文档,介绍的很详细,其中

    基础方法包括:SetVertices、SetNormals、SetUVs、SetTriangles、SetIndices、SetColors、SetTangents、SetBoneWeights
    高级方法包括:SetVertexBufferParams、SetVertexBufferData、SetIndexBufferParams、SetIndexBufferData、SetSubMesh。

    优势

    1. 使用基础方法有个限制,就是Mesh的最大顶点数量不能超过65535,而高级方法则没有这个限制
    2. 高级方法跳过了一些检查,创建速度更快,尤其模型顶点数量较多的情况下,有性能提升,实测时间缩短将近1/3

    示例

    基础方法

    需要提前准备好模型的数据

    属性名含义类型
    vertices顶点坐标Verctor3[]
    normals法线Verctor3[]
    triangles顶点索引int[]
    uv纹理坐标Verctor2[]
    
    	//创建Mesh,并赋值,相当于调用SetVertices、SetNormals、SetTriangles、SetUVs
    	Mesh mesh = new Mesh();
    	mesh.vertices = myMeshes[i].vertices;
    	mesh.normals = myMeshes[i].normals;
    	mesh.triangles = myMeshes[i].triangles;
    	mesh.uv = myMeshes[i].uv;
    	
    	//将Mesh赋值给MeshFilter组件
    	GameObject gameObject = new GameObject();
    	MeshFilter mf = gameObject.AddComponent<MeshFilter>();
    	mf.sharedMesh = mesh;
    	
    	//给模型赋予材质
    	MeshRenderer mr = gameObject.AddComponent<MeshRenderer>();
    	mr.material = material;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    高级方法(推荐)

    同上,先准备好模型的基础数据

    
    	//
    	//顶点属性描述中,添加该模型具有哪些属性,该例中有顶点、法线、一个uv,其中
    	//顶点坐标  Position 用 3 个 Float32 数据表示
    	//法线向量   Normal  用 3 个 Float32 数据表示
    	//纹理坐标 TexCoord0 用 2 个 Float32 数据表示
    	//
    	VertexAttributeDescriptor[] vertexAttributes = new[]{
    	    new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 3),
    	    new VertexAttributeDescriptor(VertexAttribute.Normal, VertexAttributeFormat.Float32, 3),
    	    new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2)
    	};
    	
    	//
    	// 根据顶点数量创建缓冲区
    	// 
    	// 假设创建一个四方面片,则缓冲区数据如下
    	//       顶点          法线           uv
    	//    -5, -5,  0,    0, 0, -1,      0, 0,    //第 1 个顶点
    	//    -5,  5,  0,    0, 0, -1,      0, 1,    //第 2 个顶点
    	//     5, -5,  0,    0, 0, -1,      1, 0,    //第 3 个顶点
    	//     5,  5,  0,    0, 0, -1,      1, 1     //第 4 个顶点
    	//
    	int vertexCount = myMeshes[i].vertices.Length;
    	int bufferLength = 3 + 3 + 2;
    	int vertexAttributeBufferLength = vertexCount * bufferLength;
    	float[] vertexAttributeBuffer = new float[vertexAttributeBufferLength];
    	
    	//
    	// 将准备好的模型数据填充到缓冲区
    	//
    	Vector3[] vertices = myMeshes[i].vertices;
    	Vector3[] normals = myMeshes[i].normals;
    	Vector2[] uv = myMeshes[i].uv;
    	for (int j = 0; j < vertexCount; j++)
    	{
    	    int start = j * bufferLength;
    		
    		//此处 +0 ... +7 的原由。观察四方面片示例
    	    vertexAttributeBuffer[start + 0] = vertices[j].x;
    	    vertexAttributeBuffer[start + 1] = vertices[j].y;
    	    vertexAttributeBuffer[start + 2] = vertices[j].z;
    	
    	    vertexAttributeBuffer[start + 3] = normals[j].x;
    	    vertexAttributeBuffer[start + 4] = normals[j].y;
    	    vertexAttributeBuffer[start + 5] = normals[j].z;
    	
    	    vertexAttributeBuffer[start + 6] = uv[j].x;
    	    vertexAttributeBuffer[start + 7] = uv[j].y;
    	}
    	
    	//将顶点缓冲区写入Mesh
    	Mesh mesh = new Mesh();
    	mesh.SetVertexBufferParams(vertexCount, vertexAttributes);
    	mesh.SetVertexBufferData(vertexAttributeBuffer, 0, 0, vertexAttributeBufferLength, 0);
    	
    	//将顶点索引写入索引缓冲区
    	int[] triangles = myMeshes[i].triangles;
    	int indexCount = triangles.Length;
    	mesh.SetIndexBufferParams(indexCount, IndexFormat.UInt32);
    	mesh.SetIndexBufferData(triangles, 0, 0, indexCount);
    	
    	//每个Mesh至少包含一个SubMesh,也可将上面的缓冲区分开赋值,分别设置到不同的SubMesh
    	mesh.subMeshCount = 1;
    	SubMeshDescriptor subMeshDescriptor = new SubMeshDescriptor(0, indexCount);
    	mesh.SetSubMesh(0, subMeshDescriptor);
    	
    	//高级方法由于跳过Unity检查,缺失Bounds信息,当任意三角面超过相机的裁剪区域时,整个模型会被裁剪掉(消失不见)
    	mesh.RecalculateBounds();
    	
    	//将Mesh赋值给MeshFilter组件
    	GameObject gameObject = new GameObject();
    	MeshFilter mf = gameObject.AddComponent<MeshFilter>();
    	mf.sharedMesh = mesh;
    	
    	//给模型赋予材质
    	MeshRenderer mr = gameObject.AddComponent<MeshRenderer>();
    	mr.material = material;
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    注意!!!

          大家可以做一下测试,使用高级方法创建出来的Mesh,在Scene窗口旋转预览相机(或调整相机的size,在Game视图观察),当模型有三角面超过可视区域后,会突然消失不见

          这是因为,使用高级方法创建出来的Mesh,由于跳过了Unity的检查,Mesh的Bounds信息缺失,这会导致一个现象,当模型的任意三角面不在相机的裁剪区域内时,模型会突然消失(被相机裁剪掉),因此需要调用RecalculateBounds方法计算一下模型的Bounds。

          基础方法创建出来的Mesh,Unity默认会对其进行Bounds计算(不需要调用RecalculateBounds方法),所以不会出现上述情况。

          因此可以推测,当修改了Mesh的顶点位置后,都需要调用一下RecalculateBounds方法,重新计算Bounds。

    备注

    此处为了展示高级方法的用法,因此未直接创建完整缓冲区数据,多执行一次数据的组装(即vertexAttributeBuffer数组)。实际应用时,会直接将缓冲区数据准备好(而不是分开存储vertices、normals、uv、triangles),直接调用SetXXXBufferParams、SetXXXBufferData。

    参考:其他博主的文章Unity3D学习笔记4——创建Mesh高级接口

  • 相关阅读:
    Linux的部分基础指令
    04-分布式事务解决方案之最大努力通知实战
    6、Elasticsearch 检索文档的方式
    C语言数组在内存中是怎样表示的?
    2022国赛C:古代玻璃制品的成分分析与鉴别
    【TOOL】ceres学习笔记(二) —— 自定义函数练习
    《混沌工程》读书笔记
    工程伦理--16.1传统制造业的转型升级与伦理问题
    Promise常用方法笔记
    第六章 查找
  • 原文地址:https://blog.csdn.net/u013283476/article/details/136254063