Unity中Mesh重叠顶点合并参考及其应用

在Unity中,如果将一个模型文件(比如从max里面导出一个fbx文件)导入到编辑器中之后,Unity会把所有在原来在面列表中公用的顶点复制一份,保证每个三角形使用的顶点都是单独的,不与其它三角形共用顶点,Unityh这么做应该有他的道理,但我尚未想明白Unity这么做的原因是什么。

但是考虑到在有些应用中使用共用顶点能够更好的处理一些问题,这里写了一个将Unity中Mesh重叠顶点合并成公用顶点的代码,目的是将位置重叠的顶点去掉,同时也将面列表的内容更新,由一个原来有重叠顶点的Mesh获得一个新的使用公用顶点的Mesh,参考如下:

cs 复制代码
Mesh GetWeldMesh(Mesh mesh)
{
	Vector3[] vs = mesh.vertices;
	int[] ts = mesh.triangles;
	Debug.Log(ts.Length);

	Dictionary<Vector3, int> dicVert = new();
	for (int i = 0; i < ts.Length; i++)
	{
		bool hasSamePos = false;
		Vector3 vert = vs[ts[i]];
		foreach (var kv in dicVert)
		{
			Vector3 key = kv.Key;
			if (vert == key)
			{
				if (kv.Value < ts[i])
				{
					ts[i] = kv.Value;
				}
				else
				{
					dicVert[kv.Key] = ts[i];
				}
				hasSamePos = true;
				break;
			}
		}
		if (!hasSamePos)
		{
			dicVert.Add(vert, ts[i]);
		}
	}

	List<Vector3> listVertice = new();
	foreach (var kv in dicVert)
	{
		listVertice.Add(kv.Key);
	}

	Mesh meshNew = new()
	{
		vertices = vs,
		triangles = ts
	};
	meshNew.RecalculateNormals();
	return meshNew;
}

目前我能想到的这么做的用处有两个,第一个是在一种描边方法中,Shader需要将要描边的物体增加一次渲染,在这次渲染中,Shader会将网格的每个顶点沿着该顶点的法线方向向外挤出一点点,形成一个更大的轮廓,然后反面渲染,这样就形成了原物体的勾边效果,不过这里有个问题,如果使用的Unity默认的Mesh,当物体的光滑组是连续的时候,挤出的效果是没问题的,当光滑组不连续(例如一个Box),顶点挤出后会出现分离问题。你当然可以在类似max这种软件中将Box的光滑组强行编成一组,这样导入Untiy中之后挤出效果倒是没问题了,但是本身显示效果就不是硬边的效果了,这也不对啊。但是如果在使用Shader渲染Mesh的时候,使用的不是原始的Mesh,而是通过上述代码生成的新的Mesh,由于顶点是共用的,就不存在出现挤出分离的问题。关于具体的Shader就不说了,玩过Shader的都懂。

第二个是查找物体的边界,一般来说查找物体边界的方法是找到不被两个三角形共用的边,但是Unity中Mesh默认所有的顶点都是单独被面列表使用的,根本没有所谓被两个三角形共用的边的情况,这中情况会发现所有的三角形的边都是边界,貌似行不通,所以还是需要上述方法将Unity中Mesh重叠顶点合并再去查找边界。其查找边界的代码参考如下(由于Mesh的编辑可能不止一组,所以用的返回值是List<int[]>,是顶点编号数组的列表):

cs 复制代码
	List<int[]> GetEdgeIndexes(Mesh mesh)
	{
		Vector3[] vs = mesh.vertices;
		int[] ts = mesh.triangles;

		Dictionary<Edge, int> dicEdge = new();

		for (int i = 0; i < ts.Length; i += 3)
		{
			int indexA = ts[i];
			int indexB = ts[i + 1];
			int indexC = ts[i + 2];

			bool hasSameEdge01 = false;
			bool hasSameEdge02 = false;
			bool hasSameEdge03 = false;
			KeyValuePair<Edge, int>[] kvs = dicEdge.ToArray();
			for (int j = 0; j < kvs.Length; j++)
			{
				Edge edge = kvs[j].Key;
				if (edge.IsSame(indexA, indexB)) { hasSameEdge01 = true; dicEdge[edge] += 1; continue; }
				if (edge.IsSame(indexB, indexC)) { hasSameEdge02 = true; dicEdge[edge] += 1; continue; }
				if (edge.IsSame(indexC, indexA)) { hasSameEdge03 = true; dicEdge[edge] += 1; continue; }
			}
			if (!hasSameEdge01) { dicEdge.Add(new Edge(indexA, indexB), 1); }
			if (!hasSameEdge02) { dicEdge.Add(new Edge(indexB, indexC), 1); }
			if (!hasSameEdge03) { dicEdge.Add(new Edge(indexC, indexA), 1); }
		}

		List<Edge> edges = new List<Edge>();
		foreach (var kv in dicEdge)
		{
			if (kv.Value == 1)
			{
				edges.Add(kv.Key);
			}
		}

		foreach (var item in edges)
		{
			Debug.Log(item.a + " , " + item.b);
		}

		List<int[]> listEdgeIndexes = new();

		while (edges.Count > 0)
		{
			List<int> listTriangle = new()
			{
				edges[0].a,
				edges[0].b
			};
			edges.RemoveAt(0);

			while (true)
			{
				int indexLast = listTriangle[^1];

				bool hasAddIndex = false;
				for (int i = 0; i < edges.Count; i++)
				{
					Edge edge = edges[i];
					if (indexLast == edge.a)
					{
						listTriangle.Add(edge.b);
						hasAddIndex = true;
						edges.RemoveAt(i);
						break;
					}
					if (indexLast == edge.b)
					{
						listTriangle.Add(edge.a);
						hasAddIndex = true;
						edges.RemoveAt(i);
						break;
					}
				}

				if (!hasAddIndex)
				{
					listEdgeIndexes.Add(listTriangle.ToArray());
					break;
				}
			}
		}

		return listEdgeIndexes;
	}

其中所用到的Edge类代码如下:

cs 复制代码
public class Edge
{
	public readonly int a;
	public readonly int b;
	public Edge(int a, int b)
	{
		this.a = a;
		this.b = b;
	}

	public bool IsSame(Edge edge) { return (a == edge.a && b == edge.b) || (a == edge.b && b == edge.a); }
	public bool IsSame(int a, int b) { return (a == this.a && b == this.b) || (a == this.b && b == this.a); }
}

下图将一个球体挖了三个洞洞,通过上述方法查找到边界,并用LineRenderer渲染了边界。

相关推荐
这个bug不是我3 小时前
使用BMFont创建适用于Unity的艺术字
unity·游戏引擎·uv·bmfont
Little丶Seven21 小时前
[游戏开发] Unity中使用FlatBuffer
数据库·unity
rellvera1 天前
Unity使用脚本控制相机移动、旋转
数码相机·unity·游戏引擎
Thomas_YXQ2 天前
Unity3D项目为什么要使用FairyGUI
开发语言·unity·游戏引擎·unity3d·游戏开发
半夏知半秋2 天前
unity打包流程整理-Windows/Mac/Linux平台
windows·笔记·学习·macos·unity·游戏引擎
benben0442 天前
通过PS和Unity制作2D动画之四:脚本控制动画
unity·游戏引擎
棪燊2 天前
Unity集成Wwise并进行开发
unity·游戏引擎
林枫依依2 天前
Unity Newtonsoft遍历json中的键值对
unity·json
tealcwu2 天前
【Unity技巧】Unity项目中哪些文件不用管理(.gitignore)
unity·游戏引擎