-
Notifications
You must be signed in to change notification settings - Fork 280
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Model Optimization Improvements (low poly simplification, edge collapsing, keep volume) #19
Comments
I don't have any ideas as to why this would happen, but it's clearly a bug. |
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. |
@Whinarn There is an obvious problem in the implementation of Garland's algorithm here https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification |
@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 !
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... |
@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. |
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:
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: 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 |
@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 ! |
@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.
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: 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.
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.
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.
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 |
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.
I can't help you on this one. Regards |
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. |
Please give me some time. I will get back to you for sure. |
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. |
Sure, Thank you |
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... |
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: 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. |
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 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. 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?
The text was updated successfully, but these errors were encountered: