Implementing Multithreaded Operations and Rendering in OpenSceneGraph

Exclusive offer: get 50% off this eBook here
OpenSceneGraph 3.0: Beginner's Guide

OpenSceneGraph 3.0: Beginner's Guide — Save 50%

Create high-performance virtual reality applications with OpenSceneGraph, one of the best 3D graphics engines.

$29.99    $15.00
by Rui Wang Xuelei Qian | February 2011 | Beginner's Guides Open Source

In this article we are going to introduce the techniques necessary for building a fast, real-time rendering system that will help users to load, organize, and render massive datasets in an efficient manner. It is relatively easy to learn all the classes, methods, and global variables of a large set of API calls, but the way to put what has been learned into practical use, properly and efficiently, is another thing. The methods to improve rendering efficiency here may help to solve some engineering problems that we meet from time to time.

In this article by Rui Wang and Xuelei Qian, authors of OpenSceneGraph 3.0: Beginner's Guide, we will learn:

  • The basic principles of implementing multithreaded operations and rendering in OSG
  • The concept of scene culling and the occlusion culling technique

 

OpenSceneGraph 3.0: Beginner's Guide

OpenSceneGraph 3.0: Beginner's Guide

Create high-performance virtual reality applications with OpenSceneGraph, one of the best 3D graphics engines.

  • Gain a comprehensive view of the structure and main functionalities of OpenSceneGraph
  • An ideal introduction for developing applications using OpenSceneGraph
  • Develop applications around the concepts of scene graphs and design patterns
  • Extend your own scene elements from the base interfaces of OpenSceneGraph
  • Packed with examples, this book explains each knowledge point in detail and makes you practice your knowledge for better understanding
        Read more about this book      

(For more resources on OpenSceneGraph, see here.)

The reader can benefit from the previous article on OpenSceneGraph: Methods for Improving Rendering Efficiency.

OpenThreads basics

OpenThreads is a lightweight, cross-platform thread API for OSG classes and applications. It supports the fundamental elements required by a multithreaded program, that is, the thread object (OpenThreads::Thread), the mutex for locking data that may be shared by different threads (OpenThreads::Mutex), barrier (OpenThreads::Barrier), and condition (OpenThreads::Condition). The latter two are often used for thread synchronization.

To create a new thread for certain purposes, we have to derive the OpenThreads::Thread base class and re-implement some of its virtual methods. There are also some global functions for conveniently handling threads and thread attributes, for example:

  • The GetNumberOfProcessors() function gets the number of processors available for use.
  • The SetProcessorAffinityOfCurrentThread() function sets the processor affinity (that is, which processor is used to execute this thread) of the current thread. It should be called when the thread is currently running.
  • The CurrentThread() static method of OpenThreads::Thread returns a pointer to the current running thread instance.
  • The YieldCurrentThread() static method of OpenThreads::Thread yields the current thread and lets other threads take over the control of the processor.
  • The microSleep() static method of OpenThreads::Thread makes the current thread sleep for a specified number of microseconds. It can be used in single-threaded applications, too.

Time for action – using a separate data receiver thread

In this example, we will design a new thread with the OpenThreads library and use it to read characters from the standard input. At the same time, the main process, that is, the OSG viewer and rendering backend will try retrieving the input characters and displaying them on the screen with the osgText library. The entire program can only quit normally when the data thread and main process are both completed.

  1. Include the necessary headers:

    #include <osg/Geode>
    #include <osgDB/ReadFile>
    #include <osgText/Text>
    #include <osgViewer/Viewer>
    #include <iostream>

  2. Declare our new DataReceiverThread class as being derived from OpenThreads::Thread. Two virtual methods should be implemented to ensure that the thread can work properly: the cancel() method defines the cancelling process of the thread, and the run() method defines what action happens from the beginning to the end of the thread. We also define a mutex variable for interprocess synchronization, and make use of the singleton pattern for convenience:

    class DataReceiverThread : public OpenThreads::Thread
    {
    public:
    static DataReceiverThread* instance()
    {
    static DataReceiverThread s_thread;
    return &s_thread;
    }
    virtual int cancel();
    virtual void run();

    void addToContent( int ch );
    bool getContent( std::string& str );
    protected:
    OpenThreads::Mutex _mutex;
    std::string _content;
    bool _done;
    bool _dirty;
    };

  3. The cancelling work is simple: set the variable _done (which is checked repeatedly during the run() implementation to true) and wait until the thread finishes:

    int DataReceiverThread::cancel()
    {
    _done = true;
    while( isRunning() ) YieldCurrentThread();
    return 0;
    }

  4. The run() method is the core of a thread class. It usually includes a loop in which actual actions are executed all the time. In our data receiver thread, we use std::cin.get() to read characters from the keyboard input and decide if it can be added to the member string _content. When _done is set to true, the run() method will meet the end of its lifetime, and so does the whole thread:

    void DataReceiverThread::run()
    {
    _done = false;
    _dirty = true;
    do
    {
    YieldCurrentThread();

    int ch = 0;
    std::cin.get(ch);
    switch (ch)
    {
    case 0: break; // We don't want '\0' to be added
    case 9: _done = true; break; // ASCII code of Tab = 9
    default: addToContent(ch); break;
    }
    } while( !_done );
    }

  5. Be careful of the std::cin.get() function: it firstly reads one or more characters from the user input, until the Enter key is pressed and a '\n' is received. Then it picks characters one by one from the buffer, and continues to add them to the member string. When all characters in the buffer are traversed, it clears the buffer and waits for user input again.
  6. The customized addToContent() method adds a new character to _content. This method is sure to be called in the data receiver thread, so we have to lock the mutex object while changing the _content variable, to prevent other threads and the main process from dirtying it:

    void DataReceiverThread::addToContent( int ch )
    {
    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
    _content += ch;
    _dirty = true;
    }

  7. The customized getContent() method is used to obtain the _content variable and add it to the input string argument. This method, the opposite of the previous addToContent() method, must only be called by the following OSG callback implementation. The scoped locking operation of the mutex object will make the entire work thread-safe, as is done in addToContent():

    bool getContent( std::string& str )
    {
    OpenThreads::ScopedLock<OpenThreads::Mutex> lock(_mutex);
    if ( _dirty )
    {
    str += _content;
    _dirty = false;
    return true;
    }
    return false;
    }

  8. The thread implementation is finished. Now let's go back to rendering. What we want here is a text object that can dynamically change its content according to the string data received from the main process. An update callback of the text object is necessary to realize such functionality. In the virtual update() method of the customized update callback (it is for drawables, so osg::NodeCallback is not needed here), we simply retrieve the osgText::Text object and the receiver thread instance, and then reset the displayed texts:
    class UpdateTextCallback : public osg::Drawable::UpdateCallback
    {
    public:
    virtual void update( osg::NodeVisitor* nv,
    osg::Drawable* drawable )
    {
    osgText::Text* text =
    static_cast<osgText::Text*>(drawable);
    if ( text )
    {
    std::string str("# ");
    if ( DataReceiverThread::instance()->getContent(str) )
    text->setText( str );
    }
    }
    };
  9. In the main entry, we first create the osgText::Text drawable and apply a new instance of our text updating callback. The setAxisAlignment() here defines the text as a billboard in the scene, and setDataVariance() ensures that the text object is "dynamic" during updating and drawing. There is also a setInitialBound() method, which accepts an osg::BoundingBox variable as the argument. It forces the definition of the minimum bounding box of the drawable and computes the initial view matrix according to it:

    osg::ref_ptr<osgText::Text> text = new osgText::Text;
    text->setFont( "fonts/arial.ttf" );
    text->setAxisAlignment( osgText::TextBase::SCREEN );
    text->setDataVariance( osg::Object::DYNAMIC );
    text->setInitialBound(
    osg::BoundingBox(osg::Vec3(), osg::Vec3(400.0f, 20.0f, 20.0f))
    );
    text->setUpdateCallback( new UpdateTextCallback );

  10. Add the text object to an osg::Geode node and turn off lighting. Before starting the viewer, we also have to make sure that the scene is rendered in a fixed-size window. That's because we have to also use the console window for keyboard entry:

    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->addDrawable( text.get() );
    geode->getOrCreateStateSet()->setMode(
    GL_LIGHTING, osg::StateAttribute::OFF );
    osgViewer::Viewer viewer;
    viewer.setSceneData( geode.get() );
    viewer.setUpViewInWindow( 50, 50, 640, 480 );

  11. Start the data receiver thread before the viewer runs, and quit it after that:

    DataReceiverThread::instance()->startThread();
    viewer.run();
    DataReceiverThread::instance()->cancel();
    return 0;

  12. Two windows will appear if you are compiling your project with your subsystem console. Set focus to the console window and type some characters. Press Enter when you are finished, and then press Tab followed by Enter in order to quit the receiver thread:

    OpenSceneGraph

  13. You will notice that the same characters come out in the OSG rendering window. This can be treated as a very basic text editor, with the text source in a separate receiver thread, and the drawing interface implemented in the OSG scene graph:

    OpenSceneGraph

