Open hybridherbst opened 5 years ago
I don't have any ideas as to why this would happen, but it's clearly a bug. I don't have much time to look into this now, so I'll see when I have time.
This might be partly due to be an inherent limitation of the original algorithm and partly because of some bug. The original mesh simplification algorithm (Surface Simplification Using Quadric Error Metrics by Michael Garland) doesn't take into account the surface curvature to preserve edges with sharper corners(curved edges). I have tweaked the algorithm based of a new approach that takes discrete curvature into account and it generates some very good quality mesh simplification without sacrificing the low poly aspect of the original Garland algorithm. I will contribute my changes to this project when I get time to do so.
@bawar9 I'm pretty sure the bug is related to this calculation. I might have made a mistake when I ported the original algorithm, or when I optimized it. Or it's even a problem in the original algorithm.
@Whinarn There is an obvious problem in the implementation of Garland's algorithm here https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification The guy modified the original algorithm and skipped the step of keeping a sorted list of Vertices with the least error on top, instead he just used an empirical threshold value to check which vertex pair (edge) to collapse. He did this to gain a big performance boost, but this can result in bad quality simplification. The same can be seen in the UnityMeshSimplifier implementation here https://github.com/Whinarn/UnityMeshSimplifier/blob/master/Runtime/MeshSimplifier.cs#L2467. @Whinarn can't we keep a sorted heap with the least cost vertex at top instead of just using an empirical threshold value.
@bawar9 I am aware. This was actually the reason why I picked this algorithm. I wanted something that was fast. I would be okay with adding a sorted heap, but only if it's optional. I would still like there to be a fast simplification, but that people can enable a less optimized simplification for a higher quality output.
@bawar9 I am aware. This was actually the reason why I picked this algorithm. I wanted something that was fast. I would be okay with adding a sorted heap, but only if it's optional. I would still like there to be a fast simplification, but that people can enable a less optimized simplification for a higher quality output.
That would be really great, giving people the option to choose for themselves. For quite some time now I've been thinking which algorithm does Cinema4D use for this. It simplifies models super fast and the results are really great. I even created a threaded version of the UnityMeshSimplifier, even that couldn't surpass or even come closer to the Cinema4D mesh simplifier. I don't really mean to question your work in any way, you really have created a very good tool. I am just wondering that the Garland's algorithm has been living for quite some time and there might be newer and better algorithms out there. If I ever find anything better I would love to contribute my changes to the project.
@Whinarn Thanks for sharing your code ! I came across your project while searching for ways to manipulate meshes in Unity as a personal journey. I sometimes have to import CAD files data into Unity and the process always involves simplifying the meshes as much as possible. I recently tried a third party plugin called PiXYZ to do it and, I must say, it does a tremendous job at importing from many source formats and at reducing polygons to a high degree with high quality. The only down side is its license cost (more than 1000 Euro/year). I tried UnityMeshSimplifier (which is free) to "crunch" some imported meshes to a high degree but I've been a bit disappointed with the result. The tool gives good results when the simplification ratio is not to high. After reading the comments here, I decided to dive into the project and try to improve (accuracy and speed) the algorithm as much as possible. Here is what I have experimented so far:
The resulting algorithm gives pretty good results but it too fails badly on the boat mesh above ! I'm currently working on the problem. What I can say right now about the boat mesh is that there are two problems:
If I succeed with all of this I will post the code here and let you decide if it can be incorporated into your project.
Regards
@is3D-1 Thank you for dedicating your time on the project, it looks like you have really dig deep into the issue. Can you please share your current code step (1- 4)?. I would like to test it on my side as well. You can send me your new "MeshSimplifier.cs" file if that's feasible.
Thank you
@bawar9 I would be please to share it but the code has intricate parts with my development project. There are modifications in more than just "MeshSimplifier.cs" file and I have to manually operate the algorithm during this development. In fact I have dropped the uvs, normals and colors... until the mesh is ok. Again I will post it when it will be in a usable state, if you can wait until then... I have made an interesting observation about the fix to Boat Problem 1 (BP1): it actually improve the result on other meshes as well when the simplification ratio is high. It is like the quadric error metric degenerate the further you increase the reduction ratio and BP1 fix seems to alleviate this. I will fork the project and gradually pull the code there.
@is3D-1 Great work! I'm impressed. I'm looking forward to seeing your results.
@Whinarn Thank you. I have to admit I'm far from a working version. In the meantime, it would help if I could get some typical meshes to benchmark the solution. Regards.
eresting observation about the fix to Boat Proble
Thank you, I'll wait for you changes to get published
@bawar9 I don't know where to address this but I have looked at the curvature error method you have committed and I have some concerns:
The idea of taking curvature data into consideration is a great addition to the algorithm however, the way it is implemented is not correct. Let me explain: 1- In the CurvatureError(ref Vertex vert0, ref Vertex vert1) function, you take the maximum value of the dot product of all triangles touching Vert0_or_Vert1 as outer loop and Vert0_and_Vert1 as inner loop. Any triangle that is part of Vert0_and_Vert1 is also part of Vert0_or_Vert1 and thus the dot product of a triangle normal by himself is always 1. Hence, the CurvatureError() function only return the length of the edge... You could fix it by rejecting the case where a triangle is member of both set like that:
foreach (var triangleWithViAndVjBoth in trianglesWithViAndVjBoth)
{
if (triangleWithViAndVjBoth == triangleWithViOrVjOrBoth)
continue;
Vector3d normVecTriangleWithViAndVjBoth = triangleWithViAndVjBoth.n;
double dot = Vector3d.Dot(ref normVecTriangleWithViOrVjOrBoth, ref normVecTriangleWithViAndVjBoth);
if (dot > maxDotInner)
maxDotInner = dot;
}
2- The CalculateError(...) function incorporate CurvatureError(..) only for the first case. Why it is not applied to all case ?
3- A good way to implement the curvature error would be to specify a value between 0..1 instead of a check box [0 or 1] to specify whether to preserve curvature or not. The resulting error would be: error = E(vertex) + alpha * E(curvature) where alpha is [0..1]. Alpha =0 corresponds to keep details while alpha=1 corresponds to keep general shape...
Regards
@bawar9 I don't know where to address this but I have looked at the curvature error method you have committed and I have some concerns: The idea of taking curvature data into consideration is a great addition to the algorithm however, the way it is implemented is not correct. Let me explain: 1- In the CurvatureError(ref Vertex vert0, ref Vertex vert1) function, you take the maximum value of the dot product of all triangles touching Vert0_or_Vert1 as outer loop and Vert0_and_Vert1 as inner loop. Any triangle that is part of Vert0_and_Vert1 is also part of Vert0_or_Vert1 and thus the dot product of a triangle normal by himself is always 1. Hence, the CurvatureError() function only return the length of the edge... You could fix it by rejecting the case where a triangle is member of both set like that: foreach (var triangleWithViAndVjBoth in trianglesWithViAndVjBoth) { if (triangleWithViAndVjBoth == triangleWithViOrVjOrBoth) continue; Vector3d normVecTriangleWithViAndVjBoth = triangleWithViAndVjBoth.n; double dot = Vector3d.Dot(ref normVecTriangleWithViOrVjOrBoth, ref normVecTriangleWithViAndVjBoth);
if (dot > maxDotInner) maxDotInner = dot; }
2- The CalculateError(...) function incorporate CurvatureError(..) only for the first case. Why it is not applied to all case ? 3- A good way to implement the curvature error would be to specify a value between 0..1 instead of a check box [0 or 1] to specify whether to preserve curvature or not. The resulting error would be: error = E(vertex) + alpha * E(curvature) where alpha is [0..1]. Alpha =0 corresponds to keep details while alpha=1 corresponds to keep general shape... Regards
@is3D-1 Thank you for pointing out the issues. To keep it organized I will answer your points in order
► You're correct in regard to saying "Any triangle that is part of Vert0_and_Vert1 is also part of Vert0_or_Vert1 and thus the dot product of a triangle normal by himself is always 1", however you're incorrect in saying that the "CurvatureError" function will only return the length of the edge (Vi, Vj). This will be true only where the afore mentioned condition is true, which won't be the case always. This is not a mistake done by me but what the authors of the original paper https://www.hindawi.com/journals/mpe/2015/428917/ intended. Below you see 2 images of a model (Before incorporating you condition and After incorporating you condition). The model is simplified to 80% (0.8) and the resulting triangles count is 3608 in both the cases, however your condition seems to disregard discrete surface curvature in some areas of the model.
The curvature of the arm and circle on the belt is preserved nicely
The curvature of the highlighted regions is slightly disrupted (Which is not the case with the original code. See the image above ^^^)
► Thank you for pointing it out this is a mistake on my side, I actually overlooked the second case. I'll make the changes and do a pull request after @Whinarn approves my last pull request.
► We can incorporate curvature error using
error = E(vertex) + alpha * E(curvature) where alpha is [0..1]. However the effect would be the same, I usually stress on simplicity and code readability so I used simple boolean conditions so that anyone can easily understand the code
@bawar9 Thanks for your reply. I don't want to accuse you or anyone else who is contributing to the project. I view it simply as a discussion to improve the final result and again I appreciate your answer with pictures that clearly illustrate your point ! Regards
@bawar9 Thanks for your reply. I don't want to accuse you or anyone else who is contributing to the project. I view it simply as a discussion to improve the final result and again I appreciate your answer with pictures that clearly illustrate your point ! Regards
@is3D-1 I am sorry if I sounded a little offensive, I can assure you that I was not. I am very happy to see some one like you contributing to the project, someone who knows what he's doing. This is a great chance for me to learn and enhance my knowledge from good people around. By no means am I an expert on this subject, in fact I am still a student and by far from being a pro. Please do point out any problems that you see, I would appreciate positive discussion and/or criticism.
Kind Regards
I finally got some results I would like to share with you. I've put a lot of effort to come to a solution for the boat problem but I'm not sure if it's a robust solution that would solve all the cases out there. I basically implemented the solution described above for a heap of edges keyed by increasing quadrics error order. I added weighted penalty planes to try to preserve 2D borders on open meshes. This version utilizes the available "smart link" and "preserve surface" features but implements its own "preserve border edge". The method collapses all edges in a single pass until the quality ratio is reached. Parameters "max iteration count" and "agressiveness" are not used. Also note that vertices attributes like uv, colors, etc are not fixed yet. Test case 1. The boat (posted version has 2196Tris, 3851Verts). From top left to bottom right: (Quality=0.5, 1098Tris, 604Verts), (Quality=0.2, 439Tris, 267Verts), (Quality=0.1, 220Tris, 149Verts) Test case 2. Men washroom device (original version has 8866Tris, 3851Verts). From left to right: (Quality=1.0, 8866Tris, 3851Verts), (Quality=0.3, 2500Tris, 1255Verts), (Quality=0.1, 726Tris, 368Verts), (Quality=0.05, 284Tris, 148Verts). handle.zip ...And the execution time is quite fast too. Some observations from my experiments:
Regards
I improved the approach to preserve 2D border edges: instead of adding a heavily weighted plane along the border, I add two very close planes, one on each side of the border so that the quadrics error metric is always much greater than zero anywhere near the border. This has solved the problem: Test case 3. Two basic perpendicular planes. From left to right: a) Quality=1.0, 400Tris, 242Verts b) Quality=0.1, 40Tris, 32Verts, preserve border edge = ON, preserve surface = ON c) Quality=0.1, 40Tris, 32Verts, preserve border edge = ON, preserve surface = OFF d) Quality=0.1, 40Tris, 32Verts, preserve border edge = OFF, preserve surface = OFF
I know almost nothing about UV (same for vertex attributes) but I'm asking anyway : do you think the approach to preserve border edge could work to preserve UV seam edges ?
I so, I could easily extend the code to manage these options before I publish.
regards
@is3D-1 Great work !. Indeed your approach to preserve 2D border edges using weighted virtual planes seems to work. I can't wait to get my hands on the code. About preserving UV Seam Edges, I haven't looked at how it is implemented and without seeing your current changes, it would be hard to say that whether it would work or not. I think @Whinarn would know better. One question I wanted to ask, how much does this all affect the performance of the Whinarn's original implementation?. I suspect the sorted heap would have made a huge difference and caused the original algorithm to become slower. Did you try to actually benchmark the 2 variants using an actual time metric ?.
Thanks
@bawar9 You are right, the performance has taken its toll ! The algorithm is somewhere between 5 to 10 times slower than @Whinarn version. This is partly due to the sorted heap management and partly to quadrics error planes and error evaluation on every edge collapsed. I have not changed the data structure of @Whinarn model beside to add an Edge class. Although I may have changed many things, I have tried to fit into @Whinarn model as much as possible. The major aspects are :
I- Managing the edges heap with two data structures:
II- Convert all struct data type to class data type: This was necessary for the edge based approach to create pointer to other objects in the model without having to instantiate data for every variable. This change alone speed up @Whinarn algorithm by a factor of 2 to 3.
III- Managing the option to preserve features usually add extra calculation that increased time. however, I have found that "preserving surface" option often reduced the calculation time. I think the error change calculated by this option improve the hashing of the sorted list algorithm and ultimately speed up the Add/Remove operations.
So overall the algorithm is an order of magnitude slower than @Whinarn version.
Regards
@is3D-1 Thanks for the detailed breakdown. In the custom version of Whinarn's mesh simplifier that I have tailored to fit my needs, I had also made the following changes:
1> As you did, I had also converted all struct/value types to class types, this greatly increases the performance simply because structures are immutable and any change to a structure requires copy pasting all the data from one to another.
2> Heavily threaded Whinarn's implementation so that it uses best of what the CPU can offer.
3> Did some other minor tweaks to gain some performance.
Although this made a huge difference but it still was no where near to the mesh simplification offered in 3D Modeling tools. I use Cinema4D and don't know what kind of magic it does but it is super fast at simplifying even complex meshes with a lot of preservation options checked (Preserve border edges, UV Seams etc) and the simplified mesh quality is great. @is3D-1 This just leaves me with one clue, I think they are somehow using the parallel power of GPU to do all the heavy lifting. They might have their own proprietary algorithm which would be tailored to run best on parallel cores. What do you think of this?. Can we use a compute shader in Unity and somehow parallelize /distribute the algorithm for the GPU?
Oh, and btw in point 1 I suggest you use HashSet instead of a Dictionary if that's possible. Insertions in Dictionaries are a little slower than HashSets. Also if you somehow get duplicate values in the Dictionary the lookup can get very slow somewhere around O(n) due to Hash collisions.
Thanks
@is3D-1 Impressive work.
do you think the approach to preserve border edge could work to preserve UV seam edges ?
I'd need to see the code in order to answer that. But I'd assume that preserving UV seams would require a different approach, but don't take my word for it.
II- Convert all struct data type to class data type: This was necessary for the edge based approach to create pointer to other objects in the model without having to instantiate data for every variable. This change alone speed up @Whinarn algorithm by a factor of 2 to 3.
I'm actually very surprised about this, I might have to evaluate it myself. I might have done some pre-mature optimizations here then, because the idea of using struct was to make it faster and avoid extra allocations. But I should have profiled the code before making assumptions.
So overall the algorithm is an order of magnitude slower than @Whinarn version.
As long as it's opt-in, I'd be fine with it.
If you keep this up, and your code looks promising, I might ask you to take over maintaining this project. With me being absent and uninterested, and people still using it, I feel like it deserves a more active and interested maintainer (with a lot more knowledge about this as well). If you'd be up for it that is. I have wanted to archive this project, because I don't feel like I have the time for it, but I would feel bad about the fact that people still use it in their projects.
Please be very cautious with respect to this algorithm. I have made a lot of promises based on the test cases above after tweaking the code to obtain the best result possible at high reduction ratio. But it has not been tested seriously yet...
I have finally not implemented the fix for the boat problem because it has disappeared by itself when I implemented the quadrics unioning for error calculation. Hence it may reappear and probably will...
My approach from the beginning is to avoid restricting the quadrics natural behavior as much as possible to achieve high reduction ratio while still recognizing the shape. So I tolerate triangles that are visually degenerated for the benefit of a high ratio. I have also observed that the more triangles that get merged at a vertex, the more stiff this vertex becomes and prevents the algorithm from doing a better job around it (the "preserve surface" feature help a lot to overcome this problem). So there is still a lot of work to do if one wants... and much more than I expected at the beginning of the project...
Hope to post the code soon
@bawar9
This just leaves me with one clue, I think they are somehow using the parallel power of GPU to do all the heavy lifting. They might have their own proprietary algorithm which would be tailored to run best on parallel cores. What do you think of this?
All these editing tools have detailed and efficient data structures to store the mesh information and to be able to quickly locate a face, edge or vertex when you click somewhere in the screen, and this is already setup before you click the optimize/reduce mesh button. On the other end, @Whinarn version receive the data from Unity and it needs to create internal arrays of triangles, vertices and references plus edges analysis to find borders... just to have the information ready for processing by the SimplifyMesh() function. To be fair with @Whinarn version, I would only measure the time it takes to actually run the SimplifyMesh() function and compare this result with the time Cinema4D takes on his side when you click its optimize button. Timing Cinema4D is feasible if you run the optimization algorithm from a python script (I think I may have such a script if you want). I would be curious to see the result. For the GPU, I think you could do a test: if you have a built-in video card, then you could disable the GPU card in Windows (Device Manager>Graphic cards>Deactivate) and test Cinema4D with/without the GPU.
They might have their own proprietary algorithm which would be tailored to run best on parallel cores. What do you think of this?. Can we use a compute shader in Unity and somehow parallelize /distribute the algorithm for the GPU?
I can't help you on this one.
Regards
@bawar9
Oh, and btw in point 1 I suggest you use HashSet instead of a Dictionary if that's possible. Insertions in Dictionaries are a little slower than HashSets. Also if you somehow get duplicate values in the Dictionary the lookup can get very slow somewhere around O(n) due to Hash collisions.
I would like to explore that possibility and some others but only after you have seen the code. I think I could post the code this week-end. Thanks
@Whinarn
If you keep this up, and your code looks promising, I might ask ...
Please give me some time. I will get back to you for sure. Regards
@bawar9
This just leaves me with one clue, I think they are somehow using the parallel power of GPU to do all the heavy lifting. They might have their own proprietary algorithm which would be tailored to run best on parallel cores. What do you think of this?
All these editing tools have detailed and efficient data structures to store the mesh information and to be able to quickly locate a face, edge or vertex when you click somewhere in the screen, and this is already setup before you click the optimize/reduce mesh button.
Hi, Sorry for the late response. I have been quite busy lately. You're right I should do performance benchmarking disregarding the initialization on Whinarn's mesh simplifier. If you have the python script for C4D (Cinema4D) please do send it over. I'll perform some performance tests on both and get back to you later this week. Disabling the dedicated GPU and then testing on C4D would still allow it to access the integrated GPU in the processor, but I'll give this a try.
I have also explored the topic of GPU based mesh decimation and it looks like there are a lot of papers on this topic that utilize the parallel processing capabilities of the GPU. I read one of them that used OpenCL to practically implement the algorithm.
I would like to explore that possibility and some others but only after you have seen the code. I think I could post the code this week-end. Thanks
Sure, Thank you
@bawar9
I have also explored the topic of GPU based mesh decimation and it looks like there are a lot of papers on this topic that utilize the parallel processing capabilities of the GPU. I read one of them that used OpenCL to practically implement the algorithm.
Very interesting. We may not be at the right place to discuss it without polluting the OP Boat Problem thread ! However, it seems you are seeking a solution to process thousands of gameobjects in the fastest time. I have just reduced a scene containing 311500 objects from 15.5M triangles down to 10.0M with a commercial application that did it in 65 seconds with quadrics. The app takes advantage of duplicated objects by just reducing one of the siblings and copying it over. It uses the CPU at 100% so all cores are working and no GPU at all ! But it takes a lot of memory, around 8 Gb. This suggests the data structure is extremely important...
This suggests the data structure is extremely important...
You're right we shouldn't spam this thread. Great conclusions anyways, indeed choosing the right data structures are crucial.
Feel free to continue the discussion here! I changed the title to reflect that there's more in here then just a bug about the model. I'm following along with great interest :)
More than a month ago, I tested the algorithm on something bigger. I have red a very interesting article about Optimization for Unity that manipulated this publicly available Space Exploration Vehicle mesh model. The model is shown below:
The algorithm took an eternity to complete a mesh reduction to quality = 0.1 on test case 4a. That is why I isolated the vehicle shell and created test case 4b. The algorithm took more than 75 seconds to achieve a reduction to quality = 0.1. I have been extremely disappointed with this performance and I took a month to redo all my work to try to improve the result. I want to summarize the work in the following points:
1. Speed. With test case 4b, I profiled the code with the Unity Profiler and it shows that using a dictionary of edges as a way to get any edge from anywhere was the most consuming thing. I decided to eliminate the dictionary and replaced it by a resizable array that gives the edges connected to any vertex, identical to the refs array in @Whinarn version that gives the triangles connected to any vertex. The next most consuming part was the sorted list of edges by increasing order of quadrics error! This is the main structure of the algorithm. This list was implemented as a <SortedList<TKey,TValue> Class>. I decided to replace it by simple <List <<>T> Class> that I keep sorted myself. Then I found that the next most consuming thing was to remove edges from this list ! Every loop iteration I removed the topmost edge (the one with least error) that was being collapsed and this was the most time consuming operation ! I decided not to remove any edges from the list. So the list can only grow and I use a pointer to keep track of the head of the list. All of these measures have greatly improve the speed of the algorithm such that test case 4b now takes less than 5 seconds to complete a reduction to quality = 0.1. Unfortunately this has made the code more obscure.
2. Boat Problem 1. Boat problem 1 reappear in test case 4. Test case 4b has near 7500 border edges out of around 47000 edges. Borders really seem to create degenerated quadrics matrices that I think is the source of the boat problem. To eliminate these artifacts, I have sketched an approach that compute the eigenvalues of the characteristic equation of the quadrics matrix. My approach uses the Newton method with deflation to find the roots (there are three) of the characteristic equation. Application usually uses QR matrix decomposition to find eigenvalues and eigenvectors but I am more familiar with Newton... I reject all cases where an eigenvalue is 0 or when the function does not converge to an eigenvalue in a reasonable number of iterations. This approach has eliminated much of these artifacts but some remains. I have made many observations with test cases 1 to 4 above and the function I have sketched rarely obtained the results mentioned by the authors of the original method (Garland & Heckbert) that the eigenvalues of the matrix are positive most of the time. In fact, I observed the opposite: the eigenvalues are negative or zero most of the time! My approach may not be mathematically sound or may contain errors but it is a work in progress and actually gives good results.
3. Boat Problem 2. Boat problem 2 concerns border edges that were destroyed by the simplification algorithm. I have mentioned that I solved this problem by using two very close virtual planes. This is not the case anymore as I propagate the penalty quadrics matrix to the vertices at each edge end and this suffices to protect the border edges. So only one single virtual plan is required per edge to preserve it.
4. Difference between single and multiple pass. I am still wondering why the result is better (visually) when I run a simplification of Quality = 0,5 twice on a mesh instead of running it once with Quality = 0.25. A big part of the answer is that the rejected edges of the first pass are reevaluated in the second pass. This is not possible if the algorithm run only once. So I have created a mechanism that recycle the rejected edges in a way that they can be reevaluated and deleted within a single pass algorithm. The recycle rate must be carefully chosen to avoid stalling the algorithm: the algorithm could reject-recycle and edge forever...
5. Simplification Options. I have added an option to specify the sorted edge method (described here) when reducing a mesh. The option is called "Use Sorted Edge Method". The method uses virtual penalty planes to preserve borders and uv seams/foldover and the algorithm deletes all edges in a single pass. The user interface is not completed yet: when you opt to use the sorted edge method, other options like "Max Iteration Count" and "Aggressiveness" are not taken into consideration but this is not reflected from the user interface. The same is true for preserving edges and uv seams: they will not be effective until "Enable Smart Link" is enabled. So some work is still needed on the UI.
6. Some results. Previous results for test cases 1 to 3 have not changed much since the focus was on execution speed. Results for test case 4 are shown below. From left to right: a) Quality = 1.0 : 413K tris, 347K verts, 157 gameobjects b) Quality = 0.1 : 41.3K tris, 53K verts, 157 gameobjects c) Quality = 0.01 : 4K tris, 7.5K verts, 157 gameobjects d) Quality = 0.001 : 382 tris, 898 verts, 157 gameobjects They all completed the reduction pass in less than 15 seconds.
7. Give it a try !. The code is all there
6. Some results. Previous results for test cases 1 to 3 have not changed much since the focus was on execution speed. Results for test case 4 are shown below. From left to right: a) Quality = 1.0 : 413K tris, 347K verts, 157 gameobjects b) Quality = 0.1 : 41.3K tris, 53K verts, 157 gameobjects c) Quality = 0.01 : 4K tris, 7.5K verts, 157 gameobjects d) Quality = 0.001 : 382 tris, 898 verts, 157 gameobjects They all completed the reduction pass in less than 15 seconds.
7. Give it a try !. The code is all there
Good job man !, keep up the good work.
I was wondering if there is a way that I can skip deleting some edges in the "RemoveEdgePass" method ?. I tried to increase the error value of an edge but that resulted in the algorithm iterating forever.
The algorithm iterates forever most probably because it tries to recycle previously rejected edges...forever. You can deactivate edge recycling by commenting these lines in MeshSimplifier.cs :
Thanks, I just figured out a way to serve my use case. Btw I can recommend the following models to base your tests on:
https://assetstore.unity.com/packages/3d/characters/humanoids/barbarian-warrior-75519 https://assetstore.unity.com/packages/3d/characters/creatures/creature-cave-troll-115707
I have noticed that the original whinarn implementation performs better with both these models. See below the results on the Barbarian model. Please note that I have only enabled smartLinking and no other simplification options in both the cases
Original Model 18062 Tris
Reduced by Whinarn Original Method (3607 Tris) at Quality 0.2f or 80% reduction intensity
Reduced by the Edge Sort Method (3613 Tris) at Quality 0.2f or 80% reduction intensity
Here's a possible enhancement. Unreal Engine 4's mesh simplifier has "silhouette protection". The outline of the object is kept from shrinking. This is a big help for thin objects. Quadric mesh reduction likes to pull in the edges of thin objects, because that doesn't change the volume much. When this happens, the reduced mesh looks awful at distance - it's missing whole areas. UE4's reducer doesn't do that. They're onto something here.
So, how to do this? A few options:
If you have silhouette protection, you can push mesh reduction for very low LODs much further. Eventually you get down to something close to an impostor.
Here's a Harvard paper on silhouette extraction.. No code, unfortunately.
The original title was "Model where simplification fails badly", but the thread has become a very valuable discussion around optimizations and improvements.
Simplification of this (already pretty simple) model fails pretty badly using the default options: boat_vertexpaint.zip
Target
Actual LOD 0
LOD 1
Any ideas?