Introduction to OpenSceneGraph

OpenSceneGraph is an open source cross-platform library for developing high-performance 3D applications. This is not a game engine that binds the user hand and foot with the restrictions laid down in it, namely the library - a set of useful modules that work great both individually and in assembly.



The core of OpenSceneGraph, the actual scene graph, is a rather thin wrapper around OpenGL, which allows you to define a hierarchy of objects and perform any desired transformations on them:
  • change the characteristics of nodes (move objects in space, assign materials, lighting properties, shaders, and other OpenGL modes and attributes to them);
  • rebuild the tree in any way you want (create and delete objects, link them to other nodes in the graph);
  • do a graph traversal, performing any actions for each of their nodes;
  • and of course render the scene using OpenGL.


Scene graph in OpenSceneGraph


Each node of the scene graph is an instance of some of the descendants of the Node class. Arrows are a parent-child relationship:



Nodes that have children are called groups. An ordinary Group does nothing with its children - they will all be rendered as they are. However, Group heirs may have additional behavior. For example, MatrixTransform inherits the Group class, and allows you to apply the transformation matrix to all children at once. For example, if you change the matrix responsible for the tank turret, the turret will rotate with the barrel:



OpenSceneGraph's drawing primitives are called Drawables. Each Drawable corresponds to some OpenGL drawing primitive: sphere, cube, arbitrary mesh, OpenGL teapot, etc. Drawables themselves get to the scene only in the Geode container (short for "geometry node"). A geode can contain any number of Drawables:



A very important property of OpenSceneGraph is that any node can have multiple children. This is important in order not to duplicate the same objects and not waste the memory of the computer and video adapter for storing duplicate fragments. For example, the tank has several wheels and several tracks, but since they are the same, you can store them in one copy, and so that they are located in different places of the tank, we will set their position by individual MatrixTransforms:



If we want to make a full-fledged model of a tank that can twist wheels and tracks, rotate the turret and control the barrel, we get a graph like this:



To twist the wheels, just change the texture mapping on the left or right wheels. And the user will see that the corresponding wheels spin all at once. Similarly with caterpillars - a change in texture, you can achieve a visible motion effect.

Two words about memory management


The scene graph can have a very complex structure, and in order to simplify memory management, OpenSceneGraph uses a garbage collector with a reference counter. Each class inheriting osg :: Referenced receives its own reference counter, which is automatically incremented and decremented using the osg :: ref_ptr smart pointer system. Here is a simple example:

{
    osg::ref_ptr geode = new osg::Geode;
}

In this example, a new instance of osg :: Geode is created, the smart pointer is initialized, then the pointer is destroyed, and with it osg :: Geode, since there are no more links to it. When adding children to a group, Drawables in Geode and all other cross-references between graph objects, smart pointers are used. This ensures that when you delete the link to the root node of the graph, all objects will be correctly destroyed.

Hello World


Here is a minimal OpenSceneGraph application:

#include 
int main()
{
    osgViewer::Viewer viewer;
    return viewer.run();
}

It uses the osgViewer module, which takes care of opening a graphic window, initializing OpenGL, creating a default camera, initializing the Escape key handler and mouse controller so that you can move the camera with it. When the program starts, we will see an empty scene, which will be closed by Escape.
The next step is to create a root node. For example, we put the sphere creation code in front of viewer.run ():

    // Создание Drawable
    osg::Sphere *shape = new osg::Sphere(
            osg::Vec3(0.0f, 0.0f, 0.0f), 1.0f);
    osg::ShapeDrawable *drawable = new osg::ShapeDrawable(shape);
    // Создание Geode
    osg::Geode *geode = new osg::Geode;
    geode->addDrawable(drawable);
    // Регистрация корневого узла сцены
    viewer.setSceneData(geode);

After starting this application, we will see the scope:



Now we can proceed to downloading the font and displaying the text. We need the osgText library, which is responsible for working with text:

    osgText::Font *font = osgText::readFontFile(
            "/usr/share/fonts/truetype/msttcorefonts/arial.ttf");
    osgText::Text *text = new osgText::Text;
    text->setFont(font);
    text->setAxisAlignment(osgText::Text::XZ_PLANE);
    text->setText("Привет, хабр!", osgText::String::ENCODING_UTF8);
    osg::Geode *geode = new osg::Geode;
    geode->addDrawable(text);

The readFontFile function loads the font from the file, then we create the Text object, which is the inheritor of Drawable. This means that it can be added to the Geode using the addDrawable method.
After starting the program, the text will appear:



Application main loop


Most graphic applications must constantly update the image on the screen: create and delete new objects, move them, change their properties and render frame by frame:



Changing the scene is the operation that the application does. Until the application finishes its updates, it is impossible to start rendering the next frame - otherwise, in the process of traversing the tree by the rendering system, the graph may be in an inconsistent state, and the application will damage its memory or simply crash. This means that the faster the updates are completed, the greater the FPS will be on output.
When creating real applications, it makes sense to perform heavy calculations at the same time as the rendering phase in another thread. There you can create new scene objects. And when the main cycle reaches the scene change phase, it will be possible to quickly apply the calculation results to the scene objects, link the created subtrees to the scene graph. Similarly, with the removal of a large number of objects. During the scene change phase, you can simply unlink them from the tree and place them in the deletion queue, and actually destroy the objects in another thread.

How to change a scene


For example, let's make the inscription "Hello, Habr" spinning on the screen. To do this, we first wrap the Geode in a MatrixTransform:

    osg::MatrixTransform *mat = new osg::MatrixTransform;
    mat->addChild(geode);
    viewer.setSceneData(mat);

Then ask Viewer to register our event handler:

    RotationHandler *handler = new RotationHandler(mat);
    viewer.addEventHandler(handler);

Each event handler is an object that inherits the osgGA :: GUIEventHandler class. We are now interested in how to handle the FRAME event, which is called before each frame:

class RotationHandler: public osgGA::GUIEventHandler {
public:
    RotationHandler(osg::MatrixTransform *mat):
        m_mat(mat)
    {
    }
    virtual bool handle(const osgGA::GUIEventAdapter& ea,
            osgGA::GUIActionAdapter &adapter)
    {
        osg::Matrix mat;
        switch (ea.getEventType()) {
        case osgGA::GUIEventAdapter::FRAME:
            mat.makeRotate(ea.getTime(), osg::Vec3(0.0f, 0.0f, 1.0f));
            m_mat->setMatrix(mat);
        }
    }
private:
    osg::ref_ptr m_mat;
};

When the program starts, the text will begin to rotate around the axis (0, 0, 1).

What other features does OpenSceneGraph have


We examined the basic principles of constructing a scene, leaving behind the scenes a graph traversal, assigning attributes to nodes (materials, lighting), camera control, mouse and keyboard processing, and much more. Just to mention some interesting features:
  • the implementation of the Visitor pattern allows you to write your own class and ask OpenSceneGraph to bypass the graph, calling your code for each suitable scene object;
  • the Switch node (which is the heir to the Group) allows you to turn children on and off, excluding them from going around the graph;
  • the LOD node (also the heir of the Group) allows you to specify at what distance from the camera which of the children should be rendered. Allows you to use simpler models at a great distance from the camera;
  • shader support (fragment and geometry);
  • To achieve high performance and scalability there is support for multi-threaded rendering of images from multiple cameras. Including multi-GPU;
  • rendering to texture is possible;
  • multilayer textures, anisotropic lighting, bump-mapping, specular highlights;
  • selecting scene objects into which the user points with the mouse (more precisely, converting screen coordinates into a long and sharp polyhedron that sticks into the screen, and then searching for intersections of this polyhedron with scene objects and sorting the found objects by distance from the camera);
  • many mathematical primitives for working with matrices, quaternions, polyhedra (calculation of intersections, unions, etc.), 3d-morphing;
  • scene objects can be serialized and deserialized. New export and import formats are easily connected using a plug-in system. The native format (osg) saves all the attributes of objects in a human-readable text format and allows you to accurately restore the graph after deserialization;
  • special nodes of the HUD graph (head up display) allow you to expand the children so that they are always oriented facing the monitor;
    Particle systems allow you to create various special effects such as fire, smoke, sparks, programming the frequency of each particle, its flight path, life time, texture, etc .;
  • support for translucent objects and shadows;
  • OpenThreads multithreading library allows you to abstract from the operating system and write a single code for all platforms;
  • The osgDB module, in addition to simply loading and unloading objects to disk, has in its arsenal an object database module that allows them to be loaded in the background stream (paging mode);
  • Integration with Qt allows you to render the GUI into a texture, which, in turn, can be stretched to some object in the scene. In particular, you can take the Qt component of a web browser, place it on a texture, display it on a stage, open YouTube in a browser and watch some video. And it works;
  • there is support for visualizing the surface of the earth (terrain) and sky (skybox);
  • the ability to work on mobile platforms Android and iOS;
  • excellent license (relaxed LGPL), which even allows you to statically link the library with closed projects.

Where to get


Official site - www.openscenegraph.org The
best documentation - books from the authors .
The best documentation available for free is the sheer number of examples that come with the library, and great code that is easy and pleasant to read.

Also popular now: