OpenSceneGraph Programming

Index

Source Code, Examples, Skeletons

License: GNU GPL 2 Requires: OSG 2.x

OSG 2.x Example - Airplane

Download OSG 2.x example (airplane)

These aircraft models originated from the FlightGear simulator.
This example demonstrates:

  • Using Visitor pattern to find a node by name.
  • Splicing a transform node to articulate part of a model (to animate propeller).
  • Local rotation using matrix multiplication with a rotation matrix.
  • Rotating a light source.

CMake skeleton

Jean-Sébastien Guay's CMake skeleton for compiling an OSG 2.x app. Put your C++ files in a src/ subdir, then customize lines with TODO.

Download CMake skeleton for OSG 2.x

OSG Programming

Object Ownership

  • new osg::Class without a matching delete is an OSG programming idiom.
  • Internally, OSG is based on smart pointers with intrusive reference-counts (OSG::Referenced).
  • Passing an OSG object to an OSG API function transfers ownership to OSG. The graph will own what was attached by addChild().
  • osg::ref_ptr
    If an app needs to hold onto an OSG object after passing it to an OSG API function, the app must store it inside a osg::ref_ptr in order to increment its reference-count (to avoid unexpected deletion).

Reference-counted Objects

A pitfall is when a constructor passes itself as this in a ref_ptr. The pitfall opens because C/C++ evaluates the right side of an assignment before the left. The ctor executes before model has incremented the ref-cnt, so effectively nothing is holding a ref-cnt yet.1 When ctor passes this, upon returning, a premature deletion happens!

osg::ref_ptr<Model> mode; = new Model();

void
EnableAlphaBlending( osg::ref_ptr<Model> model );

Model::Model( void )
{
    EnableAlphaBlending( this );  // oops!
}

Matrix, Transform

Rotating a matrix can be done by making a rotation matrix, then doing matrix multiplication. Eg, to animate rotating an aircraft by 1 degree every frame, pre-compute a 1 degree rotation matrix from an identity matrix, then multiply it by a transform's matrix every frame. The rotation matrix can remain unchanged until the angle (rotation rate) has to change.

class
{
   osg::ref_ptr<osg::MatrixTransform> mTransform;       // rotated every frame
   osg::ref_ptr<osg::Matrix>          mRotationMatrix;  // coefficient of matrix multiplication
};

// Make a rotation matrix:
mRotationMatrix.makeRotate( degree, osg::Vec3f( 1.0f, 0.0f, 0.0f ) );  // X axis

// Rotate matrix every frame (matrix of OSG::MatrixTransform):
...
osg::Matrix m = mTransform->getMatrix();
m = mRotationMatrix * m;
mTransform->setMatrix( m );

Matrix Multiplication, Rotating Around Which Axis

Rotation of a matrix can be either in object space or fixed space.

Long way: Read a linear algebra textbook and study premultiply or postmultiply.
Short way: Run your program: if rotation is wrong, swap the coefficients.

m = mRotationMatrix * m;  // rotate around local axis
m = m * mRotationMatrix;  // rotate around fixed axis

Blending

Blending in OSG is similar to OpenGL, with the addition of using OSG's transparent bin (essentially another rendering pass) for transparent primitives.

// Enable blending, select transparent bin.
stateSet->setMode( GL_BLEND, osg::StateAttribute::ON );
stateSet->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );

// Enable depth test so that an opaque polygon will occlude a transparent one behind it.
stateSet->setMode( GL_DEPTH_TEST, osg::StateAttribute::ON );

// Conversely, disable writing to depth buffer so that
// a transparent polygon will allow polygons behind it to shine thru.
// OSG renders transparent polygons after opaque ones.
osg::Depth* depth = new osg::Depth;
depth->setWriteMask( false );
stateSet->setAttributeAndModes( depth, osg::StateAttribute::ON );

// Disable conflicting modes.
stateSet->setMode( GL_LIGHTING, osg::StateAttribute::OFF );

Billboards

osg::AutoTransform is an alternative to osg::Billboard. AutoTransform doesn't rotate as the viewpoint move close to it.

Reading Compressed 3D files

