Chapter Overview
| >Windows >Viewports >Cameras >Backgrounds >Foregrounds >Tutorial - Without Simple Scene Manager >Exercises |
This chapter is all about windows and viewports. What a window is should be self explanatory, as of course, OpenSG needs a window which it will render into. Viewports are rectangular areas within the window. Applications can have an arbitrary number of Windows, each of which can have one or more viewports. All applications you have seen so far, had one window with one viewport, which were provided by the simple scene manager, but that is by means a limitation of the system.
I will also introduce foregrounds and backgrounds, as these are closely related to windows and viewports. Foregrounds can be used to display logos or they can even be used to capture a screenshot amongst other things, whereas backgrounds do exactly what you expect them to do: clear the background in different ways.
int main(int argc, char **argv) { osgInit(argc,argv); int winid = setupGLUT(&argc, argv); GLUTWindowPtr gwin= GLUTWindow::create(); gwin->setId(winid); gwin->init(); scene =createScenegraph(); // ....
Of course the GLUT Window is not the only one. Here is a list of all available windows
The first four windows are providing a connection between OpenSG and the windowing system you are using. GLUT and QT windows actually do have some advantages compared to the others, because both work on many different platforms, where as X Windows are restricted to UNIX boxes and Win32 windows to the Microsoft platform. Additionally GLUT and QT windows might be a bit easier to use as there are wrapper classes that may help you out. We have used GLUT windows all the way, so you should be familiar with that. For QT you can have a look at Appendix B - OpenSG with QT
So what does a window actually do? First of all, it is responsible for output of the rendered images. It does no handling of input events or similar things. A window keeps its instances of viewports and manages OpenGL context creation and activation/deactivation. It also handles detection of OpenGL extensions.
The passive window is a bit different from the others. A passive window does not handle an OpenGL context. It's purpose is to fit seamlessly into other OpenGL programs. Passive windows can be used with any window and GUI system that supports OpenGL. See section Passive Window with QT / Plain OpenGL Code for an example using passive windows.
The last three windows, cluster, multi display and sort first windows are used for clustering. These will be discussed in more detail in chapter Clustering.
At the end of this chapter there will be a tutorial where we will set up an application from scratch, without the simple scene manager. This tutorial may explain the usage of windows and viewports better than words can tell.
Typical use of four viewports
The size of a viewport is definied by providing values for left, right, top and bottom borders. These can either be between 0 and 1 (inclusively) or greater than 1. The first variant means the size of the viewport is relative to the window size whereas values above 1 specify the size in pixels. You should decide which variant to use depending on whether the viewports shall scale together with the window if it is resized or not. Here is an example showing a window with two viewports, one defined relativly and the other absolute.
Resizing window with two different viewports
The yellow viewport is defined relative to the window, whereas the red viewport's values are given absolutly in pixels. When the window is resized the yellow viewports changes it's size too, but the red one doesn't change shape or position at all.
You can also pass a -1 to one or more parameters which means that the most possible space is taken by the viewport. An exception to these sizing rules is the passiveViewport which simply ignores all given values, as they are taken from the currently active OpenGL context.
In order to use a viewport correctly you not only need to define its size, but some additional things are needed: A node of the scenegraph (usually the root node) where rendering traversal should begein, a camera and a background are needed. Optionally you can provide one or more foreground(s).
As you might guess there are also different types of viewports. The one that came along with the simple scene manager was a standard viewport. Here is a list of all available viewports
Well, the default Viewport is what you want in most cases. Passive viewports are needed to integrate OpenSG into existing OpenGL programs. The color buffer viewport allows rendering in specific color channels and finally the stereo buffer viewport is used mostly for active stereo applications. Stereo with shutter glasses for example need four buffers which are provided by osg::StereoBufferViewport. Passive stereo can be achived with default viewports. Again, more on that in Clustering.
Good news is, that we only need one matrix to describe the position and orientation of the camera, but bad news is that often you are given two points in space, one where the camera is located and one where it is looking at and another vector telling you where "up" is from the camera's point of view. Whenever you want to use these values you would have to calculate the matrix in order to use them with OpenSG. Well, luckily that has been done for you already: the osg::MatrixLookAt function from OSGMatrixUtility.h does that job!
OpenSG cameras are using the same coordinate system conventions like OpenGL: the camera is looking along the negative z-axis, y points up and x points right.
In addition to position and orientation there are some internal camera parameters which may differ from camera to camera. However, the near- and far clipping planes are needed by every camera.
In OpenGL you have two matrices that are responsible for transforming the world onto the rectangluar area which is your screen: GL_MODELVIEW and GL_PROJECTION matrix. OpenSG stores internally three matrices, because the projection matrix is split up into two: one for the real projection and another for a projection transformation. The last one is needed for non-coplanar multi-screen projections like a CAVE. However, we don't need to pay special attention to that, for now.
Here is a short overview of the different cameras you can use with OpenSG
Here is an example on how to setup a camera
PerspectiveCameraPtr camera = PerspectiveCamera::create();
beginEditCP(camera);
camera->setBeacon( some_node );
camera->setFov( deg2rad( 60 ) );
camera->setNear( 0.5 );
camera->setFar( 8000 );
endEditCP(camera);
Clipping Planes and Vertical Field of View
The matrix camera is a very simple one as it keeps the modelview and perspective matrices directly and ignores all other attributes it may have. Under certain circumstances this may be very useful, especially if you are integrating OpenSG into an existing OpenGL program by using a passive window as well as passive viewports. With this camera you are very close to standard OpenGL behaviour.
A big advantage of decorators in general is, that you can easily change the object which is decorated by some other and maintain the enhancements the decorators provide. In our example you could exchange a perspective camera with a matrix camera and the stereo setup won't be destroyed!
Here is an overview of the decorators currently available
OSG::ShearedStereoCameraDecorators are used for typical passive stereo applications, where two beamers are projecting the image and the audience is wearing polarising glasses. They add one additional attribute, the distance to the zero parallax plane. The zero parallax plane is a plane where the left and right image perfectly overlap, i.e. the parallax is zero. This value should reflect the distance from the user's point of view to the image plane.
The following code creates a decorator for the right eye and a corresponding vieport
cameraDecorator = ShearedStereoCameraDecorator::create();
beginEditCP(cameraDecorator);
cameraDecorator->setLeftEye(false);
cameraDecorator->setEyeSeparation(6);
cameraDecorator->setDecoratee(some_camera);
cameraDecorator->setZeroParallaxDistance(200);
rightViewport = OSG::Viewport::create();
beginEditCP(rightViewport);
rightViewport->setCamera ( cameraDecorator );
rightViewport->setBackground( some_background );
rightViewport->setRoot ( root );
rightViewport->setSize ( .5,0,1,1 );
endEditCP(rightViewport);
endEditCP(cameraDecorator);
I choose 6 for the eye separation and 200 for the distance to the image plane. This would make sense only, if one unit is equal to one centimeter. You might have to adjust these values according to your units.
OSG::ProjectionStereoCameraDecorators are mainly used for head-tracked stereo applications like a CAVE. I will not discuss this decorator here unless there is a public demand for it. ;-)
In Darmstadt, Germany there is a wall of screens, called HEye Wall, with 6 * 4 screens which is capable of stereo rendering. A 48 + 1 node cluster running OpenSG is driving the HEye Wall. Instead of using 48 cameras it is sufficient to use only one single camera and let the decorators do the rest!
The Heye Wall in Darmstadt, Germany
SolidBackgroundPtr bg = SolidBackground::create();
beginEditCP(bg, SolidBackground::ColorFieldID);
bg->setColor(Color3f(1,0,0));
endEditCP(bg, SolidBackground::ColorFieldID);
Here is a little example that will set up a gradient background, which fades from black to white.
GradientBackgroundPtr gb = GradienBackground::create();
beginEditCP(gb);
gb->addLine(Color3f(0,0,0),0.0);
gb->addLine(Color3f(1,1,1),0.0);
endEditCP(gb);
ImageBackgroundPtr imBkg = ImageBackground::create();
beginEditCP(imBkg, ImageBackground::ColorFieldMask |
ImageBackground::ImageFieldMask |
ImageBackground::ScaleFieldMask
);
imBkg->setColor(Color3f(0,0,0));
imBkg->setImage(some_image);
imBkg->setScale(false);
endEditCP(imBkg, ImageBackground::ColorFieldMask |
ImageBackground::ImageFieldMask |
ImageBackground::ScaleFieldMask
);
Please notice that the image that need to be passed to setImage() is not just some jpg file or similar. You need to pass an osg::Image object. The tutorial at the end of this chapter will show you how to use an image background.
All six images of a skybox stichted together
The usage might be a bit strange (at least it seems to me ;-) ) - you need a set of 2D points and an equal number of 3D points to define the polygon and it's position on screen. If values for the 2D points are greater than one, the starting point is the lower left corner and for values smaller then -1 are using the upper right corner as origin. This allows you to define polygons that are independent of the viewport size. The 3D pointes define the texture coordinates used by the 2D points, which allows you to use 3D textures as a Foreground, or to pass 3 parameters to the Material used to render the polygon.
The GrabForeground is inactive by default and needs to be activated by setting its boolean "active" flag to true.
The following example shows how you could set up a grab foreground. We are creating an image in RGBA color mode with size 1 to grab the whole viewport.
GrabForegroundPtr grab = GrabForeground::create();
beginEditCP(grab);
grabImage = Image::create();
beginEditCP(grabImage);
grabImage->set(GL_RGBA, 1);
endEditCP(grabImage);
grab->setImage(grabImage);
grab->setActive(true);
endEditCP(grab);
//mgr is of type osg::SimpleSceneManager
//We get a pointer to the foreground multifield from the viewport
MFForegroundPtr *foregrounds = mgr->getWindow()->getPort(0)->getMFForegrounds();
//now we simple add it to the foreground multifield
foregrounds->push_back(grab);
Whenever a frame is rendered the osg::Image grabImage will store the rendered frame overwriting a possibily previously stored frame.
Here is an overview of what we are going to do.
Application overview
This time we will start with a completely empty file! So open your editor of choice and create a new file and store it in the progs folder like the other tutorial files. First thing we need are a lot of include files:
#include <OpenSG/OSGGLUT.h> #include <OpenSG/OSGConfig.h> #include <OpenSG/OSGSimpleGeometry.h> #include <OpenSG/OSGGLUTWindow.h> #include <OpenSG/OSGSolidBackground.h> #include <OpenSG/OSGDirectionalLight.h> #include <OpenSG/OSGPerspectiveCamera.h> #include <OpenSG/OSGTransform.h> #include <OpenSG/OSGRenderAction.h> #include <OpenSG/OSGViewport.h> #include <OpenSG/OSGGradientBackground.h> #include <OpenSG/OSGImageBackground.h> #include <OpenSG/OSGImage.h> #include <OpenSG/OSGImageForeground.h> #include <OpenSG/OSGFileGrabForeground.h>
All include files should be familiar to you, however you might wonder why we need "OSGTransform.h" where we have used transformations before without ever including this header. Well, the simple scene manager we are going to drop this time, included a lot of needed files, like stuff for background, camera, viewports ans some important cores. As we are going to do it without the simple scene manager, we need to include all those files by hand now. For the lazy ones, like me, you can of course simply include "OSGSimpleSceneManager.h" even if you don't want to use the manager...
As long as not stated otherwise all coming blocks of code should be added to the bottom of the file, i.e. below the block you just added.
Alright, now activate the OpenSG namespace, because we do not want to begin with OSG:: every time
OSG_USING_NAMESPACE
We will need some global variables later on, so we are going to define them all now. Don't worry if you don't know what they will be for.
NodePtr scene; PerspectiveCameraPtr leftCamera; PerspectiveCameraPtr rightCamera; ViewportPtr leftViewport; ViewportPtr rightViewport; WindowPtr window; NodePtr leftCamBeacon, rightCamBeacon, lightBeacon, lightNode; RenderAction *renderAction;
The purpose of most variables should be obvious. The render action is needed for anything to render, but simply take it as it is, actions will be discussed in chapter Traversal of the Graph
Okay, let us insert our forward declaration like we ever did:
int setupGLUT( int *argc, char *argv[] );
Very well, our header is ready now. Now we can begin writing down the main function. We will begin with initializing OpenSG and a GLUT windows
int main(int argc, char **argv) { osgInit(argc,argv); int winid = setupGLUT(&argc, argv);
Normally we would create the scenegraph now and create an instance of the simple scene manager. This time we will create a node with a group node, which will be a placeholder for the scenegraph we'll add later on.
NodePtr scene = Node::create();
beginEditCP(scene);
scene->setCore(Group::create());
endEditCP(scene);
Now we can begin with creating some needed objects the simple scene manager would have taken care of otherwise. As we want two different views on the same scene, we need two viewports and two cameras.
// okay, that's not very spectacular anymore... leftCamera = PerspectiveCamera::create(); rightCamera = PerspectiveCamera::create(); //set attributes for camera beginEditCP(leftCamera); leftCamera->setBeacon(leftCamBeacon); leftCamera->setFov(deg2rad(90)); leftCamera->setNear(0.1); leftCamera->setFar(100); endEditCP(leftCamera); //same stuff for the other one beginEditCP(rightCamera); rightCamera->setBeacon(rightCamBeacon); rightCamera->setFov(deg2rad(90)); rightCamera->setNear(0.1); rightCamera->setFar(100); endEditCP(rightCamera);
So we just created two cameras with a field of view of 90 degrees. The near clipping plane is 0.1 units away from the camera where the far clipping plane is 100 units away.
Good, next we need a background. We decided previously to have one gradient and one image background.
GradientBackgroundPtr leftBkg = GradientBackground::create();
beginEditCP(leftBkg);
leftBkg->addLine(Color3f(0,0,0),0);
leftBkg->addLine(Color3f(1,1,1),1);
endEditCP(leftBkg);
The gradient background can have an arbitary number of different colors. We only want to have a gradiant background black (bottom) to white (top). You could simply add another line with another color and position. Fell free to experiment with that when this application is running for the first time (At this point of time you won't see anything).
The image background needs an image beforehand, so we first load one
//load the image file ImagePtr bkgImage = Image::create(); beginEditCP(bkgImage); bkgImage->read("data/front.jpg"); endEditCP(bkgImage);
Now the background itself
ImageBackgroundPtr rightBkg = ImageBackground::create();
beginEditCP(rightBkg);
rightBkg->setImage(bkgImage);
rightBkg->setScale(true);
endEditCP(rightBkg);
By now we have an image background displaying "data/front.jpg" which will be scaled to whatever size the viewport has. Now we have everything we need to create the viewports (i.e. camera, background and a root node).
leftViewport = Viewport::create();
rightViewport = Viewport::create();
beginEditCP(leftViewport);
leftViewport->setCamera(leftCamera);
leftViewport->setBackground(leftBkg);
leftViewport->setRoot(scene);
leftViewport->setSize(0,0,0.5,1);
endEditCP(leftViewport);
beginEditCP(rightViewport);
rightViewport->setCamera(rightCamera);
rightViewport->setBackground(rightBkg);
rightViewport->setRoot(scene);
rightViewport->setSize(0.5,0,1,1);
endEditCP(rightViewport);
As you can see, both viewports are of course quite similar. They both now have a camera, the corresponding background, the same root node and different sizes. If you recall what we have said about sizes of viewports earlier you can see, that we now have a viewport which covers the left half of the screen whereas the other covers the right half. We have provided the size not in pixel format, so the viewports will scale as the window is being resized.
Finally we complete the main() function by creating the window and the render action that will be needed for actually rendering the windows content
//and the render action - more on that later renderAction = RenderAction::create(); GLUTWindowPtr gwin= GLUTWindow::create(); gwin->setId(winid); gwin->setSize(300,300); window = gwin; // add the viewports to the new window window->addPort(leftViewport); window->addPort(rightViewport); window->init(); glutMainLoop(); return 0; }
For successfull compilation, you need the GLUTsetup() function we defined at the beginning.
int setupGLUT(int *argc, char *argv[]) { glutInit(argc, argv); glutInitDisplayMode(GLUT_RGB | GLUT_DEPTH | GLUT_DOUBLE); int winid = glutCreateWindow("OpenSG First Application"); return winid; }
Well, compilation will work, but execution results in an error, which tells you that there is no display callback function defined. That is true, so extend your setupGLUT function with these lines just before the return statement.
glutDisplayFunc(display);
glutIdleFunc(display);
as well as this block between the main() and setupGLUT() functions
void display(void) { window->render(renderAction); }
You now can execute the application, although there really is not much to see. You will see an error telling you that the camera has no beacon and you can see the gradient background only... anyway, we are not quite done yet, for a minimal application there is still a light source missing and beacons for the light and camera(s). So this will be our next step, to create a working scenegraph. We also add a torus to have at least some visible geometry. Locate the following lines at the beginning of the main() function
NodePtr scene = Node::create();
beginEditCP(scene);
scene->setCore(Group::create());
endEditCP(scene);
and replace them with our well know call of
scene =createScenegraph();
Next add the createScenegraph() function - place the code that belongs to it before the main() function! First we create a simple torus and we also create the nodes that will act as a beacon for the cameras and the light source.
NodePtr createScenegraph(){
//create geometry - just a simple torus
NodePtr torus = makeTorus(1,5,8,16);
//create transformations & beacons for cameras & light
leftCamBeacon = Node::create();
rightCamBeacon = Node::create();
lightBeacon = Node::create();
now we fill the three nodes we just created with life:
// the following style is a bit different than from before // this is only to remind you that beginEditCP()'s can also // be interleaved beginEditCP(leftCamBeacon); beginEditCP(rightCamBeacon); beginEditCP(lightBeacon); //create Transformations TransformPtr leftCamTrans, rightCamTrans, lightTrans; leftCamTrans = Transform::create(); rightCamTrans = Transform::create(); lightTrans = Transform::create(); beginEditCP(leftCamTrans); beginEditCP(rightCamTrans); beginEditCP(lightTrans); Matrix leftM, rightM, lightM; leftM.setTransform(Vec3f(-5,6,10)); rightM.setTransform(Vec3f(5,-6,10)); lightM.setTransform(Vec3f(1,10,2)); leftCamTrans->setMatrix(leftM); rightCamTrans->setMatrix(rightM); lightTrans->setMatrix(lightM); endEditCP(leftCamTrans); endEditCP(rightCamTrans); endEditCP(lightTrans); leftCamBeacon->setCore(leftCamTrans); rightCamBeacon->setCore(rightCamTrans); lightBeacon->setCore(lightTrans); endEditCP(leftCamBeacon); endEditCP(rightCamBeacon); endEditCP(lightBeacon); // -- end of camera beacon creation
(Note that this can be quite a bit more compact when using CPEdit and makeCoredNode(), if you can use a more current version than 1.2.)
All three nodes now contain a transformation core with a different transformation matrix. Relative to the origin, the first camera is located in the upper left corner where as the second is in the lower right, both 10 units away along the z axis. The light will be located 10 units above the origin.
Next, we create the light source for the scene. As we have only very simple geometry in our scene, one directional light should be sufficient.
//create the light source DirectionalLightPtr dLight = DirectionalLight::create(); beginEditCP(dLight); dLight->setDirection(Vec3f(0,1,2)); //color information dLight->setDiffuse(Color4f(1,1,1,1)); dLight->setAmbient(Color4f(0.2,0.2,0.2,1)); dLight->setSpecular(Color4f(1,1,1,1)); //set the beacon dLight->setBeacon(lightBeacon); endEditCP (dLight);
The beacon is set, but we will also need a node which will contain the light as a core to define what should be lit (compare Point Light)
// create the node that will contain the light source
lightNode = Node::create();
beginEditCP(lightNode);
lightNode->setCore(dLight);
lightNode->addChild(torus);
endEditCP(lightNode);
We are nearly finished for the first working version, we are just missing the root node:
// now create the root and add all children NodePtr root = Node::create(); beginEditCP(root); root->setCore(Group::create()); root->addChild(lightNode); root->addChild(leftCamBeacon); root->addChild(rightCamBeacon); root->addChild(lightBeacon); endEditCP(root); return root; }
Now try to compile and execute the application. It should look like this
Two different viewports on the same scene
As you can see, the torus is displayed from two different positions. This is really not a miracle as we have defined different locations for both cameras. This is nice, but if you resize the window, the viewports are not scaled correctly although they should! The reason is, that we have not specified the GLUT reshape callback function. Add this line to the setupGLUT() function
glutReshapeFunc(reshape);
and add this block of code right below the display() function
void reshape(int w, int h) { window->resize(w, h); glutPostRedisplay(); }
If you compare this function with the previous reshape function you will notice that the only thing that changed, is a replacement of the simple scene manager object with the window object.
Now the application can be resized correctly. We are still missing functionality we wanted to implement: the foreground is still missing as well as the snapshot feature. First we create a foreground which will display a png file in the right viewport. In the main() function locate the lines that say
rightViewport->setRoot(scene);
rightViewport->setSize(0.5,0,1,1);
endEditCP(rightViewport);
//and the render action - more on that later
renderAction = RenderAction::create();
and add the following new code right before the renderAction is created.
// add a logo foreground to the right viwport //load the logo image file ImagePtr frgImage = Image::create(); beginEditCP(frgImage); frgImage->read("data/logo.png"); endEditCP(frgImage); ImageForegroundPtr imgFrg = ImageForeground::create(); beginEditCP(imgFrg); //NOTE: the position values are between 0 and 1 //and are relative to the viewport! imgFrg->addImage(frgImage,Pnt2f(0.1,0)); endEditCP(imgFrg);
Now we have loaded an image from a file like we did for the image background. After that, we created the image foreground and simply added the image. The position I provided via Pnt2f is the location of the image relative to the lower left corner of that viewport. As these values are smaller than one, these are meant relative the same way when defining sizes of viewports.
The foreground still would not be visible, because we need to add it to the foreground field of a viewport. That is done by the following line of code
//add the created foreground by appending it to //the vieports foreground multifield rightViewport->getMFForegrounds()->push_back(imgFrg);
Finally we need to add another foreground that will allow us to capture the screen into files. This is with the osg::FileGrabForeground. The code can be placed right below the code you just added
//create the foreground for screenshot functionality FileGrabForegroundPtr fileGrab = FileGrabForeground::create(); beginEditCP(fileGrab); fileGrab->setActive(false); fileGrab->setName("data/screenshot%04d.jpg"); endEditCP(fileGrab); //add this one to the right viewport, too rightViewport->getMFForegrounds()->push_back(fileGrab);
We added the foreground the same way we did with the image foreground. We only set two attributes for the file grab foreground. We set a file name and also set it's active flag to false, to avoid that the applications starts to shot screenshots right at the beginning. Rather we want to de-/ activate this feature by pressing the 's' key. Maybe you find the filename a bit unusual - if I would have set it to "data/screenshot.jpg" every screenshot taken, would have overwritten that file. Of course that can be exactly what you want, but I decided, we want to have a series of images taken. The d is replaced by some internal counter variable and 04d means that there are leading zeros until four digits are used. Therefore the images series is screenshot0001.jpg screenshot0002.jpg and so on.
Let us add the keyboard callback to enable the user to switch screen capturing on and off. Add this to the setupGLUT function
glutKeyboardFunc(keyboard);
add this right below the reshape() function
void keyboard(unsigned char k, int x, int y){ switch(k){ case 27: { osgExit(); exit(1); } break; case 's': // The return value is actually a pointer to on osgPtr class // I don't know if that makes much sense at all... MFForegroundPtr foregrounds = *(window->getPort(1)->getMFForegrounds()); FileGrabForegroundPtr fg = FileGrabForegroundPtr::dcast(foregrounds[1]); if (!fg->getActive()){ std::cout << "start recording..." << std::endl; fg->setActive(true); }else{ std::cout << "stopped" << std::endl; fg->setActive(false); } break; } }
By the way: I know that it would be easier to store the foreground as a global variable, but I wanted to show you how elements of OpenSG can be retrieved from more complex data structures. With bigger projects it won't be possible to store everything globally ;-)
Our application is complete now. One hint for those with fast machines: Do not turn on screen capturing for to long, or you will have lots of jpg files...
One essential functionaliy is indeed still missing! We are not able to navigate in any means. Well, this is up to you now, have a look at the exercises ;-)
Important Hint: You can save a great amount of time, if you have a closer look at the osg::Navigator helper class. This class is also used by the simple scene manager.
Next Chapter : Traversal of the Graph
1.4.3