What just happened?

It is very common that applications use separate threads to load huge files from disk or from the Local Area Network (LAN). Other applications use threads to continuously receive data from the network service and client computers, or user-defined input devices including GPS and radar signals, which is of great speed and efficiency. Extra data handling threads can even specify an affinity processor to work on, and thus make use of today's dual-core and quad-core CPUs.

The OpenThreads library provides a minimal and complete object-oriented thread interface for OSG developers, and even general C++ threading programmers. It is used by the osgViewer library to implement multithreaded scene updating, culling, and drawing, which is the secret of highly efficient rendering in OSG. Note here, that multithreaded rendering doesn't simply mean executing OpenGL calls in different threads because the related rendering context (HGLRC under Win32) is thread-specific. One OpenGL context can only be current in one thread (using wglMakeCurrent() function). Thus, one OSG rendering window which wraps only one OpenGL context will never be activated and accept OpenGL calls synchronously in multiple threads. It requires an accurate control of the threading model to make everything work well.

 

OpenSceneGraph 3.0: Beginner's Guide Create high-performance virtual reality applications with OpenSceneGraph, one of the best 3D graphics engines.
Published: December 2010
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

 

        Read more about this book      

(For more resources on OpenSceneGraph, see here.)

Understanding multithreaded rendering

The traditional method of real-time rendering always involves three separate phases: user updating (UPDATE), scene culling (CULL), and executing OpenGL calls (DRAW).

User updating include all kinds of dynamic data modifications and operations, like changing the scene graph hierarchy, loading files, animating mesh vertices, and updating camera positions and attitudes. It then sends the scene graph to the culling phase, within which the scene is rebuilt, for the purpose of improving final rendering performance. Objects that are invisible in the viewing frustum or hidden for any reason will be removed, and the rest are sorted by rendering states and pushed into a drawing list. The list will be traversed in the final, drawing phase, and all OpenGL commands will be issued to the graphics pipeline for processing.

A single processor system would need to process all three phases serially, which may cause the one frame to be too long to fit user requirements.

In a system with multiple processors and multiple display devices, we can have more parallelizable CULL and DRAW tasks to speed up the frame rate. Especially when managing more than one rendering windows, it is necessary to have a new threading model with one CULL and one DRAW phase for each window, and execute them concurrently. This is, of course, more efficient than just using a single thread.

Time for action – switching between different threading models