One way is to pass a C++ stream to osg::ReaderWriter::readNode( istreamstream& ). The C++ stream is the buffer for the decompressed file.


// Read gzip-compressed file into a C++ string.
string buf;
ReadGzipFile( buf, filename );

// Convert C++ string to a C++ stream.
istringstream ss( buf );

// Pass the C++ stringstream, containing the decompressed file, to OSG.
osg::ref_ptr<osgDB::ReaderWriter> readerWriter = \
   osgDB::Registry::instance()->getReaderWriterForExtension( modelFileSuffix );
osgDB::ReaderWriter::ReadResult res = readerWriter->readNode( ss );

Converting GIS Satellite Images for OSG

1st install GDAL. 2nd install VirtualPlanetBuilder.

Go to USGS Seamless Data Distribution System to get satellite imagery. In Firefox, allow usgs.gov to open popups. Pick a location, outline a small square, download. A page will popup, select options, check Orthoimagery (near bottom of popup). Not all locations have Orthoimagery data.

-rw------- 1 jimb jimb     39214 2007-11-26 13:52 84233928.aux
-rw------- 1 jimb jimb      4099 2007-11-26 13:52 84233928.prj
-rw------- 1 jimb jimb        95 2007-11-26 13:52 84233928.tfw
-rw------- 1 jimb jimb 143794609 2007-11-26 13:52 84233928.tif
-rw------- 1 jimb jimb     23160 2007-11-26 13:52 meta1.html
-rw------- 1 jimb jimb     15558 2007-11-26 13:52 Metadata.xml

To preview the TIF file, use this command to create a smaller TIF file (the TIF file is too gigantic for viewing).

gdal_translate -outsize 5% 5% orig.tif small.tif

This is the command to generate a scene graph from the satellite imagery. The same .TIF file is used as the DEM (height-map) and the texture. .ive is in face OSG's native binary format. -a neatly stores a multitude of auxillary files in the single .osga file.

osgdem -d 84233928.tif -t 84233928.tif -o sfbay.ive -a sfbay.osga -l 8 -v 0.08

Links

OSG Programming Links

Misc OSG Programming Notes

  • drawable, geode:
  • A Geometry is derived from Drawable (isn't a Node).
    A Geode is a Node that has a Drawable attached to it.
    Hence the name "geode" (a Node with a Geometry).
    
    Drawable --> Geometry
    Node --> Geode
    
    geode->addDrawable( geometry );
    
  • modes vs. attributes:
  • OSG modes directly correlate to OpenGL modes such as shading and blending.
    Attributes are the parameters of a mode such as the shade model and blend function.
    OSG defines attributes as set of classes derived from StateAttribute (BlendFunc, etc).
    
    A mode is part of a StateSet, so:
    osg::StateSet::setMode()
    
    For convenience, both an attribute and mode can be set by setAttributeAndModes():
    osg::BlendFunc* bf = new osg::BlendFunc();
    state->setAttributeAndModes( bf, osg::StateAttribute::ON );
    state->setAttributeAndModes( bf );  // ON is implied
    
  • Inheriting state:
  • A child inherits state from its parent.
    
    OVERRIDE forces applying a state to all children, regardless.
    PROTECTED makes an exception to OVERRIDE, allowing a child to "override OVERRIDE".
    
    BY DEFAULT, STATE-SET IN A CHILD NODE OVERRIDES
    THE *SAME* TYPE OF STATE-SET IN A PARENT NODE [Martz].
    
       A
      / \
     B   C
    
    A sets a blue color.
    B sets a red color.
    C doesn't change color.
    What color will C be?
    C will be the same color as B (suprise) because OSG will let
    the last glColor() remain in effect (applied when B was visited).
    The "return traversal" from B --> A doesn't restore nor pop OpenGL state.
    This isn't peculiar to OSG (this is fundamental behavior of any scene graph).
    
  • Disabling nodes:
  • node->setNodeMask(0)
    
    This can effectively disable not-so-obvious nodes such as Cameras etc.
    
  • Visitor pattern, NodeVisitor, apply():
  • OSG names the Visitor design pattern's visit() method as apply().
    A derivative of NodeVisitor should override apply().
    
    class Visitor : public osg::NodeVisitor
    {
        virtual void apply( osg::Node& node )  // visit() method
        {
            ...
              // Keep searching.
              traverse( node );
        }
    };
    
  • Screenshot, recording an image:
  • osg::Image* shot = new osg::Image();
    shot->allocateImage(width, height, 24, GL_RGB, GL_UNSIGNED_BYTE);
    camera->attach(osg::Camera::COLOR_BUFFER, shot);
    osgDB::writeImageFile(*shot,"image_file.png");
    Then Viewer has to render a frame in order to get a screenshot.
    
  • Direct rendering:
  • OSG provides different ways to directly issue OpenGL calls:
    
    1. osg::Operation callback of an osg::Window (Palomino uses this for motion-blur).
    2. osg::Camera::DrawCallback
    3. derivative of osg::Drawable
    
    The first two ways renders per-frame.
    The third way (osg::Drawable) renders per-node.
    
    An excellent example of rendering per-node (osg::Drawable) is osgteapot.
    
    class Teapot : public osg::Drawable
    {
        virtual void drawImplementation( osg::RenderInfo& ) const;
    
        // We need to set up the bounding box of the data too, so that the
        // scene graph knows where this object is, for both positioning
        // the camera at start up, and most importantly for culling.
        virtual osg::BoundingBox computeBound() const;
    };
    
  • render bins:
  • [Martz]
    "Subject: Re: [osg-users] Render Queue
    The osgUtil::CullVisitor creates RenderBins (render bins) from the visible Drawables,
    then each RenderBin is processed.  So, take a look at CullVisitor and RenderBin.
    Then also look at, for example, osg::Geometry::apply to see how Geometry is
    sent to OpenGL.  You can capture the OpenGL commands with a tool like GLIntercept.
    This will show you what OSG is doing to render the scene."
    
    [Osfield]
    "The state sorting in the OSG is done as part of the cull traversals
    when the various Drawables and Stateset's are placing in the rendering
    backend, composed of RenderBin/RenderStages and a StateGraph.  When a
    RenderBin is state sorted sorting is done via an STL map container,
    sorting purely on the ptr to the StateSet.  In the default state
    sorting no fine grained start sorting is done - so contents of
    StateSet aren't compared.  Its possible to enable fine grained state
    sorting but I've found that the cost of such sorting generally
    outweighs the benefits, what is done by default appears to be a good
    compromise between low CPU overhead and efficient use of the GPU."
    
  • To disable lighting:
  • PROTECTED doesn't work but OVERRIDE does.
    mSwitchNode->getOrCreateStateSet()->setMode( GL_LIGHTING, osg::StateAttribute::OVERRIDE | osg::StateAttribute::OFF );
    
  • multiple lights:
  • osg::Light is a state attribute;
    osg::LightSource is a Group.
    Only one osg::Light can be attached to a osg::LightSource.
    So to enable multiple lights, build a linear chain of LightSources (light groups).
    Then add geometry at the end of the chain.
    
       root
        |
        V
    lightSource 0
        |
        V
    lightSource 1
        |
        V
      geodes
    
  • To handle the ESC key:
  • viewer->setKeyEventSetsDone( 0 );
    
  • Cameras:
  • Cameras define the 3D modelview matrix, 2D viewpoint, and render target.
    Cameras are attached to the GraphicsContext of a GraphicsWindows.
    
    Cameras can either be attached as a node to the scene graph (old way),
    or added to an osg::Viewer (new way).
    
    Manipulators control a camera.
    Thus a manipulator will effectively define the modelview matrix.
    Use Manipulator::getInverseMatrix() to extract a modelview matrix.
    
    A camera defines the rendering target.
    Rendering to different 2D viewports of a window, each with its own viewpoint,
    is done in OSG by adding more cameras.
    Rendering to a texture (RTT) is done thru an osg::Camera also.
    Set the "render target" of the camera to a texture.
    
    Callbacks to invoke before/after a view is drawn can be registered by osg::Camera.
    
  • To enable windowed-mode in Viewer:
  • viewer.setUpViewInWindow( 0, 0, 1024, 768 );
    
   index
©2007,2009 Jim Brooks