A week into using OpenSceneGraph, I like to think I have a pretty good grasp on its concepts and internal workings. Having used Ogre previously helps a lot, but there are a few Ogre concepts that I’ve had to un-learn in order to proceed. This is not by any means a comparison of which library is “better”, nor do I think these are points the Ogre team should be improving on – it’s better to improve on the aspects that set the two libraries apart, in my opinion. The design goals are also different, with Ogre more focused on ease of use, and OSG more focused on flexibility.
In Ogre, the scene graph is mostly used to manage the derived transforms of the Renderables attached to leaf nodes, and that’s it. In OSG, the scene graph is a much more integral part of the library’s design and feature set. A few examples:
In Ogre, a Material contains all possible render state in it, and Materials can only be set on Renderables, i.e. leafs.
In OSG, any node can have a StateSet attached to it. A StateSet behaves more like an std::map or std::set in that it only contains the render state you requested, and none else. For state that you haven’t set up, the renderer will effectively use either the parent’s State, or if no parent defines this State either, then the OpenGL default state. The State of a child node will override the State of its parent, unless the parent has an “override” flag set on it.
I really like this approach, it makes it easy to toggle state on an entire sub-graph, whereas in Ogre you’d need to walk over all renderables and change their material, being careful not to affect any unintended renderables when materials are shared.
It also allows for better optimization of the renderer itself, by reducing redundant state changes, and rarely used state like stencil settings will have zero overhead if they’re not contained in any StateSets.
Passes and techniques
One side effect of the StateSet approach having children override their parent’s state is that multipass rendering is no longer inbuilt into the StateSet. A StateSet effectively describes a single pass only. In Ogre, each material has a vector of Techniques and each Technique a vector of passes, which then contains the actual render state.
In OSG, multipass rendering is handled by a special node, which traverses it’s children N times when N passes are defined, and sets the StateSet of the appropriate Pass on each traversal.
Being able to define multiple passes on an entire sub-graph rather than just on leafs is very powerful, and the implementation is in the stand-alone osgFX component, which means the renderer itself doesn’t have to deal with the complexity of multiple passes/techniques, nor do users who do not need multiple passes/techniques need to know that the feature even exists.
Nodes can have multiple parents
This seemed a bit strange when I first came across it. Nodes can have more than one parent, which means the node will be traversed (and rendered) multiple times, similar to the multipass implementation. Since each traversal is coming from a different node path, it can have a different world matrix, different State, etc. One example use for this is to create clones of a part of the scene at different positions, with very low memory usage.
Obviously there are more sophisticated techniques for cloning geometry, like static geometry batching and instancing, but they have their own drawbacks, in particular with high memory usage, culling problems, micromanagement overhead, and not being able to properly set up the closest point lights for each individual mesh, which was a big issue in OpenMW.
Of course, with more power comes more responsibility, so you need to be careful not to create loops in your graph (I tried it – stack overflow).
Streamlined vertex formats
osg::Geometry only deals with non-interleaved vertex formats, i.e. vertices, normals, and UV coords are all in separate buffers. This makes it much easier to implement SW skinning/morphing or passing the vertices to your physics engine for raytesting purposes. Note that should you really need interleaved vertex data, it can still be used by creating your own Drawable class.
No custom allocators
All allocations are done using the standard new, whereas in Ogre you have to use macros like OGRE_MALLOC / OGRE_MALLOC_T whenever passing allocated memory to the library. By default Ogre uses the nedmalloc allocator, which at the time it was implemented promised a performance boost on some platforms, but should be obsolete by now, as stated on its homepage:
(Windows 7, Linux 3.x, FreeBSD 8, Mac OS X 10.6 all contain state-of-the-art allocators and no third party allocator is likely to significantly improve on them in real world results).
It might be about time to switch Ogre back to using standard allocators, and while at it, maybe remove the custom allocator system all together, because it does add some complexity for the user to deal with.
Both Ogre and OSG are using CMake, so it can be interesting to compare how they’re using it.
I said this wouldn’t be a competition, but here I found the difference in complexity and platform specific workarounds to be staggering. OSG is the clear winner in this category… sorry. See for yourself: