11 min read

Creating billboards in a scene

In the 3D world, a billboard is a 2D image that is always facing a designated direction. Applications can use billboard techniques to create many kinds of special effects, such as explosions, fares, sky, clouds, and trees. In fact, any object can be treated as a billboard with itself cached as the texture, while looking from a distance. Thus, the implementation of billboards becomes one of the most popular techniques, widely used in computer games and real-time visual simulation programs.

The osg::BillBoard class is used to represent a list of billboard objects in a 3D scene. It is derived from osg::Geode, and can orient all of its children (osg::Drawable objects) to face the viewer’s viewpoint. It has an important method, setMode(), that is used to determine the rotation behavior, which must set one of the following enumerations as the argument

UsagePOINT_ROT_EYEIf all drawables are rotated about the viewer position with the object coordinate Z axis constrained to the window coordinate Y axis.POINT_ROT_WORLDIf drawables are rotated about the viewer directly from their original orientation to the current eye direction in the world space.AXIAL_ROTIf drawables are rotated about an axis specified by setAxis().

Every drawable in the osg::BillBoard node should have a pivot point position, which is specified via the overloaded addDrawable() method, for example:

billboard->addDrawable( child, osg::Vec3(1.0f, 0.0f, 0.0f) );

All drawables also need a unified initial front face orientation, which is used for computing rotation values. The initial orientation is set by the setNormal() method. And each newly-added drawable must ensure that its front face orientation is in the same direction as this normal value; otherwise the billboard results may be incorrect.

Time for action – creating banners facing you

The prerequisite for implementing billboards in OSG is to create one or more quad geometries first. These quads are then managed by the osg::BillBoard class. This forces all child drawables to automatically rotate around a specified axis, or face the viewer. These can be done by presetting a unified normal value and rotating each billboard according to the normal and current rotation axis or viewing vector.

We will create two banks of OSG banners, arranged in a V, to demonstrate the use of billboards in OSG. No matter where the viewer is and how he manipulates the scene camera, the front faces of banners are facing the viewer all the time. This feature can then be used to represent textured trees and particles in user applications.

  1. Include the necessary headers:
    #include <osg/Billboard>
    #include <osg/Texture2D>
    #include <osgDB/ReadFile>
    #include <osgViewer/Viewer>
  2. Create the quad geometry directly from the osg::createTexturedQuadGeometry() function. Every generated quad is of the same size and origin point, and uses the same image file. Note that the osg256.png file can be found in the data directory of your OSG installation path, but it requires the osgdb_png plugin for reading image data.

    osg::Geometry* createQuad()
    {
    osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
    osg::ref_ptr<osg::Image> image =
    osgDB::readImageFile( “Images/osg256.png” );
    texture->setImage( image.get() );

    osg::ref_ptr<osg::Geometry> quad=
    osg::createTexturedQuadGeometry(
    osg::Vec3(-0.5f, 0.0f,-0.5f),
    osg::Vec3(1.0f,0.0f,0.0f),
    osg::Vec3(0.0f,0.0f,1.0f) );

    osg::StateSet* ss = quad->getOrCreateStateSet()
    ss->setTextureAttributeAndModes( 0, texture.get() );
    return quad.release();
    }

    
    
  3. In the main entry, we first create the billboard node and set the mode to POINT_ROT_EYE. That is, the drawable will rotate to face the viewer and keep its Z axis upright in the rendering window. The default normal setting of the osg::BillBoard class is the negative Y axis, so rotating it to the viewing vector will show the quads on the XOZ plane in the best appearance:

    osg::ref_ptr<osg::Billboard> geode = new osg::Billboard;
    geode->setMode( osg::Billboard::POINT_ROT_EYE );

    
    
  4. Now let’s create the banner quads and arrange them in a V formation:

    osg::Geometry* quad = createQuad();
    for ( unsigned int i=0; i<10; ++i )
    {
    float id = (float)i;
    geode->addDrawable( quad, osg::Vec3(-2.5f+0.2f*id, id, 0.0f)
    );
    geode->addDrawable( quad, osg::Vec3( 2.5f-0.2f*id, id, 0.0f)
    );
    }

    
    
  5. All quad textures’ backgrounds are automatically cleared because of the alpha test, which is performed internally in the osgdb_png plugin. That means we have to set correct rendering order of all the drawables to ensure that the entire process is working properly:
    osg::StateSet* ss = geode->getOrCreateStateSet();
    ss->setRenderingHint( osg::StateSet::TRANSPARENT_BIN );
  6. It’s time for us to start the viewer, as there are no important steps left to create and render billboards:

    osgViewer::Viewer viewer;
    viewer.setSceneData( geode.get() );
    return viewer.run();

    
    
  7. Try navigating in the scene graph:

    OpenSceneGraph

  8. You will find that the billboard’s children are always rotating to face the viewer, but the images’ Y directions are never changed (point to the window’s Y coordinate all along). Replace the mode POINT_ROT_EYE to POINT_ROT_WORLD and see if there is any difference:

OpenSceneGraph

What just happened?

The basic usage of billboards in OSG scene graph is shown in this example. But it is still possible to be further improved. All the banner geometries here are created with the createQuad() function, which means that the same quad and the same texture are reallocated at least 20 times! The object sharing mechanism is certainly an optimization here. Unfortunately, it is not clever enough to add the same drawable object to osg::Billboard with different positions, which could cause the node to work improperly. What we could do is to create multiple quad geometries that share the same texture object. This will highly reduce the video card’s texture memory occupancy and the rendering load.

Another possible issue is that somebody may require loaded nodes to be rendered as billboards, not only as drawables. A node can consist of different kinds of child nodes, and is much richer than a basic shape or geometry mesh. OSG also provides the osg::AutoTransform class, which automatically rotates an object’s children to be aligned with screen coordinates.

Have a go hero – planting massive trees on the ground

Billboards are widely used for simulating massive trees and plants. One or more tree pictures with transparent backgrounds are applied to quads of different sizes, and then added to the billboard node. These trees will automatically face the viewer, or to be more real, rotate about an axis as if its branches and leaves are always at the front. Now let’s try to create some simple billboard trees. We only need to prepare an image nice enough.

Creating texts

Text is one of the most important components in all kinds of virtual reality programs. It is used everywhere—for displaying stats on the screen, labeling 3D objects, logging, and debugging. Texts always have at least one font to specify the typeface and qualities, as well as other parameters, including size, alignment, layout (left-to-right or right-to-left), and resolution, to determine its display behaviors. OpenGL doesn’t directly support the loading of fonts and displaying texts in 3D space, but OSG provides full support for rendering high quality texts and configuring different text attributes, which makes it much easier to develop related applications.

The osgText library actually implements all font and text functionalities. It requires the osgdb_freetype plugin to work properly. This plugin can load and parse TrueType fonts with the help of FreeType, a famous third-party dependency. After that, it returns an osgText::Font instance, which is made up of a complete set of texture glyphs. The entire process can be described with the osgText::readFontFile() function.

The osgText::TextBase class is the pure base class of all OSG text types. It is derived from osg::Drawable, but doesn’t support display lists by default. Its subclass, osgText::Text, is used to manage fat characters in the world coordinates. Important methods includes setFont(), setPosition(), setCharacterSize(), and setText(), each of which is easy to understand and use, as shown in the following example.

Time for action – writing descriptions for the Cessna

This time we are going to display a Cessna in the 3D space and provide descriptive texts in front of the rendered scene. A heads-up display (HUD) camera can be used here, which is rendered after the main camera, and only clears the depth buffer for directly updating texts to the frame buffer. The HUD camera will then render its child nodes in a way that is always visible.

    1. Include the necessary headers:
      #include <osg/Camera>
      #include <osgDB/ReadFile>
      #include <osgText/Font>
      #include <osgText/Text>
      #include <osgViewer/Viewer>
    2. The osgText::readFontFile() function is used for reading a suitable font file, for instance, an undistorted TrueType font. The OSG data paths (specified with OSG_FILE_PATH) and the windows system path will be searched to see if the specified file exists:

      osg::ref_ptr<osgText::Font> g_font =
      osgText::readFontFile(“fonts/arial.ttf”);

      
      
    3. Create a standard HUD camera and set a 2D orthographic projection matrix for the purpose of drawing 3D texts in two dimensions. The camera should not receive any user events, and should never be affected by any parent transformations. These are guaranteed by the setAllowEventFocus() and setReferenceFrame() methods:

      setAllowEventFocus() and setReferenceFrame() methods:

      osg::Camera* createHUDCamera( double left, double right,
      double bottom, double top )
      {
      osg::ref_ptr<osg::Camera> camera = new osg::Camera;
      camera->setReferenceFrame( osg::Transform::ABSOLUTE_RF );
      camera->setClearMask( GL_DEPTH_BUFFER_BIT );
      camera->setRenderOrder( osg::Camera::POST_RENDER );
      camera->setAllowEventFocus( false );
      camera->setProjectionMatrix(
      osg::Matrix::ortho2D(left, right, bottom, top) );
      return camera.release();
      }

      
      
    4. The text is created by a separate global function, too. It defines a font object describing every character’s glyph, as well as the size and position parameters in the world space, and the content of the text. In the HUD text implementation, texts should always align with the XOY plane:

      osgText::Text* createText( const osg::Vec3& pos,
      const std::string& content,
      float size )
      {
      osg::ref_ptr<osgText::Text> text = new osgText::Text;
      text->setFont( g_font.get() );
      text->setCharacterSize( size );
      text->setAxisAlignment( osgText::TextBase::XY_PLANE );
      text->setPosition( pos );
      text->setText( content );
      return text.release();
      }

      
      
    5. In the main entry, we create a new osg::Geode node and add multiple text objects to it. These introduce the leading features of a Cessna. Of course, you can add your own explanations about this type of monoplane by using additional osgText::Text drawables:

      osg::ref_ptr<osg::Geode> textGeode = new osg::Geode;
      textGeode->addDrawable( createText(
      osg::Vec3(150.0f, 500.0f, 0.0f),
      “The Cessna monoplane”,
      20.0f)
      );
      textGeode->addDrawable( createText(
      osg::Vec3(150.0f, 450.0f, 0.0f),
      “Six-seat, low-wing and twin-engined”,
      15.0f)
      );

      
      
    6. The node including all texts should be added to the HUD camera. To ensure that the texts won’t be affected by OpenGL normals and lights (they are textured geometries, after all), we have to disable lighting for the camera node:

      osg::Camera* camera = createHUDCamera(0, 1024, 0, 768);
      camera->addChild( textGeode.get() );
      camera->getOrCreateStateSet()->setMode(
      GL_LIGHTING, osg::StateAttribute::OFF );

      
      
    7. The last step is to add the Cessna model and the camera to the scene graph, and start the viewer as usual:

      osg::ref_ptr<osg::Group> root = new osg::Group;
      root->addChild( osgDB::readNodeFile(“cessna.osg”) );
      root->addChild( camera );

      osgViewer::Viewer viewer;
      viewer.setSceneData( root.get() );
      return viewer.run();

      
      

In the rendering window, you will see two lines of text over the Cessna model. No matter how you translate, rotate, or scale on the view matrix, the HUD texts will never be covered. Thus, users can always read the most important information directly, without looking away from their usual perspectives:

OpenSceneGraph

What just happened?

To build the example code with CMake or other native compilers, you should add the osgText library as dependence, and include the osgParticle, osgShadow, and osgFX libraries.

Here we specify the font from the arial.ttf file. This is a default font in most Windows and UNIX systems, and can also be found in OSG data paths. As you can see, this kind of font offers developers highly-precise displayed characters, regardless of font size settings. This is because the outlines of TrueType fonts are made of mathematical line segments and Bezier curves, which means they are not vector fonts. Bitmap (raster) fonts don’t have such features and may sometimes look ugly when resized. Disable setFont() here, to force osgText to use a default 12×12 bitmap font. Can you figure out the difference between these two fonts?

Have a go hero – using wide characters to support more languages

The setText() method of osgText::Text accepts std::string variables directly. Meanwhile, it also accepts wide characters as the input argument. For example:

wchar_t* wstr = …;
text->setText( wstr );

This makes it possible to support multi-languages, for instance, Chinese and Japanese characters. Now, try obtaining a sequence of wide characters either by defining them directly or converting from multi-byte characters, and apply them to the osgText::Text object, to see if the language that you are interested in can be rendered. Please note that the font should also be changed to support the corresponding language.

LEAVE A REPLY

Please enter your comment!
Please enter your name here