Chapter 17. Using Inventor with OpenGL

Chapter Objectives

After reading this chapter, you'll be able to do the following:

(Advanced)
This chapter describes how to combine calls to the Inventor and OpenGL libraries in the same window. It includes several examples of programs that combine use of Inventor and OpenGL in different ways. Table 17-1 through Table 17-9 show how Inventor affects and is affected by OpenGL state. This entire chapter can be considered advanced material.

Introduction

This chapter is for the experienced OpenGL programmer and is not intended as an introduction to OpenGL. Before you read this chapter, be sure to read at least Chapters 1 through 5 and Chapter 9 of this programming guide. You'll need a basic understanding of the Inventor database (Chapter 1 through Chapter 4), Inventor actions (Chapter 9), and Inventor event handling (Chapter 10) before you begin combining features of OpenGL with Inventor.

The preferred way to combine use of OpenGL and Inventor is by subclassing. When you subclass, you create a new node that makes calls to OpenGL. This process, which is beyond the scope of this chapter and is described in detail in The Inventor Toolmaker, allows you to build on an existing node. Another advantage of subclassing is that your new class has access to Inventor reading and writing (callback nodes, described in this chapter, do not read and write detailed information to a file).

It is important to note that Inventor inherits state from OpenGL for rendering only. Additional Inventor features, such as picking, computing bounding boxes, and writing to a file, do not use OpenGL and are unaware of changes made directly to the OpenGL state variables. For example, it is possible to send a viewing matrix directly to OpenGL and then use Inventor to draw a scene without a camera. However, if you then try to pick an object, Inventor will not know what viewing transformation to use for picking, since it doesn't use OpenGL for picking.

You can combine Inventor with OpenGL in several ways. An easy way to add custom OpenGL rendering to a scene database is to add a callback node (SoCallback; see Example 17-2). This node allows you to set a callback function that is invoked for each of the various actions that nodes perform (rendering, picking, bounding-box calculation). The SoCallback node differs from the event callback node in that it provides callbacks for all scene operations rather than just for event handling.

A second way to combine Inventor with OpenGL is to create a GLX window, make OpenGL and Inventor calls, and then apply an SoGLRenderAction, as shown in Example 17-3. For instance, you could create a GLX window, clear the background, do some initial rendering into the window, set up the viewing matrix, and then use Inventor to draw a scene by applying a GL render action to the scene graph. Or, you could use Inventor to set up the camera, lights, and materials, and then use OpenGL code to draw the scene. As long as you follow the general rules described in the following section on OpenGL state usage, you can mix OpenGL and Inventor rendering as you wish. (Note that this is an advanced feature, not for the faint of heart.)

OpenGL State Variables and Inventor

If you need to combine Inventor and OpenGL calls, Table 17-1 through Table 17-9 list the OpenGL state variables and describe which Inventor nodes (or actions) change those variables. If Inventor uses the current value of an OpenGL state variable and never changes it, the variable is omitted from this set of tables. See the OpenGL Programming Guide for a complete list of all OpenGL state variables and their default values. The recommended value for these variables is the default value, with two exceptions: turn on z-buffering and use RGB color mode.

Remember, the constructor for SoGLRenderAction takes a parameter that specifies whether to inherit the current OpenGL values. If you specify TRUE, Inventor inherits values from OpenGL. If you specify FALSE (the default), Inventor sets up its own reasonable default values (see Chapter 9).

To save and restore OpenGL state, use the OpenGL pushAttributes() and popAttributes() commands. For example, if you change variables in the OpenGL state in a callback node, you need to restore the state when the callback node is finished. Note that if your callback node begins with a pushAttributes() and ends with a popAttributes(), but a render abort occurs in between, popAttributes() is never called and the state is not restored.

Table 17-1. OpenGL State Variables: Current Values and Associated Data

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_CURRENT_COLOR

Shapes, Material, Base Color

GL_CURRENT_INDEX

Color Index node, Shapes

GL_CURRENT_TEXTURE_COORDS

Shapes, TextureCoordinate2

GL_CURRENT_NORMAL

Shapes, Normal

GL_CURRENT_RASTER_POSITION

Text2

GL_CURRENT_RASTER_COLOR

Text2

GL_CURRENT_RASTER_INDEX

Text2

GL_CURRENT_RASTER_POSITION_-
VALID

Text2


Table 17-2. OpenGL State Variables: Transformation State

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_MODELVIEW_MATRIX

Transformation nodes, Cameras

GL_PROJECTION_MATRIX

Cameras

GL_TEXTURE_MATRIX

Texture2Transform

GL_VIEWPORT

Cameras

GL_DEPTH_RANGE

Cameras

GL_MODELVIEW_STACK_DEPTH

Transformation nodes

GL_TEXTURE_STACK_DEPTH

Texture2Transform

GL_MATRIX_MODE

Cameras, Texture2Transform


Table 17-3. OpenGL State Variables: Coloring

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_FOG_COLOR

Environment node

GL_FOG_INDEX

Environment node

GL_FOG_DENSITY

Environment node

GL_FOG_START

Environment node

GL_FOG_END

Environment node

GL_FOG_MODE

Environment node

GL_FOG

Environment node

GL_SHADE_MODEL

Light Model, if in color index mode


Table 17-4. OpenGL State Variables: Lighting

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_LIGHTING

Light Model

GL_COLOR_MATERIAL

Shapes

GL_MATERIAL_PARAMETER

Shapes

GL_MATERIAL_FACE

Shapes

GL_AMBIENT

Shapes, Material

GL_DIFFUSE

Shapes, Material

GL_SPECULAR

Shapes, Material

GL_EMISSION

Shapes, Material

GL_SHININESS

Shapes, Material

GL_LIGHT_MODEL_AMBIENT

Shapes, Material

GL_LIGHT_MODEL_LOCAL_VIEWER

Shapes, Material

GL_LIGHT_MODEL_TWO_SIDE

Shape Hints

GL_AMBIENT

Lights

GL_DIFFUSE

Lights

GL_SPECULAR

Lights

GL_POSITION

Lights

GL_CONSTANT_ATTENUATION

Environment

GL_LINEAR_ATTENUATION

Environment

GL_QUADRATIC_ATTENUATION

Environment

GL_SPOT_DIRECTION

Lights

GL_SPOT_EXPONENT

Lights

GL_SPOT_CUTOFF

Lights

GL_LIGHTi

Lights

GL_COLOR_INDEXES

Lights


Table 17-5. OpenGL State Variables: Rasterization

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_POINT_SIZE

Draw Style

GL_POINT_SMOOTH

Render action

GL_LINE_WIDTH

Draw Style

GL_LINE_SMOOTH

Render Action

GL_LINE_STIPPLE_PATTERN

Draw Style

GL_LINE_STIPPLE

Draw Style

GL_CULL_FACE

Shape Hints

GL_CULL_FACE_MODE

Shape Hints

GL_POLYGON_MODE

Draw Style

GL_POLYGON_STIPPLE

Shapes if SCREEN_DOOR transparency


Table 17-6. OpenGL State Variables: Texturing

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_TEXTURE_x

Texture2 node

GL_TEXTURE

Texture2 node

GL_TEXTURE_WIDTH

Texture2 node

GL_TEXTURE_HEIGHT

Texture2 node

GL_TEXTURE_COMPONENTS

Texture2 node

GL_TEXTURE_MIN_FILTER

Complexity node

GL_TEXTURE_MAG_FILTER

Complexity node

GL_TEXTURE_WRAP_x

Texture2 node

GL_TEXTURE_ENV_MODE

Texture2 node

GL_TEXTURE_ENV_COLOR

Texture2 node

GL_TEXTURE_GEN_x

Texture Coordinate Function nodes

GL_EYE_LINEAR

Texture Coordinate Function nodes

GL_OBJECT_LINEAR

Texture Coordinate Function nodes

GL_TEXTURE_GEN_MODE

Texture Coordinate Function nodes


Table 17-7. OpenGL State Variables: Pixel Operations

GL_BLEND

Render action, Texture2 node

GL_BLEND_SRC

Render action, Texture2 node

GL_BLEND_DST

Render action, Texture2 node


Table 17-8. OpenGL State Variables: Pixels

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_UNPACK_ALIGNMENT

Texture2 node

GL_*_SCALE
(* = RED; GREEN; BLUE; ALPHA)

Texture2 node

GL_*_BIAS
(* = RED; GREEN; BLUE; ALPHA)

Texture2 node


Table 17-9. OpenGL State Variables: Miscellaneous

OpenGL State Variable

Inventor Nodes That Change This Variable

GL_LIST_BASE

Text2, Text3 nodes

GL_LIST_INDEX

Separator, Text2, Text3 nodes

GL_LIST_MODE

Separator, Text2, Text3 nodes


Color-Index Mode

You can open an X window that supports OpenGL rendering in either RGB mode or color-index (also referred to as color-map) mode. If you use color-
index mode, be sure to load the color map. Example 17-1 shows how to set the color map for the SoXtRenderArea. See also the Open Inventor C++ Reference Manual on SoXtRenderArea::setColorMap().

If you are using BASE_COLOR lighting, use the SoColorIndex node to specify the index into the color map.

If you are using PHONG lighting, use the SoMaterialIndex node to specify indices into the color map for the ambient, diffuse, and specular colors. This node also includes fields for specifying the shininess and transparency values (but not the emissive value). It expects the color map to contain a ramp from ambient to diffuse to specular colors.


Tip: You can design a scene graph that can be used in RGB or color index windows by putting both SoMaterialIndex and SoMaterial nodes in it.


Example 17-1. Using Color Index Mode


#include <Inventor/SoDB.h>
#include <Inventor/SoInput.h>
#include <Inventor/nodes/SoNode.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/viewers/SoXtExaminerViewer.h>
#include <GL/glx.h>

// Window attribute list to create a color index visual.
// This will create a double buffered color index window
// with the maximum number of bits and a zbuffer.
int attribList[] = {
   GLX_DOUBLEBUFFER, 
   GLX_BUFFER_SIZE, 1, 
   GLX_DEPTH_SIZE, 1, 
   None };

// List of colors to load in the color map
static float colors[3][3] = {{.2, .2, .2}, {.5, 1, .5}, 
         {.5, .5, 1}};

static char *sceneBuffer = "\
#Inventor V2.0 ascii\n\
\
Separator { \
   LightModel { model BASE_COLOR } \
   ColorIndex { index 1 } \
   Coordinate3 { point [ -1 -1 -1, -1 1 -1, 1 1 1, 1 -1 1] } \
   FaceSet {} \
   ColorIndex { index 2 } \
   Coordinate3 { point [ -1 -1 1, -1 1 1, 1 1 -1, 1 -1 -1] } \
   FaceSet {} \
} ";

void
main(int , char **argv)
{
   // Initialize Inventor and Xt
   Widget myWindow = SoXt::init(argv[0]);
   
   // Read the scene graph in
   SoInput in;
   SoNode *scene;
   in.setBuffer((void *)sceneBuffer, (size_t)
            strlen(sceneBuffer));
   if (! SoDB::read(&in, scene) || scene == NULL) {
      printf("Couldn't read scene\n");
      exit(1);
   }
   
   // Create the color index visual
   XVisualInfo *vis = glXChooseVisual(XtDisplay(myWindow), 
      XScreenNumberOfScreen(XtScreen(myWindow)), attribList);
   if (! vis) {
      printf("Couldn't create visual\n");
      exit(1);
   }
   
   // Allocate the viewer, set the scene, the visual and
   // load the color map with the wanted colors.
   //
   // Color 0 will be used for the background (default) while
   // color 1 and 2 are used by the objects.
   //
   SoXtExaminerViewer *myViewer = new
            SoXtExaminerViewer(myWindow);
   myViewer->setNormalVisual(vis);
   myViewer->setColorMap(0, 3, (SbColor *) colors);
   myViewer->setSceneGraph(scene);
   myViewer->setTitle("Color Index Mode");
   
   // Show the viewer and loop forever...
   myViewer->show();
   XtRealizeWidget(myWindow);
   SoXt::mainLoop();
}

Using an SoCallback Node