OSG provides a very convenient interface for choosing a threading model. Different threading models can be used in different circumstances, and have different efficiencies. In this example, we are going to show the difference between three common threading models when running a scene with a huge number of quad geometries, in three rendering windows of an osgViewer::CompositeViewer, synchronously.

  1. Include the necessary headers:

    #include <osg/Group>
    #include <osgDB/ReadFile>
    #include <osgViewer/ViewerEventHandlers>
    #include <osgViewer/CompositeViewer>

  2. The quads can be generated with the osg::createTexturedQuadGeometry() function. Their positions are decided simply by a random number generator. One such quad doesn't consume too much system resource. But a considerable number of these quads without using object sharing, will quickly waste system and video card memory (because of the construction of each geometry's display list), which is helpful for testing the system load capacity:

    #define RAND(min, max) \
    ((min) + (float)rand()/(RAND_MAX+1) * ((max)-(min)))
    osg::Geode* createMassiveQuads( unsigned int number )
    {
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    for ( unsigned int i=0; i<number; ++i )
    {
    osg::Vec3 randomCenter;
    randomCenter.x() = RAND(-100.0f, 100.0f);
    randomCenter.y() = RAND(1.0f, 100.0f);
    randomCenter.z() = RAND(-100.0f, 100.0f);

    osg::ref_ptr<osg::Drawable> quad =
    osg::createTexturedQuadGeometry(
    randomCenter,
    osg::Vec3(1.0f, 0.0f, 0.0f),
    osg::Vec3(0.0f, 0.0f, 1.0f)
    );
    geode->addDrawable( quad.get() );
    }
    return geode.release();
    }

  3. The composite viewer requires a separate osgViewer::View instance for the rendering windows. The window location and size are determined by the setUpViewInWindow() method:

    osgViewer::View* createView( int x, int y, int w, int h,
    osg::Node* scene )
    {
    osg::ref_ptr<osgViewer::View> view = new osgViewer::View;
    view->setSceneData( scene );
    view->setUpViewInWindow( x, y, w, h );
    return view.release();
    }

  4. In the main entry, we first use an argument parser to select a threading model. By default, OSG will automatically choose the best threading strategy according to the number of processors and rendering windows of the application, that is, the AutomaticSelection case. But we can still specify a way to handle multithreaded rendering from inbuilt ones, including SingleThreaded, ThreadPerContext, and ThreadPerCamera:

    osg::ArgumentParser arguments( &argc, argv );

    osgViewer::ViewerBase::ThreadingModel th =
    osgViewer::ViewerBase::AutomaticSelection;
    if ( arguments.read("--single") ) th =
    osgViewer::ViewerBase::SingleThreaded;
    else if ( arguments.read("--useContext") ) th =
    osgViewer::ViewerBase::ThreadPerContext;
    else if ( arguments.read("--useCamera") ) th =
    osgViewer::ViewerBase::ThreadPerCamera;

  5. Create three rendering views and apply massive quad geometries to each of them. Totally, 20 thousand quads are allocated in this example for the purpose of illustrating different threading models:

    osgViewer::View* view1 = createView( 50, 50, 640, 480,
    createMassiveQuads(10000) );
    osgViewer::View* view2 = createView( 50, 550, 320, 240,
    createMassiveQuads(5000) );
    osgViewer::View* view3 = createView( 370, 550, 320, 240,
    createMassiveQuads(5000) );
    view1->addEventHandler( new osgViewer::StatsHandler );

  6. Create a composite viewer and set the user-specified threading model. Note that the setThreadingModel() method here not only works for osgViewer::CompositeViewer, but is also available for more common osgViewer::Viewer instances:

    osgViewer::CompositeViewer viewer;
    viewer.setThreadingModel( th );

    viewer.addView( view1 );
    viewer.addView( view2 );
    viewer.addView( view3 );
    return viewer.run();

  7. Compile the application (assuming that its name is MyProject.exe) and enter the following command in console mode:

    # MyProject.exe --single

  8. The result is shown in the following image. Notice that the frame rate is only 20 for the single threaded model, in which the update, cull, and draw phases are executed one by one in the same thread:

    OpenSceneGraph

  9. Change the argument --single to --useContext and start the test program again. This time you will find the frame rate has increased. This is because OSG uses separate threads for culling and drawing besides the user update phase, which improves the rendering performance a lot.

    OpenSceneGraph

  10. Change the command line to --useCamera and restart the program once more. This is actually the default strategy for most multi-processor computers these days. It should be even better than the second threading model, because it uses different threads for cameras and rendering windows, and runs threads on separate CPUs to obtain maximum efficiency:

    OpenSceneGraph

What just happened?

The SingleThreaded threading models can be demonstrated by the diagram below. The CULL and DRAW phases in each rendering window (view) may have different aggregated time, and one frame here is defined as the total time starting from the first view's CULL, and to the end of the last view's DRAW. User updating operations are ignored here because they always take the same aggregated time in all threading models:

OpenSceneGraph

