close Warning:

<< Previous Chapter: Introduction Tutorial Overview Next Chapter: Basics >>

First Application

I personally like doing things and watch what happens, before I actually understand what I am doing. This is why I want to begin with a little OpenSG application right away. This will give you a basic feeling of how things work in OpenSG and will already show you some curiosities found in OpenSG...

First Tutorial

The following code will create a wonderful torus, which you will see again in further tutorials. Every palace is build upon a first stone, and this torus will be our first step in handling OpenSG! You can copy and paste this code or you can use the provided file. You will find all files related to this online tutorial in your_opensg_dir/Examples/Tutorial of your copy of OpenSG. The filenames aregiven at the beginning of the appropriate tutorials.

// File : 01firstapp.cpp

//what follows here is the smallest OpenSG programm possible
//most things used here are explained now or on the next few pages, so don't 
//worry if not everything is clear right at the beginning...

//Some needed include files - these will become more, believe me ;)
#include <OpenSG/OSGConfig.h>
#include <OpenSG/OSGGLUT.h>
#include <OpenSG/OSGSimpleGeometry.h>
#include <OpenSG/OSGGLUTWindow.h>
#include <OpenSG/OSGSimpleSceneManager.h>

//In most cases it is useful to add this line, otherwise every OpenSG command
//must be preceeded by an extra OSG::

//The SimpleSceneManager is a useful class which helps us to 
//manage simple configurations. It will be discussed in detail later on
SimpleSceneManagerRefPtr mgr;

//we have a forward declaration here, just to have a better order 
//of codepieces
int setupGLUT( int *argc, char *argv[] );

int main(int argc, char **argv)
    // Init the OpenSG subsystem
    // We open a new scope here to make sure the pointers inside it go out of
    // scope before entering glutMainLoop.
    // This is necessary since in general glutMainLoop does not return and
    // therefore these pointers would never destroyed otherwise.
        // We create a GLUT Window (that is almost the same for most applications)
        int winid = setupGLUT(&argc, argv);
        GLUTWindowRecPtr gwin = GLUTWindow::create();
        // This will be our whole scene for now : an incredible torus
        NodeRecPtr scene = makeTorus(.5, 2, 16, 16);
        // Create and setup our little friend - the SSM
        mgr = SimpleSceneManager::create();
    // Give Control to the GLUT Main Loop
    return 0;

// react to size changes
void reshape(int w, int h)
    mgr->resize(w, h);

// just redraw our scene if this GLUT callback is invoked
void display(void)

void keyboard(unsigned char key, int /* x */, int /* y */)
    case 'q':
    case 'Q':
    case 27:
        mgr = NULL;

// The GLUT subsystem is set up here. This is very similar to other GLUT 
// applications. If you have worked with GLUT before, you may have the
// feeling of meeting old friends again, if you have not used GLUT before 
// that is no problem. GLUT will be introduced briefly on the next section

int setupGLUT(int *argc, char *argv[])
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    int winid = glutCreateWindow("OpenSG First Application");
    // register the GLUT callback functions

    return winid;

That is all to get a little nice window, displaying a torus. I admit that there are OpenGL programs out there that are shorter and do the same - but that will soon change, as our tutorials will get more advanced.

The first thing that is boring is the fact that we can not interact with our newly created virtual world. It would be wonderful to have some mouse input, so that we can navigate within it. That will be our first task: Open (if you have closed it) the file you just created and jump to the setupGLUT() function. Just below the other two callback functions add these two lines


and add this whole function anywhere before the declaration of the setupGLUT() function

// react to mouse motions with pressed buttons
void motion(int x, int y)
    mgr->mouseMove(x, y);

// react to mouse button presses
void mouse(int button, int state, int x, int y)
    if (state)
        mgr->mouseButtonRelease(button, x, y);
        mgr->mouseButtonPress(button, x, y);

The complete file for our first little programm can also be found in Examples/Tutorial/01firstapp2.cpp.

A Makefile

So far so good - but still we need to compile that file we just created. Windows users should paste the code simply in one of the existing tutorial files where as Unix users should create a little makefile. Because the last part is a bit tricky, I present a makefile which is ready to use and which can easily be taken as a starting point for own better suited makefiles

# File : Makefile

# Very general makefile for compiling OpenSG Programs
# This makefile will compile every programm in this folder 
# with a filename like this 01******.cpp

# "opt" if you use the optimized library otherwise it is "dbg"
LIBTYPE ?= dbg

# set the path to the installed osg2-config executable here
# i.e. if you installed in /usr/local it is
OSGCONFIG := /usr/local/bin/osg2-config

# use osg-config to set the options needs to compile and link
CC = "$(shell $(OSGCONFIG) --compiler)"
CCFLAGS = $(shell $(OSGCONFIG) --cflags --$(LIBTYPE) Base System GLUT)
LDFLAGS = $(shell $(OSGCONFIG) --libs --$(LIBTYPE) Base System GLUT)

# all tutorials in this directory

TUTS :=  $(wildcard [0-9][0-9]*.cpp) 
PROGS := $(TUTS:.cpp=) 

# program dependencies

default:    $(PROGS)

# make rules

# by the way, do not let it bother you, if you do not understand that
# "makefile-magic". You can write great programs without this knowledge,
# I know that ;)

.PHONY: clean Clean

    rm -f  *.o 

Clean: clean
    rm -f $(PROGS) 

%.o: %.cpp
    $(CC) -c $(CCFLAGS) $<

%: %.o
    $(CC) -o $@ $< $(LDFLAGS)

%: %.cpp
    $(CC) $(CCFLAGS) $< $(LDFLAGS) -o $@ 

I often wonder why makefiles work, because often they are hard to read. If you are not very familiar with makefiles, like I am, then let me tell you that it doesn't matter if you don't understand how this file works. Just believe that it works! ;-)


If you are familiar with GLUT, you can skip to the next section right away, otherwise you may find it useful, even though GLUT is not a must as OpenSG applications can also be driven by other window systems like QT or MFC, or anything that has an OpenGL window.

So what is GLUT? GLUT stands for GL Utility Library - with GLUT you can easily create a window in which OpenGL will render. Furthermore you can access the keyboard and mouse with just a few lines of code. That is what OpenSG uses GLUT for. Of course it is not as powerful as QT, but it is much simpler to handle.

GLUT is always initialized at the beginning, by telling the library the size of the window, the color depth and similar things. Afterwards the callbacks are registered. Let us have a closer look at that

int setupGLUT(int *argc, char *argv[])
    glutInit(argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
    int winid = glutCreateWindow("OpenSG First Application");    

    return winid;

glutInit() does some magic initialization stuff which should not concern us right here. The parameters given to glutInitDisplayMode() are telling GLUT to use the RGB Color Mode and that it should activate the depth buffer of OpenGL, whereas GLUT_DOUBLE tells the library to use a double buffered window mode which will result in smoother graphics. If you do not specify the GLUT_DEPTH constant you will have no depth testing in your OpenSG application, what is most likely not what you want.

And what about those callbacks? If you have a look at the main() function of our first application you will notice that the last command (before the return) is the glutMainLoop(). Calling this will give control of your application to GLUT. That is if you have not registered a single callback you will never get control back... even worse you won't be able to see anything on the screen except a black window. So what does this main loop actually do? Without going into too much unecessary detail, it simply checks if any event occurs that a callback is registered for and invokes it in that case. There is at least one callback you should always register (and of course implement) and that is the display function. This function will always be called if the window needs to be redrawn. That is either the case if the operating system says that this window needs redrawing or if you explicitly tell the system (via glutPostRedisplay() for example).

Well, we registered the display callback like this


so we need a method called display that actually implements what needs to be done when it comes to drawing the window. Let's have a look what we have done

void display(void)

That is not really much, isn't it? Normally a lot of stuff is done in here, because that is what we will see on screen in the end. If you would write a usual OpenGL application with GLUT but without OpenSG you would place all your drawing related OpenGL commands here - and that is usually quite a lot!

Fortunately we do use OpenSG, so in most cases we do not need to hack OpenGL commands directly. OpenSG does the dirty work for us ;). The next section is about what happens when mgr->redraw() is called.

A last word about all the other callbacks: The procedure is always the same, you register the desired callback somewhere at the beginning and you implement the function you passed as the argument. This function will be called if the corresponding event occurs, e.g. the keyboard callback is invoked when a key is pressed, the mouse callback when the mouse button is pressed and so on.

Simple Scene Manager

The simple scene manager is a useful helper class for quickly creating simple window configurations and interact with them. If you do not want to use the simple scene manager (SSM) you would have quite a lot of work to do (if you are already comfortable with OpenSG you can have a look at \ref TutorialWindowsTutorial? to see how you can setup a scene witout the SSM). You need to create a window, add a viewport to that window, create one or more lights and a camera, set up a navigation and you have to bind all that together. Sometimes that has to be done as the manager is useable for simple scenes only (as the name already says), but that will be discussed in detail later. For now we are happy with the SSM as it does all of the tasks described above for us.

Sometimes using the SSM is not a good idea. If you are devolping a stereo application for example you would have to change so much that it is a better idea to write your own code from the bottom up. As a rule of thumb you can and should use the SSM if and only if you want to have a window with one viewport and a perspective camera. If you want to change the navigation mode or the light sources you can easily do that.

How to use the Simple Scene Manager

In most cases you need a global instance of the SSM. So like in the First Tutorial we define a global variable "mgr" as a pointer. Of course we need to include the header file for the SSM

#include <OpenSG/OSGSimpleSceneManager.h>

// ....

SimpleSceneManager *mgr;

The next thing we need to do, is to create an instance of the SSM and specify the two most important things: the window which will be used for the rendering output and the root node of the scenegraph

//call the constructor with no arguments
mgr = new SimpleSceneManager;
// set the rendering window (look at 01firstapp.cpp on how to create the window)
// set the root of the scenegraph
// makes the whole scene visible

The last command ensures the correct position of the camera. The camera is positioned so that the whole scene will be visible. Of course this is not always optimal, because OpenSG cannot know where the most beautiful spot in your scene is, but it effectively prevents one of the most common problems in OpenGL programs: "So I'm done with the program, but why is everything black?".

Maybe you have already some knowledge of OpenSG and want to know what the simple scene manager actually creates for you. So here it is: A simple single fullscreen Viewport (OSG::Viewport), a perspective Camera (OSG::PerspectiveCamera), a render action needed for rendering traversals of the graph (OSG::RenderAction) and a simple directional light (OSG::DirectionalLight) attached to the camera, i.e. functioning as a headlight, are created for you. In addition to that a simple Navigator (OSG::Navigator) is created which allows you to rotate with the left, to pan with the right and to zoom with the middle mouse button, except for me (Mac user with a one button mouse...).

The Scenegraph

I guess the probability that you already know what a scenegraph is is very high, because you are reading these lines right now. So it would be worth a whole book to discuss scenegraphs in every aspect and detail. That cannot be achieved here and it also won't be necessary, so I make it short.

The basic idea of a scene graph is just that: a graph (i.e. a group of nodes connected to each other) that represents the whole scene you want to display. In the next parts of this tutorial we will talk about the different kinds of nodes that you put in the graph and what they do.

To get a feeling for the power that lies within scenegraph systems we should look at recent computer games. Whether the 300+ first person shooters found at a time in stores are educationally valuable for kids or not is another question, but one thing is for sure: the high demand for these games has pushed the development of high performance graphics cards and other technologies. They show us in a very illustrative way what is possible in computer graphics. And they all rely on a scenegraph one way or another.

A Scenegraph holds a scene efficiently in memory and tries to sort the big polygon soup so that it can be passed to the hardware in the most efficient way. If you have some basic knowledge of OpenGL you will know that the glBegin(...) and glEnd() command is very expensive. Imagine you have one hundred red and one hundred blue triangles and you draw them all in a loop, first a red one, then a blue, and a red and so on. If programmed that bad, it will result in 200 glBegin/End blocks and 200 color state changes. If programmed well, it only requires on begin/end block and two color state changes. A good scenegraph will sort the triangles in the latter way no matter what the input was. Of course this is a very synthetic example, but real life will show you, that big scenes coded by hand are never optimal and with models exported from another software it even gets worse.

Another very important aspect of scenegraphs is, that they have knowledge of the entire world. So you can check if parts of your scene are not visible. There is no need to render things that are behind the camera. The scenegraph detects parts that are not inside the viewing frustum and do not render them. In this way a lot of processing time is saved.

OpenSG Scenegraph Compared to Other Systems

Most scenegraphs have a similar data structure. Nodes are used to build a graph and these store information about the functionality. Nodes may carry information about transformations, materials, geometry and a lot of other stuff. One of the biggest differences is, whether the scenegraph is a multiparent or single parent one. Imagine the simplified model of a car consisting of a body and four wheels. The geometry of every wheel is identical, so you do not need four wheels in memory. If you have a single parent scenegraph, there is no way, you need to have four copies of the wheel. A multi parent system only needs four transformations relative to the body of the car and one wheel which is applied to each transformation and thus a complete car will be rendered. All current scenegraphs support multiple parents one way or another.

"This graph shows the concept how scenegraphs work"

In OpenSG nodes are used only to define the hierarchy of the graph. They store their children (if any) and a core. The core is the place where all the important information is stored, i.e. geometry, transformations etc. It is very important to know, that every node can only have one parent, but of course as many children as necessary. If for some reason a new parent is assigned to a node, the former parent will be replaced by the new one (actually not the node itself, but the link to it). The cores themselves can be referenced by as many nodes as desired, and this is how OpenSG can share data efficiently, even though it formally is only single-parent.

"A simple graph demonstrating how node and cores are used in OpenSG"

The yellow circles represent nodes while the orange rectangles are cores. As you can see every node has one parent, but the wheel geometry core has four. There are four different transform cores and every one of them stores a unique transform matrix.

If you compare the last two pictures it seems that the concept of how OpenSG works is much more difficult. You will see soon, that actually the separtion of core and node is very easy to handle. It may produce some additional lines of code though, but later on you will also see what the big picture of that concept is.

No exercises in this chapter!

<< Previous Chapter: Introduction Tutorial Overview Next Chapter: Basics >>
Last modified 4 years ago Last modified on 10/03/12 06:43:39