19.5. Implementing a New Stereo Technique

The current stereo model allows programmers to add support for specific stereo devices that would not be covered by the default types of stereo. Supporting a new stereo technique relies on two classes: SoStereoViewer which describes what a stereo viewer should be (all viewers are of type SoStereoViewer ) and SoBaseStereo SoBaseStereo which describes any class that supports a stereo technique.

SoStereoViewer

Presumably, nobody should have to write a new stereo viewer – unless a new viewer is being written that does not derive from SoWinViewer SoWinViewer , for instance one that derives from SoWinRenderArea SoWinRenderArea . Note that SoStereoViewer is a pure virtual class. The main behavior of this class is to allow the specification of a specific stereo type and to trigger the actual rendering (the viewer is the only one that knows how to render a scene). Therefore, when a redraw is needed, the stereo viewer calls the render code of the stereo object; this sets up what is needed for rendering one eye’s view, calls the method for actual rendering on the stereo viewer, sets what is needed for rendering the other eye’s view, and calls the method for actual rendering again.

virtual void setStereoViewType(SoBaseStereo *stereo) = 0;
virtual SoBaseStereo *getStereoViewType() = 0;
virtual void setStereoActive(SbBool active) = 0;
virtual SbBool isStereoActive() = 0;
virtual void actualRendering() = 0;
  
void SetStereoViewType(SoBaseStereo stereoViewType);
SoBaseStereo GetStereoViewType();
void SetStereoActive(bool activate);
bool IsStereoActive();
void ActualRendering();
  

Other methods act and report on the state of the stereo viewing. This includes all methods related to adjusting the offset as well as the balance, plus the inversion of stereo. In some cases, the stereo class must make assumptions about how the stereo device interprets the data. For instance, in interleaved mode, it is assumed that the even scanlines will be used for the left eye, and odd scanlines for the right eye. If the stereo device interprets the data differently, the scene will appear in pseudo stereo, with distant points appearing closer than near points. Reversing the stereo (switching the left and right eye views) solves this problem.

virtual void reverseStereoView(SbBool reversed) = 0;
virtual SbBool isStereoViewReversed() = 0;
  
void ReverseStereoView(bool reverse);
bool IsStereoViewReversed();
  

Finally, other methods act and report on the state of the viewer itself, such as accessing the camera, getting and setting the viewport and the size and position of the window.

SoBaseStereo

SoBaseStereo SoBaseStereo is the base class of all stereo classes. Therefore, if it is necessary to implement support of another stereo technique, subclass this class. It allows both software and hardware OpenGL stereo to be implemented. Note that all default stereo classes assume that a valid OpenGL context is available, though this is not necessarily a requirement when creating new stereo classes.

The main method in SoBaseStereo SoBaseStereo is renderStereoView(). This method is called by the stereo viewer to ask the stereo object to start all the work needed to completely render in stereo. Therefore, the code in this method should at least call the method actualRendering() on the stereo viewer. Usually, the code in renderStereoView() follows three steps:

  1. Sets the camera in stereo mode for one eye and ask the stereo viewer to do an actual rendering,

  2. Sets the camera in stereo mode for the second eye and ask the stereo viewer to do an actual rendering, and

  3. Sets the camera back to monoscopic mode.

virtual void renderStereoView() = 0;
  
public void RenderStereoView();
  

Two additional methods, which may be used by the stereo viewer, report on the requirements of the stereo object during one rendering pass:

virtual SbBool canClearBeforeRender() = 0;
virtual SbBool requireHardware() = 0;
  
public bool CanClearBeforeRender();
public bool RequireHardware()
  

canClearBeforeRender() is not a query from the stereo object to the stereo viewer to ask to clear the color buffer. Rather, its return value indicates whether it is okay to clear the color buffer of the stereo object. If this method returns TRUE, the stereo viewer does not necessarily need to clear the buffer. But if it returns FALSE, the viewer should avoid clearing the color buffer because the buffer contains information needed for the stereo effect. For instance in interleaved mode, after the first eye’s view is rendered, the color buffer already contains every-other-scanline worth of information, thanks to the stencil buffer.

requireHardware() is the way for the stereo object to let the stereo viewer know that the selected stereo type requires OpenGL stereo to be activated. If it does, this means the viewer must try to find a pixel format that contains a stereo buffer.

Finally, a method is provided for clearing any state or resource used by the stereo object when it is no longer in use (for instance when the stereo viewer is set to another stereo object).

virtual void clearStereo() {};
  
public void ClearStereo();

Example

This example shows how to render stereo images in an offscreen buffer. It makes use of a custom stereo viewer (this is one exception to what is said in the section called “ SoStereoViewer”) and a simple stereo object. The offscreen rendering is done by the stereo viewer. This stereo viewer can’t be used with stereo classes that assume the presence of a valid OpenGL context. The source code is available in:$OIVHOME/src/Inventor/examples/Features/OffscreenStereo/OffscreenStereo.cxx.

Example 19.1. Render stereo images in an offscreen buffer

class OffscreenStereo : public SoStereoViewer {
public:

  // scene will be ref'ed and unref'ed

  OffscreenStereo(SoNode *scene)
    {
    setStereoViewType(new SimpleStereo);
    this->scene = scene;
    if (scene) scene->ref();
    width = height = 200;
    }
  ~OffscreenStereo()
    {
    if (scene) scene->unref();
    stereoViewType->clearStereo();
    delete stereoViewType;
    }

  void renderSceneInStereo()
    {
    stereoViewType->renderStereoView();
    }

  virtual void setStereoActive(SbBool active)
  virtual SbBool isStereoActive() { return TRUE; }

  virtual void actualRendering()
    {
    if (!scene) return;

    SbViewportRegion vp(SbVec2s(width, height));
    SoOffscreenRenderer *offscreenRender = new SoOffscreenRenderer(vp);
    offscreenRender->render();
    // do whatever is needed with the rendered eye view
    Output(offscreenRender->getBuffer());
    }

  virtual SoCamera *getViewerCamera()
    {
    if (!scene) return NULL;

    // else look for a camera in the scene. If one found
    // send the camera, otherwise create a camera, add it to
    // the scene and send it.
    return camera;
    }

  virtual void adjustClippingPlanes() { /* no need */ }

  virtual const SbVec2s& getSize() { return size; }
  virtual const SbVec2s& getTopLeft() { return topLeft; }
  virtual const SbVec2s getBottomRight() { return bottomRight; }

  virtual SbBool isViewerDoubleBuffer()
    {                                         // whatever
    return TRUE;
    }

  virtual void setStereoViewType(SoBaseStereo *svt)
    { stereoViewType = svt; }
  virtual SoBaseStereo getStereoViewType()
    { return stereoViewType; }

  virtual void reverseStereoView(SbBool reverse)
    { stereoViewType->reverseStereoView(reverse); }
  virtual SbBool isStereoViewReversed()
    { return stereoViewType->isStereoViewReversed(); }

  virtual void setStereoOffset(float stereoOffset)
    { offset = stereoOffset; }
  virtual float getStereoOffset()
    { return offset; }

  virtual void setStereoBalance(float stereoBalance)
    { balance = stereoBalance; }
  virtual float getStereoBalance()
    { return balance; }

  virtual void setViewport(short left, short bottom,
    short width, short height);
  virtual float getViewport(short &left, short &bottom,
    short &width, short &height);

private:
  SbVec2s size, topLeft, bottomRight;
  SoNode *scene;
  SoBaseStereo *stereoViewType;
  float offset, balance;
};

class SimpleStereo : public SoBaseStereo {
public:
  SimpleStereo() { reverse = FALSE; }
  ~SimpleStereo() { }

  virtual void reverseStereoView(SbBool reverse)
    { this->reverse = reverse; }
  virtual SbBool isStereoViewReversed()
    { return reverse; }

  virtual renderStereoView()
    {
    if (stereoViewer == NULL) return;
    SoCamera *camera = stereoViewer->getViewerCamera();
    camera->setStereoMode(reverse ? SoCamera::RIGHT_VIEW :SoCamera::LEFT_VIEW);
    stereoViewer->actualRendering();

    camera->setStereoMode(reverse ? SoCamera::LEFT_VIEW :SoCamera::RIGHT_VIEW);
    stereoViewer->actualRendering();

    camera->setStereoMode(SoCamera::MONOSCOPIC);
    }
  virtual StereoViewType getStereoViewType()
    {                                         // whatever
    return RAW_STEREO;
    }
  virtual SbBool canClearBeforeRender()
    {                                         // whatever
    return TRUE;
    }

  virtual SbBool requireHardware()
    { return FALSE; }
  virtual void clearStereo() { }

private:
  SbBool reverse;
};
  
            
          
            
          


The program then needs to create a scene graph, provide an output method to deal with each rendered image, and an OffscreenStereo object containing the scene graph. On this object, call the method renderSceneInStereo(). Although this example does not deal with a specific stereo device, it makes use of the Open Inventor stereo model by taking advantage of the view volume reorientation (as described in Section 19.2, “ Camera Projection and View Volume”) to deliver two images with high geometric consistence.

Note that the class SimpleStereo can be applied to any viewer, though it would act almost like monoscopic mode: as canClearBeforeRender() returns TRUE, and as no other specific code activates a particular device driver, the scene would be rendered twice on whatever OpenGL buffer is set at the moment. The first render would be overwritten by the second render, and the scene would appear as seen by one eye (with the view volume adapted to that eye).