Chapter Objectives
After reading this chapter, you'll be able to do the following:
Create Inventor callback nodes that include calls to the OpenGL Library
Explain how Inventor uses and affects OpenGL state variables
Write a program that combines use of Inventor and OpenGL and uses the SoGLRenderAction
Use color index mode
(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.
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.)
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_- | 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 | Texture2 node |
GL_*_BIAS | 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 |
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. |
#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(); } |
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 .. } |
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.
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.
#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(); } |
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.
#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; } |