A typical use of an SoCallback node is to make calls to OpenGL. At the beginning of the callback function, you need to check the action type and then proceed based on the type of action that has been applied to the node. Typically, you are interested in the render action:

if(action->isOfType(SoGLRenderAction::getClassTypeId())){
   
   ...execute rendering code ..

}

Caching

The effects of a callback node may not be cacheable, depending on what it does. For example, if the callback node contains shapes whose geometry is changing, it should not be cached. In Example 17-2, the callback node creates a checked background, which can be cached because it is not changing.

If a callback node relies on any information outside of Inventor that may change (such as a global variable), it should not be cached. To prevent Inventor from automatically creating a cache, use the SoCacheElement::-
invalidate()
method from within a callback. For example:

void
myCallback(void *myData, SoAction *action)
{
   if (action->isOfType(SoGLRenderAction::getClassTypeId())){
      SoCacheElement::invalidate(action->getState());
         //makes sure this isn't cached
      //...make OpenGL calls that depend on a global variable...//
   }
}

Be careful when opening an OpenGL display list inside an SoCallback node. Recall from Chapter 9 that the Inventor render cache contains an OpenGL display list. Only one OpenGL display list can be open at a time, and a separator node above the callback node may have already opened a display list for caching. If your callback node opens a second display list, an error occurs. Use the SoCacheElement::anyOpen() method to check whether a cache is open.

Using a Callback Node

Example 17-2 creates an Inventor render area. It uses Inventor to create a red cube and a blue sphere and then uses an SoCallback node containing GL calls to draw a checked “floor.” The floor is cached automatically by Inventor. Note that the SoXtRenderArea automatically redraws the scene when the window is resized. Example 17-3, which uses a GLX window, does not redraw automatically.

Both Examples 17-2 and 17-3 produce the same image, shown in Figure 17-1.

Figure 17-1. Combining Use of Inventor and OpenGL


Example 17-2. Using a Callback Node


#include <GL/gl.h>
#include <Inventor/SbLinear.h>
#include <Inventor/Xt/SoXt.h>
#include <Inventor/Xt/SoXtRenderArea.h>
#include <Inventor/nodes/SoCallback.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoPerspectiveCamera.h>
#include <Inventor/nodes/SoSphere.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoTransform.h>

float   floorObj[81][3];

// Build a scene with two objects and some light
void
buildScene(SoGroup *root)
{
   // Some light
   root->addChild(new SoLightModel);
   root->addChild(new SoDirectionalLight);

   // A red cube translated to the left and down
   SoTransform *myTrans = new SoTransform;    
   myTrans->translation.setValue(-2.0, -2.0, 0.0);
   root->addChild(myTrans);

   SoMaterial *myMtl = new SoMaterial;
   myMtl->diffuseColor.setValue(1.0, 0.0, 0.0);
   root->addChild(myMtl);
   
   root->addChild(new SoCube);

   // A blue sphere translated right
   myTrans = new SoTransform;    
   myTrans->translation.setValue(4.0, 0.0, 0.0);
   root->addChild(myTrans);

   myMtl = new SoMaterial;
   myMtl->diffuseColor.setValue(0.0, 0.0, 1.0);
   root->addChild(myMtl);
   
   root->addChild(new SoSphere);
}

// Build the floor that will be rendered using OpenGL.
void
buildFloor()
{
   int a = 0;

   for (float i = -5.0; i <= 5.0; i += 1.25) {
      for (float j = -5.0; j <= 5.0; j += 1.25, a++) {
         floorObj[a][0] = j;
         floorObj[a][1] = 0.0;
         floorObj[a][2] = i;
      }
   }
}

// Draw the lines that make up the floor, using OpenGL
void
drawFloor()
{
   int i;

   glBegin(GL_LINES);
   for (i=0; i<4; i++) {
      glVertex3fv(floorObj[i*18]);
      glVertex3fv(floorObj[(i*18)+8]);
      glVertex3fv(floorObj[(i*18)+17]);
      glVertex3fv(floorObj[(i*18)+9]);
   }

   glVertex3fv(floorObj[i*18]);
   glVertex3fv(floorObj[(i*18)+8]);
   glEnd();

   glBegin(GL_LINES);
   for (i=0; i<4; i++) {
      glVertex3fv(floorObj[i*2]);
      glVertex3fv(floorObj[(i*2)+72]);
      glVertex3fv(floorObj[(i*2)+73]);
      glVertex3fv(floorObj[(i*2)+1]);
   }
   glVertex3fv(floorObj[i*2]);
   glVertex3fv(floorObj[(i*2)+72]);
   glEnd();
}

// Callback routine to render the floor using OpenGL
void
myCallbackRoutine(void *, SoAction *)
{
   glPushMatrix();
   glTranslatef(0.0, -3.0, 0.0);
   glColor3f(0.0, 0.7, 0.0);
   glLineWidth(2);
   glDisable(GL_LIGHTING);  // so we don't have to set normals
   drawFloor();
   glEnable(GL_LIGHTING);   
   glLineWidth(1);
   glPopMatrix();
}


main(int, char **)
{
   // Initialize Inventor utilities
   Widget myWindow = SoXt::init("Example 17.1");

   buildFloor();

   // Build a simple scene graph, including a camera and
   // a SoCallback node for performing some GL rendering.
   SoSeparator *root = new SoSeparator;
   root->ref();

   SoPerspectiveCamera *myCamera = new SoPerspectiveCamera;
   myCamera->position.setValue(0.0, 0.0, 5.0);
   myCamera->heightAngle  = M_PI/2.0;  // 90 degrees
   myCamera->nearDistance = 2.0;
   myCamera->farDistance  = 12.0;
   root->addChild(myCamera);

   SoCallback *myCallback = new SoCallback;
   myCallback->setCallback(myCallbackRoutine);
   root->addChild(myCallback);

   buildScene(root);
   
   // Initialize an Inventor Xt RenderArea and draw the scene.
   SoXtRenderArea *myRenderArea = new SoXtRenderArea(myWindow);
   myRenderArea->setSceneGraph(root);
   myRenderArea->setTitle("OpenGL Callback");
   myRenderArea->setBackgroundColor(SbColor(.8, .8, .8));
   myRenderArea->show();
   
   SoXt::show(myWindow);
   SoXt::mainLoop();
}

Applying a Render Action Inside a GLX Window

Example 17-3 creates a GLX window, makes Inventor and OpenGL calls, and then applies a GL render action. It uses OpenGL to render a checked “floor” and Inventor to render a red cube and a blue sphere, in the same window.

Example 17-3. Using a GLX Window


#include <GL/glx.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <stdio.h>
#include <unistd.h>

#include <Inventor/SoDB.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/nodes/SoCube.h>
#include <Inventor/nodes/SoDirectionalLight.h>
#include <Inventor/nodes/SoLightModel.h>
#include <Inventor/nodes/SoMaterial.h>
#include <Inventor/nodes/SoTransform.h>
#include <Inventor/nodes/SoSeparator.h>
#include <Inventor/nodes/SoSphere.h>

#define    WINWIDTH   	400	
#define    WINHEIGHT  	400	

float   floorObj[81][3];

// Build an Inventor scene with two objects and some light
void
buildScene(SoGroup *root)
{
   // Some light
   root->addChild(new SoLightModel);
   root->addChild(new SoDirectionalLight);

   // A red cube translated to the left and down
   SoTransform *myTrans = new SoTransform;
   myTrans->translation.setValue(-2.0, -2.0, 0.0);
   root->addChild(myTrans);

   SoMaterial *myMtl = new SoMaterial;
   myMtl->diffuseColor.setValue(1.0, 0.0, 0.0);
   root->addChild(myMtl);
   root->addChild(new SoCube);

   // A blue sphere translated right
   myTrans = new SoTransform;
   myTrans->translation.setValue(4.0, 0.0, 0.0);
   root->addChild(myTrans);

   myMtl = new SoMaterial;
   myMtl->diffuseColor.setValue(0.0, 0.0, 1.0);
   root->addChild(myMtl);

   root->addChild(new SoSphere);
}


// Build a floor that will be rendered using OpenGL.
void
buildFloor()
{
   int a = 0;

   for (float i = -5.0; i <= 5.0; i += 1.25) {
      for (float j = -5.0; j <= 5.0; j += 1.25, a++) {
         floorObj[a][0] = j;
         floorObj[a][1] = 0.0;
         floorObj[a][2] = i;
      }
   }
}

// Callback used by GLX window
static Bool
waitForNotify(Display *, XEvent *e, char *arg)
{
   return (e->type == MapNotify) && 
            (e->xmap.window == (Window) arg);
}

// Create and initialize GLX window.
void
openWindow(Display *&display, Window &window)
{
   XVisualInfo	*vi;
   Colormap cmap;
   XSetWindowAttributes swa;
   GLXContext cx;
   XEvent event;
   static int attributeList[] = {
            GLX_RGBA,
            GLX_RED_SIZE, 1,
            GLX_GREEN_SIZE, 1,
            GLX_BLUE_SIZE, 1,
            GLX_DEPTH_SIZE, 1,
            GLX_DOUBLEBUFFER,	
            None,
   };

   display = XOpenDisplay(0);
   vi = glXChooseVisual(display, 
            DefaultScreen(display), attributeList);
   cx = glXCreateContext(display, vi, 0, GL_TRUE);
   cmap = XCreateColormap(display, 
            RootWindow(display, vi->screen), 
            vi->visual, AllocNone);
   swa.colormap = cmap;
   swa.border_pixel = 0;
   swa.event_mask = StructureNotifyMask;
   window = XCreateWindow(display, 
            RootWindow(display, vi->screen), 100, 100,
            WINWIDTH, WINHEIGHT, 0, vi->depth, InputOutput,
            vi->visual, 
            (CWBorderPixel | CWColormap | CWEventMask), &swa);

   XMapWindow(display, window);
   XIfEvent(display, &event, waitForNotify, (char *) window);
   glXMakeCurrent(display, window, cx);
}


// Draw the lines that make up the floor, using OpenGL
void
drawFloor()
{
   int i;

   glBegin(GL_LINES);
   for (i=0; i<4; i++) {
      glVertex3fv(floorObj[i*18]);
      glVertex3fv(floorObj[(i*18)+8]);
      glVertex3fv(floorObj[(i*18)+17]);
      glVertex3fv(floorObj[(i*18)+9]);
   }

   glVertex3fv(floorObj[i*18]);
   glVertex3fv(floorObj[(i*18)+8]);
   glEnd();

   glBegin(GL_LINES);
   for (i=0; i<4; i++) {
      glVertex3fv(floorObj[i*2]);
      glVertex3fv(floorObj[(i*2)+72]);
      glVertex3fv(floorObj[(i*2)+73]);
      glVertex3fv(floorObj[(i*2)+1]);
   }
   glVertex3fv(floorObj[i*2]);
   glVertex3fv(floorObj[(i*2)+72]);
   glEnd();
}


main(int, char **)
{
   // Initialize Inventor
   SoDB::init();

   // Build a simple scene graph
   SoSeparator *root = new SoSeparator;
   root->ref();
   buildScene(root);

   // Build the floor geometry
   buildFloor();

   // Create and initialize window
   Display *display;
   Window window;
   openWindow(display, window);
   glEnable(GL_DEPTH_TEST);
   glClearColor(0.8, 0.8, 0.8, 1.0);
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

   // Set up the camera using OpenGL.
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluPerspective(90.0, 1.0, 2.0, 12.0);

   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
   glTranslatef(0.0, 0.0, -5.0);

   // Render the floor using OpenGL
   glPushMatrix();
   glTranslatef(0.0, -3.0, 0.0);
   glColor3f(0.0, 0.7, 0.0);
   glLineWidth(2.0);
   glDisable(GL_LIGHTING);
   drawFloor();
   glEnable(GL_LIGHTING);
   glPopMatrix();

   // Render the scene
   SbViewportRegion myViewport(WINWIDTH, WINHEIGHT);
   SoGLRenderAction myRenderAction(myViewport);
   myRenderAction.apply(root);
   glXSwapBuffers(display, window);

   sleep (10); 
   root->unref();
   return 0;
}