By default, when Unity imports a model it welds any duplicate vertices it finds (though this can be disabled by unchecking “Weld Vertices” in the model’s import settings). For example, here’s the effect it has on a simple plane where the original model has each tri unwelded from the others:
Is there a way to use this functionality at runtime, on procedurally generated/imported meshes? I’m currently dealing with some models loaded from external files at runtime, where every tri always has three verts of its own, whether there’s already a vert with the exact same position/normal/UV or not… So everything looks fine but the vertex count is through the roof.
So far I haven’t seen any indication Unity exposes this functionality so it can be used anywhere other than asset importing… So it looks like I might have to re-implement a feature already in the engine, unless I’m just missing something. Any tips would be appreciated.
Well you have to do it “manually”. Just follow those steps:
First find all duplicate vertices in the vertices array. This could be done by using an List and a int array with one element for each vertex. Use a double for loop and iterate trough the vertices. Compare the current vertex with each referenced vertex in the int-list. If no match was found, add the current index to the list and set the index in the int array to the current index. If a duplicate was found in the list, don’t add the current index to the list. Instead just add the index of the found duplicate to the int array of the current vertex.
Now you have two things: A list that contains indices of all unique vertices and an array that contains the vertex mapping for every vertex.
The list can be used to create the new vertices array. So you only add the vertices which are referenced from the List.
The array can be used to fix the indices in the triangle list of the mesh. So just use the indices of the triangle list as index into the int array. This gives you the new index.
Something like this (untested):
public static void WeldVertices(Mesh aMesh, float aMaxDelta = 0.001f)
{
var verts = aMesh.vertices;
var normals = aMesh.normals;
var uvs = aMesh.uv;
List<int> newVerts = new List<int>();
int[] map = new int[verts.Length];
// create mapping and filter duplicates.
for(int i = 0; i < verts.Length; i++)
{
var p = verts*;*
var n = normals*;* var uv = uvs*;* bool duplicate = false; for(int i2 = 0; i2 < newVerts.Count; i2++) { int a = newVerts[i2]; if ( (verts[a] - p).sqrMagnitude <= aMaxDelta && // compare position Vector3.Angle(normals[a], n) <= aMaxDelta && // compare normal (uvs[a] - uv).sqrMagnitude <= aMaxDelta // compare first uv coordinate ) { map = i2; duplicate = true; break; } } if (!duplicate) { map = newVerts.Count; newVerts.Add(i); } } // create new vertices var verts2 = new Vector3[newVerts.Count]; var normals2 = new Vector3[newVerts.Count]; var uvs2 = new Vector2[newVerts.Count]; for(int i = 0; i < newVerts.Count; i++) { int a = newVerts*;* verts2 = verts[a]; normals2 = normals[a]; uvs2 = uvs[a]; } // map the triangle to the new vertices var tris = aMesh.triangles; for(int i = 0; i < tris.Length; i++) { tris = map[tris*];* } aMesh.vertices = verts2; aMesh.normals = normals2; aMesh.uv = uvs2; aMesh.triangles = tris; } Note that this specific implementation only works when the mesh has normals and uv coordinates. It does not compare the second or third uv channel nor if the vertex colors nor the tangents are equal. Creating a method that dynamically only checks and copies the vertex attributes that are available is very tricky. The “if statement” that determines if two vertices are equal would need to be extended to the other attributes. Also when you copy the old vertices into the new arrays it would also need to adapt automatically which data is actually present. At the moment i only use a single maxDelta value for comparison, however comparing positions or angles might require different maxdelta values. Same for UV coordinates those would need a better resolution than the position. edit I just created a “small” helper class to “weld” vertices manually. I wanted to put it on the Unity wiki, but it seems it’s currently down. Anyways here’s a [dropbox link][1]. The class should dynamically compare and assign only those vertex attributes that are actually present in the mesh. It also has seperate max delta values. Note that i haven’t really tested the class on any meshes, so feel free to give feedback if it works or if there are any errors. var welder = new MeshWelder(someMesh);
// change any setting if necessary
welder.Weld(); For those who are not familiar with namespaces, you need to use using B83.MeshHelper; at the top in order to use the class. _*[1]: Dropbox - MeshWelder.cs - Simplify your life
Thank you! It's very helpful to merge different meshes and then create a single mesh collider. So that I don't have any glitches during a rigidbody moving.
This is old topic, but I don’t see any good answer.
Mesh welding can be much faster by using Dictionary (which is in fact hash table), instead of 2 nested for loops.
Based on Bunny83 answer:
public static Mesh WeldVertices(Mesh aMesh, float aMaxDelta = 0.01f) {
var verts = aMesh.vertices;
var normals = aMesh.normals;
var uvs = aMesh.uv;
Dictionary<Vector3, int> duplicateHashTable = new Dictionary<Vector3, int>();
List<int> newVerts = new List<int>();
int[] map = new int[verts.Length];
//create mapping and find duplicates, dictionaries are like hashtables, mean fast
for (int i = 0; i < verts.Length; i++) {
if (!duplicateHashTable.ContainsKey(verts*)) {*
// create new vertices var verts2 = new Vector3[newVerts.Count]; var normals2 = new Vector3[newVerts.Count]; var uvs2 = new Vector2[newVerts.Count]; for (int i = 0; i < newVerts.Count; i++) { int a = newVerts*;* verts2 = verts[a]; normals2 = normals[a]; uvs2 = uvs[a]; } // map the triangle to the new vertices var tris = aMesh.triangles; for (int i = 0; i < tris.Length; i++) { tris = map[tris*];* } aMesh.triangles = tris; aMesh.vertices = verts2; aMesh.normals = normals2; aMesh.uv = uvs2;
Regarding the first issue, you could pass a IEqualityComparer into the dictionary constructor to resolve it. var duplicateHashTable = new Dictionary<Vector3, int>(new ImpreciseVertexComparer(aMaxDelta));
I aded float roundFctr = 1000f; and in the first line in loop, before "if" statement: verts = new Vector3(Mathf.Round(roundFctr * verts*.x) / roundFctr,* <em>Mathf.Round(roundFctr * verts*.y) / roundFctr,*</em> <em><em>Mathf.Round(roundFctr * verts*.z) / roundFctr);*</em></em> <em><em>and now it is fast and works very well :) Thanks</em></em>
Did you have a look at Unity’s MeshUtility.Optimize()? From the docs:
Optimizes the mesh for GPU access.
This operation might take a while but
will make the geometry displayed be
faster. You should use it if you
generate a mesh from scratch
procedurally and you want to trade
better runtime performance against
higher load time. Internally it
converts the mesh to triangle list and
optimizes the triangles and vertices
for both pre and post transform cache
locality. For imported models you
should never call this as the import
pipeline already does it for you.
AFAIK, that's unfortunately editor only (would love to be wrong though).
Wow, this looks very promising! I didn't expect anyone to outright make a script to do the job haha, much appreciated. Seems to have some bugs to work out though; it's turned this: [http://i.imgur.com/vm0MuAg.png][1] Into this: [http://i.imgur.com/DGBmf51.png][2] And this: [http://i.imgur.com/wVWVRvy.png][3] Into this: [http://i.imgur.com/30iRXPr.png][4] [1]: http://i.imgur.com/vm0MuAg.png [2]: http://i.imgur.com/DGBmf51.png [3]: http://i.imgur.com/wVWVRvy.png [4]: http://i.imgur.com/30iRXPr.png
– TerrevThank you! It's very helpful to merge different meshes and then create a single mesh collider. So that I don't have any glitches during a rigidbody moving.
– Feelnside