Chapter Overview
Previous Chapter: Introduction
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_folder/Doc/tutorial/progs of your copy of OpenSG (CVS version only!). The filenames are
given at the beginning of the appropriate tutorials.
#!cpp
// 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::
OSG_USING_NAMESPACE
//The SimpleSceneManager is a useful class which helps us to
//manage simple configurations. It will be discussed in detail later on
SimpleSceneManager *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
osgInit(argc,argv);
// We create a GLUT Window (that is almost the same for most applications)
int winid = setupGLUT(&argc, argv);
GLUTWindowPtr gwin= GLUTWindow::create();
gwin->setId(winid);
gwin->init();
// This will be our whole scene for now : an incredible torus
NodePtr scene = makeTorus(.5, 2, 16, 16);
// Create and setup our little friend - the SSM
mgr = new SimpleSceneManager;
mgr->setWindow(gwin);
mgr->setRoot(scene);
mgr->showAll();
// Give Control to the GLUT Main Loop
glutMainLoop();
return 0;
}
// react to size changes
void reshape(int w, int h)
{
mgr->resize(w, h);
glutPostRedisplay();
}
// just redraw our scene if this GLUT callback is invoked
void display(void)
{
mgr->redraw();
}
//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
glutDisplayFunc(display);
glutReshapeFunc(reshape);
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
#!cpp glutMotionFunc(motion); glutMouseFunc(mouse);
and add this whole function anywhere before the declaration of the setupGLUT() function
#!cpp
// react to mouse motions with pressed buttons
void motion(int x, int y)
{
mgr->mouseMove(x, y);
glutPostRedisplay();
}
// react to mouse button presses
void mouse(int button, int state, int x, int y)
{
if (state)
mgr->mouseButtonRelease(button, x, y);
else
mgr->mouseButtonPress(button, x, y);
glutPostRedisplay();
}
The complete file of our first little programm can also be found in the progs folder as
../../progs/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 use 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 osg-config executable here # i.e. if you installed in /usr/local it is OSGCONFIG := /usr/local/bin/osg-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 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!
GLUT
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
#!cpp
int setupGLUT(int *argc, char *argv[])
{
glutInit(argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE);
int winid = glutCreateWindow("OpenSG First Application");
glutDisplayFunc(display);
glutReshapeFunc(reshape);
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
#!cpp glutDisplayFunc(display);
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
#!cpp
void display(void){
mgr->redraw();
}
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 that all 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
#!cpp #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
#!cpp //call the constructor with no arguments mgr = new SimpleSceneManager; // set the rendering window (look at 01firstapp.cpp on how to create the window) mgr->setWindow(gwin); // set the root of the scenegraph mgr->setRoot(scene); // makes the whole scene visible mgr->showAll();
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. Wether 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!
Attachments
- car_graph.png (27.4 kB) - added by cneumann on 03/28/07 08:10:48.
- car_graph_general.png (25.1 kB) - added by cneumann on 03/28/07 08:11:06.