The updating, culling, and drawing operations are always executed in one thread. If there are multiple sub-views, that is, multiple culling and drawing tasks to finish, then they are going to be issued one by one. This is the most inefficient model of rendering the scene in OSG, but it is still useful for testing new functionalities. It also simplifies the integration with GUIs such as MFC and Qt. Because we do not care about thread conflicts, you may just put the run() method of osgViewer::Viewer or osgVIewer::CompositeViewer in a GUI timer event callback, instead of using an additional thread.

The ThreadPerContext model can be described by the following image:

OpenSceneGraph

Every sub-view of the composite viewer will have its own thread in which to execute culling and drawing tasks. Because of the parallelization characteristic of threads, the execution time of a frame will be shorter than the total time of the longest CULL and DRAW pair. After all DRAW tasks are finished, the user update of the next frame will start immediately.

This is much better in rendering performance than the first single-threaded model. It can even make use of multiple processors because each thread can occupy an individual processor in order to maximize the use of hardware resources.

However, an even better solution is the ThreadPerCamera model. This separates the CULL phase of each view from the DRAW phase and implements them in threads, too. This means we can have at least one CULL thread and one DRAW thread for each rendering window and therefore can make full use of multi-processor systems. Because the culling operations must be related with an osg::Camera node (it manages view and projection matrices for viewfrustum culling), we call this threading model a "thread per camera" model, as illustrated in the following image:

OpenSceneGraph

In this threading model, the DRAW phase is considered as two parallel processes, dispatching commands on the CPU side, and issuing a rendering buffer swap to execute them on the GPU side. The time-cost of swapping buffer operations can be unified and executed after all of the DRAW dispatching operations are done. But before that, this threading model starts the user UPDATE phase of the next frame in advance. This exciting work improves the rendering performance again, but may cause unexpected results if a user's updates change scene data that is being dispatched. That is why we are going to set a dynamic flag for scene objects that may be modified:

node->setDataVariance( osg::Object::DYNAMIC );

By default, OSG will suggest the ThreadPerCamera threading model, if a multi-processor system is detected.

 

OpenSceneGraph 3.0: Beginner's Guide Create high-performance virtual reality applications with OpenSceneGraph, one of the best 3D graphics engines.
Published: December 2010
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

 

        Read more about this book      

(For more resources on OpenSceneGraph, see here.)

Dynamic scene culling

The culling technique can be described easily: don't draw things you can't see. We can achieve this goal in two main ways: by reducing polygon faces that do not need to be detailed, and ignoring objects that are invisible in the current viewport.

The former is usually implemented by level-of-detail (LOD) algorithms, which in OSG is done by the osg::LOD class. The latter, which is actually the definition of scene culling, will aim to find objects in the scene graph that don't need to be rendered at all. There are several kinds of culling techniques assigned in OSG:

  • Back face culling: This is implemented by the osg::CullFace class, which encapsulates OpenGL's glCullFace() function. It removes all polygons facing away from the camera from the rendering pipeline, thus reducing the memory traffic. This technique works well, especially for manifold watertight objects, but may be erroneous for transparent objects or objects with holes.
  • Small feature culling: This technique enables the removal of objects that are too small to be seen, based on a visibility test, the outcome of which is the number of pixels that the object would affect if drawn. If that number is lower than the user-defined mini-pixel threshold, the object will be removed from the rendering list.
  • View-frustum culling: The idea here is simply not to render what is outside of the viewing volume (often a truncated pyramid, that is, a frustum) defined by the view and projection matrices of the rendering window. This is one of the most efficient methods in modern rendering applications.
  • Occlusion culling: This technique attempts to determine what objects are totally invisible because they are behind other objects. We will discuss this method soon, in the next section.

Note that the small feature culling method may cause actual geometry points to be not renderable. To disable this feature, we can make use of the setCullingMode() method of the camera node:

camera->setCullingMode(
camera->getCullingMode() & ~osg::Camera::SMALL_FEATURE
_CULLING );

Occluders and occludees

When rendering a complex scene, it is very common that two or more objects are overlapped, from the perspective of the viewer. This could lead to an overdraw, which means that pixels at the same location will be written to the frame buffer several times, while the final image only shows the last one. This causes efficiency losses because of multiple drawing that is not necessary (so called overdrawing).

The Occlusion culling technique simply increases the rendering performance by not rendering geometries hidden by other objects that are closer to the camera. The objects that cover other renderables are called occluders, and the rest of the scene graph can be treated as occludees (but it's not necessary to use such an unfamiliar word).

The general occlusion culling algorithm performs a visibility test on every object in the scene (of course, they should pass the view-frustum culling method first). The algorithm checks whether an object is occluded by an occlusion representation, which consists of some kind of occlusion information, for instance, polygonal clipping volumes, that can be used as occluders.

OSG provides the osg::OccluderNode class for implementing a basic occlude object. It is derived from osg::Group, and will check the relation between its occlusion representation and all scene nodes and objects except its children. This means that the osg::OccluderNode's child nodes will never be occluded and can thus represent the geometry of the occluder.

Time for action – adding occluders to a complex scene

The scene which demonstrates how to use occlusion culling should have two parts: a huge number of geometries that must be culled sometime to improve the efficiency, and a few good enough osg::OccluderNode instances as occluders. Here we are going to create massive data once more, and create an occluder plane, which can speed up the rendering by removing quads that are behind it from the graphics pipeline.

  1. Include the necessary headers:

    #include <osg/Geometry>
    #include <osg/Geode>
    #include <osgViewer/ViewerEventHandlers>
    #include <osgViewer/Viewer>

  2. The massive quads creation function is listed here again.

    #define RAND(min, max) \
    ((min) + (float)rand()/(RAND_MAX+1) * ((max)-(min)))
    osg::Geode* createMassiveQuads( unsigned int number )
    {
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    for ( unsigned int i=0; i<number; ++i )
    {
    osg::Vec3 randomCenter;
    randomCenter.x() = RAND(-100.0f, 100.0f);
    randomCenter.y() = RAND(1.0f, 100.0f);
    randomCenter.z() = RAND(-100.0f, 100.0f);

    osg::ref_ptr<osg::Drawable> quad =
    osg::createTexturedQuadGeometry(
    randomCenter,
    osg::Vec3(1.0f, 0.0f, 0.0f),
    osg::Vec3(0.0f, 0.0f, 1.0f)
    );
    geode->addDrawable( quad.get() );
    }
    return geode.release();
    }

  3. In the main entry, we first create the occluder node:

    osg::ref_ptr<osg::OccluderNode> occluderNode = new
    osg::OccluderNode;

  4. The occlusion representation class is osg::ConvexPlanarOccluder, which is actually made up of a convex clipping polygon (defined by the getOccluder() method) with several holes (defined by the addHole() method). The polygon and hole vertices are added by the add() method of the osg::ConvexPlanarPolygon class. The occlusion representation must be set to the occluder node with the setOccluder() method:

    osg::ref_ptr<osg::ConvexPlanarOccluder> cpo = new
    osg::ConvexPlanarOccluder;
    cpo->getOccluder().add( osg::Vec3(-120.0f, 0.0f,-120.0f) );
    cpo->getOccluder().add( osg::Vec3( 120.0f, 0.0f,-120.0f) );
    cpo->getOccluder().add( osg::Vec3( 120.0f, 0.0f, 120.0f) );
    cpo->getOccluder().add( osg::Vec3(-120.0f, 0.0f, 120.0f) );
    occluderNode->setOccluder( cpo.get() );

  5. We create a big geometry plane as the occlusion representation. To render its shape in the scene along with the massive occludees, we have to add the geometry that is created by osg::createTexturedQuadGeometry() as the occluder node's child:

    osg::ref_ptr<osg::Geode> occluderGeode = new osg::Geode;
    occluderGeode->addDrawable( osg::createTexturedQuadGeometry(
    osg::Vec3(-120.0f, 0.0f,-120.0f),
    osg::Vec3(240.0f, 0.0f, 0.0f),
    osg::Vec3(0.0f, 0.0f, 240.0f))
    );
    occluderNode->addChild( occluderGeode.get() );

  6. When constructing the scene graph, the group node of the 100,000 objects and the occluder must be siblings under the same root node. We will also turn off lighting here in order to focus on observing if there are any efficiency improvements using occluders. After that, the osgViewer::StatsHandler is used to obtain scene statistics, and we can start the viewer:

    osg::ref_ptr<osg::Group> root = new osg::Group;
    root->addChild( createMassiveQuads(100000) );
    root->addChild( occluderNode.get() );
    root->getOrCreateStateSet()->setMode(
    GL_LIGHTING, osg::StateAttribute::OFF );
    osgViewer::Viewer viewer;
    viewer.addEventHandler( new osgViewer::StatsHandler );
    viewer.setSceneData( root.get() );
    return viewer.run();

  7. Press the S key here to see the detailed frame rate. Drag the main camera with your left mouse button pressed to rotate the whole world in your eyes. The plane occluder can always cull away small quads that are totally hidden behind it, when looking from the current view point:

    OpenSceneGraph

  8. You may soon find that the maximum frame rate happens when the plane is completely in front of the eye point (as shown in the previous image), and the minimum one happens when the plane covers none of the massive number of quads (as shown in the following image):

    OpenSceneGraph

What just happened?

Somebody may think of implementing an algorithm that computes the occlusions between each of the two objects in the scene. Any object that may be hidden by one or more objects will thus be picked up and removed from the current drawing list. In this ideal situation, we don't need the concepts of occluders or occludes any more.

Unfortunately, this imagination is nearly impossible at present. An algorithmic approach to avoid inefficiency may cost too much in terms of speed. The previous example shows the time-cost of the culling work. The developer is working on a graphics system with the vertical synchronization (V-sync) at 60 Hz. The maximum frame rate should therefore be equal or less than 60 fps (frames per second). But here the maximum frame rate (when the occluder plane hides all other quads totally) is only about 40 fps, because of the dissipation of comparing the plane with 100,000 objects. Thus, an actual ideal efficient occlusion culling algorithm must perform simple enough tests, a limited number of times.

Have a go hero – adding holes to the occluder

The osg::ConvexPlanarOccluder class, which is the occlusion representation of osg::OccluderNode, accepts the osg::ConvexPlanarPolygon variable as the clipping component (via the setOccluder() method). Besides, it also accepts a number of holes to increase the complexity of the representation. Use the addHole() method with an osg::ConvexPlanarPolygon parameter to configure the osg::ConvexPlanarOccluder object, and don't forget to update the corresponding geometry representation, which is often placed as the child of the occlude node.

Summary

In this article we specifically covered:

  • Making use of the OpenThreads library to develop multithreaded programs
  • How to understand and choose different threading models in OSG, including the single-threaded, thread-per-graphics-context, and thread-per-camera models
  • A basic occlusion culling implementation with the osg::OccluderNode class

Further resources on this subject:


About the Author :


Rui Wang

Rui Wang is a Software Engineer at Beijing Crystal Digital Technology Co., Ltd. (Crystal CG), in charge of the new media interactive application design and development. He wrote a Chinese book called OpenSceneGraph Design and Implementation in 2009. He also wrote the book OpenSceneGraph 3.0 Beginner's Guide in 2010 and OpenSceneGraph 3.0 Cookbook in 2012, both of which are published by Packt Publishing and co-authored by Xuelei Qian. In his spare time he also writes novels and is a guitar lover.

Xuelei Qian

Xuelei Qian received his Ph.D. in applied graphic computing from the University of Derby in 2005. From 2006 to 2008 he worked as a post-doctoral research fellow in the Dept. of Precision Instrument and Mechanology at Tsinghua University. In 2008 he was appointed by the School of Scientific Research and Development of Tsinghua University. He is also the Deputy Director of the Overseas R&D Management Office of Tsinghua University and Deputy Secretary in General of UniversityIndustry Cooperation Committee, Tsinghua University.

Books From Packt


Inkscape 0.48 Essentials for Web Designers
Inkscape 0.48 Essentials for Web Designers

Scribus 1.3.5: Beginner's Guide
Scribus 1.3.5: Beginner's Guide

Drupal 7 First Look
Drupal 7 First Look

OGRE 3D 1.7 Beginner's Guide
OGRE 3D 1.7 Beginner's Guide

Inkscape 0.48 Illustrator's Cookbook
Inkscape 0.48 Illustrator's Cookbook

OpenAM
OpenAM

wxPython 2.8 Application Development Cookbook
wxPython 2.8 Application Development Cookbook

Learning jQuery 1.3
Learning jQuery 1.3


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software