It happens too often that I meet a bright developer researching a new data structure or algorithm whose efforts are hindered by the visualization tools they possess. Often they end up trying to visualize complex 3D environments in 2D grayscale OpenCV maps because they've just never been exposed to the right tools.
OpenSceneGraph has helped me debug my algorithms many times over. Many times, visual tests of an algorithm have proven more useful and less error-prone than software tests. And because OSG is open and cross-platform, I don't have to worry about changing environments.
This guide is meant as a springboard to help those developers looking to visualize 3D scenes in a practical and efficient manner—without worrying about realistic shadows, transparency, and other effects. If that sounds like you, keep on reading!
This guide is meant to be a springboard for new developers who want to learn OpenSceneGraph. As such, it's not the most comprehensive nor detailed guide to the variety of capabilities of OpenSceneGraph, but it'll get you on your feet so you at least know what to Google and how to parse the forums. I won't be getting into the installation of OpenSceneGraph, though—hopefully you can figure that out on your own.
For reference, after installation, if you don't know how to link your practice examples to the OpenSceneGraph library, here is a sample CMakeLists.txt file to get you started (don't worry, I was new to CMake when I started, too).
Before we dive into scene graphs, just be aware that OSG ships
with its own brand of smart pointers, called
osg::ref_ptr<>. Just like
std::shared_ptr<>, which you may be used to, these
pointers maintain a count of how many times an object is
referenced and will automatically delete the object when its
reference count falls to zero. Unlike
std::shared_ptr<>, the reference count in OSG also
increments when an object is added to the scene graph and
likewise decrements when the object is removed. Generally, it's
good practice to use these smart pointers as much as possible.
You'll find in examples that even the helper methods for
constructing OSG objects for us will generally return a smart
pointer to the constructed object rather than a regular pointer.
Good practices like these will help prevent our objects from
being deleted underneath us when we least expect.
First and foremost, OpenSceneGraph is about constructing a graph, and for the purposes of this guide we can abstract away the specifics of how that graph is actually rendered into the 2D picture that appears on your screen and worry instead about creating just that graph. In fact, let's simplify further: for most purposes, your scene graph will really just be a scene tree.
Now, a tree consists of nodes, one of which is the root. This is
the root of your scene, and you'll see later that we specify
setSceneData(root) to define the point where
rendering begins. All other nodes will have one parent (in this
guide, anyway) and zero or more children. There's two main types
of nodes: group nodes and leaf nodes. Groups can have one or
more children, and they may apply some sort of transform or
special properties to all downstream children. Leaf nodes, on
the other hand, cannot have children. They describe some sort of
geometry or specific 2D/3D object in the scene, like a triangle
.3ds model imported from a CAD program.
With that, let's jump right in. Geodes are a special type of
node which contain geometries; in fact geode is short for
geometry node. The way to use these is to create one or more
osg::Drawables defining some geometry (like a line,
triangle, sphere, text, etc.) and add it to your geode using
geode->addDrawable(geom). Then, add your geode to
the scene graph under a group node with
Let's expand on three types of drawables you can use inside geodes—geometries, shapes (yes, they're different), and text.
Geometries are custom user-defined shapes. By combining individual shapes like triangles and quads, you can create shapes as simple as hexagons or as complex as pentagonal trapezohedrons—or really any shapes you can think of, including point clouds and nonconvex crescents. Let's start by creating a triangle.
If you build and run this, you should be able to launch a window
with a green triangle that you can spin around by clicking and
dragging your mouse. Play around with it. Try adding 6 vertices
instead of 3 and change the primitive type from
LINE_LOOP. Now switch it
LINE_STRIP Can you tell
what these do?
In this case, we chose to
BIND_OVERALL, meaning we
assigned a single color to the entire geometry. But we can
actually assign individual colors at each vertex—OSG will
blend the colors together for you if you add two more colors in
colors array and replace
Lighting is used to darken and lighten objects in the scene depending on the direction of the light source of the scene. For our purposes, they just make details hard to see so we turn them off. Blending enables helpful effects like transparency so we leave it on.
TRIANGLES to the geometry, we've
defined that we want to interpret every three vertices as a new
triangle. Of course, there's plenty of other primitive types.
Here's a non-exhaustive list to get you started:
||Renders each vertex as a point|
||Connects every pair of vertices with a line|
||Draws a line connecting every point in sequence|
||Renders every triplet of points as a triangle|
||Renders triangles with shared edges (see details here)|
||Renders triangles with a shared vertex (see details here)|
||Renders every 4 points as a quad|
||Renders quads with shared edges|
A variety of other basic shapes exist for quickly adding 3D
objects to scenes, such as
They behave similarly to geometries in that they are attached to
Adding text to scenes is done using the
osgText namespace. Simply create an
osgText::Text object and attach it to a geode using
addDrawable(). Common methods used with text are:
setAxisAlignment()Place the text on the XY, YZ, XZ, or other planes.
setAlignment()Options here include centered, left-aligned, right-aligned, etc.
As mentioned before, groups are simply a type of node in the
scene graph which contain children. Some groups, like
osg::Group don't have any special
properties—they simply help organize your scene graph in
an intelligible way. Others apply special properties to their
Below is a code snippet with a short example of how to use
groups in your scene graph. To run the code snippet, you will
need to download the example dataset for your version of
unzip it to a folder, and point an OSG-specific environment
$OSG_FILE_PATH to that folder so
the files can be found at runtime. If you don't set
$OSG_FILE_PATH, you'll have to provide paths to
each model, like cessna.osg.
"That covers objects in the scene, but how do you actually move
them around in the scene?" you may be wondering. Well, there's
two main ways of transforming objects in OSG:
MatrixTransform. Both are identical in their
functionality, but their interfaces differ.
For PositionAttitudeTransforms, or PATs for short you apply a
setScale() to transform all children downstream in
the scene graph. You can imagine how this could be used to
construct a robot arm in OSG—a relatively linear scene
graph where each linkage of the arm would be added under its own
osg::PositionAttitudeTransform, which itself would
be a child of the previous linkage of the arm. Changing the
position or attitude of any linkage would automatically
transform the downstream linkages.
MatrixTransforms behave the same way, but rather than setting a
position or attitude, you set a
In this case, we can see that the glider is translated along the
x-axis and is rotating relative to the origin of the scene,
marked by the set of axes. Note that
in the previous examples was automatically attaching a
manipulator to the scene for us. When we use
viewer.frame(), we have to create and attach our
own to enable zooming and panning.
Switches are used to selectively hide or show objects in the
scene. It's hard to find this written down anywhere, but it's
generally not a good idea to
from your scene graph often—rather, you should use
hide and show your nodes. To use a switch, initialize the
switch, add it to the scene, give it some children, and use
to make them visible or invisible.
LODs (Level of Detail)
LOD group nodes are used to make objects visible or invisible
depending on the distance of the camera from the object. When
you add a child, you also specify a range like so:
lod->addChild(node, 0, 1000). LOD nodes are often
used for paging terrain data in and out—for example, when
you zoom far out some high resolution terrain tiles can get
replaced by lower resolution ones, putting less burden on the
system and resulting in a smoother scene (all without the user
Well, that's a whirlwind tour of OpenSceneGraph. Of course, there's much more to it, including handling mouse interactions using intersections, keyboard interactions using event handlers, other types of manipulators, and autotransforms and billboards—perhaps for another blog post. Until then, these principles alone will get you pretty far with quickly creating complex 3D environments to visualize your algorithms. For more details, look to OSG Quick Start Guide. As you become more familiar, you can even take advantage of the open-source nature of OSG and explore the source code of applications like osgViewer, a command line utility that ships with OSG. Please feel free to share your adventures, and remember that play makes perfect!