Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
12 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
6
Question by Terrev · Jul 22, 2017 at 04:40 PM · meshvertices

Welding vertices at runtime

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:

alt text

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.

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

4 Replies

· Add your reply
  • Sort: 
avatar image
5
Best Answer

Answer by Bunny83 · Jul 22, 2017 at 06:53 PM

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[i];
         var n = normals[i];
         var uv = uvs[i];
         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[i] = i2;
                 duplicate = true;
                 break;
             }
         }
         if (!duplicate)
         {
             map[i] = 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[i];
         verts2[i] = verts[a];
         normals2[i] = normals[a];
         uvs2[i] = uvs[a];
     }

     // map the triangle to the new vertices
     var tris = aMesh.triangles;
     for(int i = 0; i < tris.Length; i++)
     {
         tris[i] = map[tris[i]];
     }

     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. 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.

Comment
Add comment · Show 6 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Terrev · Jul 23, 2017 at 12:44 AM 0
Share

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/vm0$$anonymous$$uAg.png

Into this: http://i.imgur.com/DGBmf51.png

And this: http://i.imgur.com/wVWVRvy.png

Into this: http://i.imgur.com/30iRXPr.png

avatar image Bunny83 Terrev · Jul 23, 2017 at 01:31 AM 1
Share

Yes, there was a copy&paste error inside "AssignNewVertexArrays". I actually used the old vertices array ins$$anonymous$$d of the "newVerts" list ^^. I've fixed it. Also i needed to swap "RemapTriangles();" and "AssignNewVertexArrays();" because when you assign a new vertex array but still using the old triangle array it will cause out-of-bounds errors since the old triangles try to access vertices that do no longer exist. The other way round is not a problem. First doing the triangle remapping doesn't hurt as the new vertex count is either the same or less than the old one. So all indices will be "in bounds".

I tried it with a procedurally generated plane with 600 vertices and 200 triangles (10x10 quad). After welding it has 121 vertices and of course still 200 triangles. (121 == 11x11) So all vertices got merged correctly.

Note that even you can increase the max delta to make it for example merge two vertices with different normals, it will not calculate the average of the shared vertices. It will simple grab the first vertex that matches and use it for ever vertex that matches the same criteria.

avatar image Terrev Bunny83 · Jul 23, 2017 at 04:29 PM 0
Share

Awesome, it seems to be working perfectly! Thank you so much!

avatar image Feelnside · Feb 02, 2018 at 06:34 PM 0
Share

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.

avatar image Terrev Feelnside · Feb 02, 2018 at 06:57 PM 0
Share

BTW, Bunny83's original version has some significant slowdown on larger meshes, so it ended up getting reworked a bit by grappigegovert - here's the version currently used: https://github.com/Terrev/3DX$$anonymous$$L-to-OBJ/blob/master/Assets/Scripts/$$anonymous$$eshWelder.cs It's also been streamlined a bit since it only needs to worry about positions, normals, and UV1 for this particular project... So you'd probably have to make some changes to make it more general purpose again.

avatar image Feelnside Terrev · Feb 03, 2018 at 06:10 AM 0
Share

Thank you for your note. Unfortunately for my purpose the $$anonymous$$axPositionDelta value is very important (which is not existing in the updated version). $$anonymous$$y levels are created by different sub items. They are linked between themselves and in some cases there are a little holes (not visible, but critical for collision detection). In the first version I have removed everything except position compare. It's still not so fast but better than before.

avatar image
3

