OpenSceneGraph Tutorial for Algorithms Development

Prateek Sahay
7 min readSep 1, 2018

--

It happens too often that I see a bright developer researching a new data structure or algorithm only to be held back 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 are more useful and less error-prone than software-based tests (although both are recommended). And because OSG is open-source 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!

Introduction

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.

Smart Pointers

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.

Nodes

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 or a .obj or .3ds model imported from a CAD program.

Geodes

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 group->addChild(geode).

Let’s expand on three types of drawables you can use inside geodes — geometries, shapes (yes, they’re different), and text.

Geometries

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 TRIANGLES to LINE_LOOP. Now switch it to LINES, or LINE_STRIP Can you tell what these do?

Your first OSG geometry

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 the colors array and replace BIND_OVERALL with BIND_PER_VERTEX.

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.

By specifying 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:

Descriptions of some of the primitive types available in OSG

Shapes

A variety of other basic shapes exist for quickly adding 3D objects to scenes, such as Sphere, Box, Cone, and Cylinder. They behave similarly to geometries in that they are attached to geodes using addDrawable().

Text

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:

  • setText()
  • setPosition()
  • setFont()
  • setColor()
  • setAxisAlignment() Place the text on the XY, YZ, XZ, or other planes.
  • setAlignment() Options here include centered, left-aligned, right-aligned, etc.
  • setCharacterSize()

Groups

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 children.

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 OpenSceneGraph here, unzip it to a folder, and point an OSG-specific environment variable called $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.

Transforms

“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: PositionAttitudeTransform and MatrixTransform. Both are identical in their functionality, but their interfaces differ.

For PositionAttitudeTransforms, or PATs for short you apply a setPosition(), setAttitude(), and/or 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 Matrix instead.

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 viewer.run() 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

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 removeChildren() from your scene graph often—rather, you should use osg::Switch or node->setNodeMask() to hide and show your nodes. To use a switch, initialize the switch, add it to the scene, give it some children, and use setChildValue() or setSingleChildOn() 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 knowing).

Conclusion

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: play makes perfect!

--

--