PixarAnimationStudios / OpenSubdiv

An Open-Source subdivision surface library.
graphics.pixar.com/opensubdiv
Other
2.88k stars 561 forks source link

Topology Converter #792

Closed ghost closed 8 years ago

ghost commented 8 years ago

I'm trying to transfer my mesh using a converter as seen in tutorial_1. I can get all the required data from my mesh rep but it seems OSD expects edges to be ordered in some way. After I set validateFullTopology to true I get a message saying: "_FAILED_ORIENTATION_INCIDENTEDGE - vertex 0 orientation failure at incident edge 0". Is there a specific edge ordering I have to follow when transferring edge indices?

ghost commented 8 years ago

Oh sorry, I found what I was looking for in a comment in topologyRefinerFactory.h:

The ordering of entries in these arrays is important -- they are expected to be ordered counter-clockwise for a right-hand orientation.

Non-manifold components must be explicitly tagged as such and they do not require the ordering expected of manifold components. Special consideration must also be given to certain non-manifold situations, e.g. the same edge cannot appear twice in a face, and a degenerate edge (same vertex at both ends) can only have one incident face. Such considerations are typically achievable by creating multiple instances of an edge. So while there will always be a one-to-one correspondence between vertices and faces, the same is not guaranteed of edges in certain non-manifold circumstances.

barfowl commented 8 years ago

Glad you found what you needed. There is a comment in the Doxygen block for the methods of TopologyRefinerFactory<> that you have to provide that includes the following:

///  See comments in the generic stubs, the factory for Far::TopologyDescriptor
///  or the tutorials for more details on writing these.

And I hoped that would lead people more quickly to what you eventually found above.

Not many users are writing such a converter, so I'd be interested in feedback once you get things working. It can be tricky to get everything specified as required (particularly for non-manifold meshes, as the comment you included above notes) and I have my own reservations about some of the things that were done, so any input would be appreciated -- particularly if it will help others. Thanks

ghost commented 8 years ago

Thanks. It does get very involved with a converter. After hours of fiddling around I got it working but normals were not showing up correctly then I stumbled upon a converter in Blender’s source code that gave me a few hints. I will investigate further.

barfowl commented 8 years ago

If you're making it past the topology validation that you had enabled earlier and are having problems with normals, I would be surprised if that was due to the topology being improperly constructed.

It may be too late for this now, but one thing I would recommend is comparing the results of your custom Factory to what is generate from just the face-vertex list of your mesh used to initialize a Far::TopologyDescriptor and its factory (as is done in all far/tutorial's but 1). Other than permutations of the edge list or incident components (i.e. closed rings of vertex neighbors are free to start anywhere) they should be the same. Any global or local discrepancy between the base levels of the two resulting TopologyRefiners should be relatively easy to identify on very simple models.

The Far::TopologyDescriptor and its Factory are the recommended entry point to the library for topology conversion. Unless the conversion process is taking up a considerable chunk of the time (and it is often dwarfed by other stages like refinement or construction of other objects like PatchTable and StencilTable), it may not be worth the trouble of writing a custom Factory initially. You can always go back and optimize the conversion with one later once everything else is in place and working as expected.

ghost commented 8 years ago

Just a sanity check, because you said most people use the default factory. For a mesh with one million faces the TopologyRefinerFactory::Create function takes about 1.4 ~ 1.6 seconds. I believe I’m building the library in release mode so I just want to make sure this is a normal timing since I don’t have anything to compare it at the moment. This is used in a modeling application where topology is changed often. Unless my timings are off, I think I have to go with a custom factory. I had to make some modifications in my mesh rep so I’m going to give it another try today. Up to now with a custom descriptor I manage to lower the creation time to 200ms but I think I was passing some relation wrong because after topology changed I was getting a few console messages from OSD about face correlation etc.

manuelk commented 8 years ago

Seems a little slow for a high-end CPU, but not too far fetched to me. It looks like you are using MSVC : make sure that STL's "safe" mode is off (should be in Release). Otherwise, i do remember writing a refiner factory that was getting all edge-vert topo relationships out of Maya. Maybe someone at Pixar can look at the cloth sim plugin code i wrote and see if they would be willing to share snippets.

At 1M faces though, i would start looking at the mesh itself :

barfowl commented 8 years ago

I'm going to be doing some profiling for some work I'm doing and will run a one million face mesh through when I get a chance to see what numbers I'm getting.

My point was that conversion is usually insignificant relative to what usage of OpenSubdiv usually follows. What are you doing with OSD with the TopologyRefiner that results?

Aside from the one million faces, what concerns me more is "topology is changed often". Most of what OpenSubdiv provides is geared toward static topology where a relatively high price is paid to initialize objects that are then used for repeated evaluation or display. If topology changes are localized in your application, partitioning the mesh would be to your advantage -- not partitioning the mesh within your application, but building multiple TopologyRefiners (and other associated OSD objects) for subsets of your mesh, mapping your faces to these and only updating (discarding and rebuilding) those whose topology change. That will also give you more control over threading during the initialization phase.

barfowl commented 8 years ago

I just ran a quick test with a 1000x1000 face grid, so one million faces (and no face-varying channel for UVs -- which adds an incremental overhead)...

Using the TopologyDescriptor, the Create() method for its factory takes around 465ms, while using a custom converter its Create() takes around 175ms.

So I'm a bit surprised at your timing for the TopologyDescriptor conversion, particularly given we are in the same ballpark with the custom converters. The topology representation I am converting from is very similar to what the TopologyRefinerFactory builds, so I wouldn't expect a custom converter to get that much faster (single threaded at least -- it is possible to multi-thread a converter in certain places given the way allocation and population of memory is staged, but I wouldn't recommend it at this point (I do recall population of some relations needed to be sequential)).

ghost commented 8 years ago

Thanks a lot for taking the time to run the test. It’s good to have something to compare to. I might have been somewhat off with my timing, I ran it again just now and it yielded about 700ms give or take with a UV channel. I’m currently working with Xcode and I couldn’t find any setting for STL. It would be nice because it might have explained some other issues I have when using STL containers. Im using this as a final modifier before rendering a mesh. Any deformation/topology changes made to the cage mesh are transferred over and rendered on screen. The only times I use the subdivided results other then rendering, is to check for ray intersections and in case the user wants to freeze the mesh where I transfer it over to my mesh rep. Mesh segmentation is definitely something I have in my future plans, maybe that could make the static nature of OSD almost transparent. It would be great if we could see some of those snippets you mentioned. Might be helpful to other also.

Till now, the only elements that are oriented in my mesh-rep are face edges. I suppose that vertex edges have to be oriented as well. So I’ll give that a try today and see how it goes.

manuelk commented 8 years ago

Secure SCL

barfowl commented 8 years ago

With a face-varying channel for UVs I'm up to 520ms which is bringing us closer together.

You described in general terms what you intended to do with your mesh, but I was more specifically curious about how you intend to process your mesh with OpenSubdiv -- and from there, how much those costs compare to the Create() method. Even just a single level of subdivision and interpolation of vertices involved comes out as multiple times the cost of Create() and creating a Far::PatchTable for evaluation/tessellation (implicitly or explicitly) is many times more.

But now we're getting back to the original point of this thread (not that the diversion wasn't worthwhile)... Yes, in addition to face-verts and face-edges being oriented, vertex-edges and vertex-faces must be ordered counter-clockwise as well (for manifold vertices that can be oriented). The paragraph you cited above is intended to communicate that, though it is a bit terse. The stub for TopologyRefinerFactory::assignComponentTopology() documents the requirements in more detail.

Granted the documentation and examples could be much better with more effort. In the meantime, if you think there was anything missing or misleading that could be easily improved, let me know. I already have a few ideas that I'll probably act on in a documentation pass next time around.

ghost commented 8 years ago

I found that Xcode doesn’t add the NDEBUG flag in release mode, so assertions don’t get removed by default. After I modified that flag I got a small overall speed increase. Oh, regarding mesh processing with OSD, I was hopping I would squeeze as much speed as I can by multithreading the actual refinement/interpolation with the use of Patch Tables and TBB. At the moment that’s solely hypothetical as I haven’t played around with Patch Tables that much. But you are right, the creation process, especially now that I got that speeded up a little might be insignificant to the actual time it takes for refinement/interpolation. I got most of my mesh elements oriented. I think my custom descriptor worked the first time around because most faces and edges were “accidentally” oriented when I first created the mesh but that orientation broke as I made topology changes.

Thanks a lot for your help and time!

manuelk commented 8 years ago

multithreading the actual refinement/interpolation

By its very nature, topo data manipulation is very hard to thread : the only way to make this work efficiently is to run multiple instances of refiners on separate primitives. That's why single very large meshes (1M faces) are "pathological" use-cases.

ghost commented 8 years ago

I see. Thanks for pointing that out. I guess I'll hold up on that until I get my mesh-rep segmented.