Answer by imaxus · Aug 19, 2019 at 08:18 PM

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[i])) {
                     duplicateHashTable.Add(verts[i], newVerts.Count);
                     map[i] = newVerts.Count;
                     newVerts.Add(i);
                 }
                 else {
                     map[i] = duplicateHashTable[verts[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[i];
                 verts2[i] = verts[a];
                 normals2[i] = normals[a];
                 uvs2[i] = uvs[a];
             }
             // map the triangle to the new vertices
             var tris = aMesh.triangles;
             for (int i = 0; i < tris.Length; i++) {
                 tris[i] = map[tris[i]];
             }
             aMesh.triangles = tris;
             aMesh.vertices = verts2;
             aMesh.normals = normals2;
             aMesh.uv = uvs2;
 
             aMesh.RecalculateBounds();
             aMesh.RecalculateNormals();
 
             return aMesh;
         }

Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Bunny83 · Aug 19, 2019 at 10:14 PM 1
Share

Your approach has several issues. First of all your vertex positions have to match exactly, so there's no room for any variation. Though the biggest issue is that you ignored the normal / uv coordinates of the vertices. So your approach might work for a very specific sub-case, however for any more general vertex welding (when having uv coordinates or different normals) it doesn't work.


Also the nested for loop shouldn't be that bad depending on the vertex count. The inner loop starts with 0 iterations and only iterates through the new vertices. So the time complexity is about O(n*m) for n original vertices and m merged vertices. Since welding should reduce the vertex count it shouldn't be that bad. However if you really have a lot vertices, you may speed up the comparison with some octrees or other spatial structures.

avatar image imaxus · Aug 20, 2019 at 04:16 AM 0
Share

Yes thats true, it will only work for specific cases. But performance gain is massive, so for this cases it will be much better solution.

avatar image peter8kersting · Nov 27, 2019 at 08:39 AM 0
Share

Regarding the first issue, you could pass a IEqualityComparer into the dictionary constructor to resolve it.

 var duplicateHashTable = new Dictionary<Vector3, int>(new ImpreciseVertexComparer(a$$anonymous$$axDelta));

avatar image Bunny83 peter8kersting · Nov 27, 2019 at 11:18 AM 0
Share

That doesn't really help. A Dictionary can only "compare" items which produce the same hash. So the dictionary would not find any "equal" vector3 values. The items inside the dictionary are distributed into several "buckets" based on their hashcode. Items which should considered equal have to produce the same hashcode. If you now override the hashcode with some custom spatial depended magic you essentially loose almost all advantages of a dictionary.


If you really have a lot of vertices (64k or more) you may want to build an octree or use some BSP tree. The octree is usually easier to implement.


If you have trouble to wrap your head around how an octree works you may want to look up this Coding Challenge about creating a quadtrees. An octree works pretty much the same just in 3D. Also you could also just use a quadtree in 3d by just ignoring one axis. Though the performance wouldn't be as good depending on the vertex distribution.

avatar image ppiotrowiak · May 20, 2020 at 03:55 AM 0
Share

I aded

  float roundFctr = 1000f;

and in the first line in loop, before "if" statement:

         verts[i] = new Vector3(
             $$anonymous$$athf.Round(roundFctr * verts[i].x) / roundFctr,
             $$anonymous$$athf.Round(roundFctr * verts[i].y) / roundFctr,
             $$anonymous$$athf.Round(roundFctr * verts[i].z) / roundFctr);

and now it is fast and works very well :) Thanks

avatar image
0

Answer by JPhilipp · Feb 03, 2018 at 07:30 AM

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.

https://docs.unity3d.com/ScriptReference/MeshUtility.Optimize.html

Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Terrev · Feb 03, 2018 at 07:38 AM 0
Share

AFAI$$anonymous$$, that's unfortunately editor only (would love to be wrong though).

avatar image JPhilipp Terrev · Feb 03, 2018 at 07:59 AM 0
Share

Ah ouch. Why does Unity even do these editor-only functions? /ends rant

avatar image
0

Answer by BergOnTheJob · Jul 10, 2020 at 02:41 PM

I found this tool, the Oasis Mesh Editor, that can Split Vertices and Merge Vertices back together if you also needed more Editor functions, https://assetstore.unity.com/packages/slug/166155

Comment
Add comment · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

82 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

How to find the closest 4 verts/corners ( along with their orientation ) of a rectangular object in relationship with another object? 1 Answer

mask one gameObject mesh by another mesh vertex transparency 1 Answer

Mesh SetVertices() vs vertices 2 Answers

Creating a mesh out of a linerenderer - how do you convert revolved vertices set to triangles and uv's 1 Answer

Mirror vertices procedurally 2 Answers


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges