|
Writing Stereoscopic Graphics Software Using Silicon Graphics GL
By Robert Akka
1. Introduction
Nearly all SGI hardware systems built in the last five years (including all systems currently being shipped) include built-in support for StereoGraphics CrystalEyes(R) compatible stereoscopy, the most notable exceptions being some of the early Indigo models that operate at resolutions lower than 1280 x 1024. Furthermore, such stereo-ready Silicon Graphics computer systems have also been shipped with monitors that are designed to handle the higher vertical frequency that CrystalEyes requires. This nearly universal support for stereoscopic graphics makes SGI a very attractive platform for developing stereoscopic applications.
This document discusses methods for developing stereoscopic software on SGI systems using GL, including:
- How to check whether the hardware supports stereo
- Converting to and from stereo display mode
- Opening a window for stereoscopic display
- Viewporting for left and right eyes
- Stereoscopic perspective projections
- Interface issues
- Stereo support under OpenGL
Many of the concepts discussed in this document are illustrated in a short example program, whose source code should accompany this document.
2. Checking Stereoscopic Hardware
This section discusses how an application ought to check the stereoscopic compatibility of a user's computer system. If you would like to test your own computer's stereo compatibility, attempt to switch in and out of stereo mode using setmon (see section 4); if the system is not stereo-ready, you will most likely see no change.
Nearly all SGI computers that are currently in use include system support for stereoscopic display. Some older models, and a few lower priced recent models, do not support stereo. Application software should check whether the system is stereo-ready before attempting to display stereoscopic graphics. Here's how:
testval = (int) getgdesc (GD_STEREO);
If the return value equals 1 (TRUE), then the computer system is stereo-ready. If the return value equals 0 (FALSE), then it is not stereo-ready, and your application should not attempt to switch to a stereoscopic display mode.
Certain high-end SGI computer systems (some Crimson, VTX, Onyx models) include support for windowed square-pixel stereoscopy. To check whether this higher level of stereo support is available, use:
testval = (int) getgdesc (GD_STEREO_IN_WINDOW);
3. Four Different SGI Stereo Modes
SGI has four different stereoscopic display modes. Three of these are available on most SGI computer systems, and all four are available on certain high-end models.
STR_RECT:
The most common SGI stereo mode is the one that has been available the longest, STR_RECT. In this stereo mode, the vertical sync rate of the monitor is doubled from its normal display rate, and:
- Everything that is drawn to the top half (actually, the upper 492 out of 1024 pixel rows, a little less than half) of the normal display will appear to fill the screen for the left eye only.
- Everything that is drawn to the bottom half (actually, the lower 492 out of 1024 pixel rows) of the normal display will appear to fill the screen for the right eye only.
- The middle 40 pixel rows disappear into the display blanking interval.
STR_TOP:
This newer stereo mode allows stereoscopic windowing. In this stereo mode:
- Everything that is drawn to the top half (the upper 492 out of 1024 pixel rows) of the normal display will appear to fill the screen for both eyes, unless the contents of windows have been programmed to have both left- and right-eye buffers, in which case the left- and right-eye buffers will each be displayed to the appropriate eye.
Programming for STR_TOP display mode requires using SGI extensions to OpenGL.
STR_BOT:
This stereo mode is just like STR_TOP, except that the bottom half (492 pixel rows) of the normal display, rather than the top half, fill the screen for both eyes.
Programming for STR_BOT display mode requires using SGI extensions to OpenGL.
Square-pixel stereo:
Some high-end SGI models (those for which getgdesc (GD_STEREO_IN_WINDOW) returns TRUE) offer a fully double-buffered stereo windowing mode, though at somewhat lower than normal stereoscopic resolution.
4. Switching To and From Stereo Display Mode
Switching to and from stereoscopic display mode may be done by the application, or by the user from the command line.
To manually switch to stereo mode, type one of the following at the command line (see the above section about the difference between these stereo modes):
/usr/gfx/setmon STR_RECT
/usr/gfx/setmon STR_TOP
/usr/gfx/setmon STR_BOT
Before doing this, be sure that you will have a convenient command prompt to switch back out of stereo mode. For example, if switching to STR_TOP stereo mode, make sure that you will have a command prompt available on the top half of the display.
You would also use /usr/gfx/setmon to manually switch to square-pixel stereo, on those SGI models that support it. Consult SGI documentation, including man setmon and man stereo, for further details.
To manually switch out of stereo mode, type one of the following at the command line:
/usr/gfx/setmon 60HZ
/usr/gfx/setmon 72HZ
If you are not sure whether your system normally uses a 72 Hz vertical frequency, use 60HZ.
It is generally far more elegant for your application to switch to and from stereo mode on its own, without requiring the user to shell out and use setmon. To switch display modes using GL, simply use the setmonitor() function:
setmonitor (STR_RECT);
setmonitor (STR_TOP);
setmonitor (STR_BOT);
setmonitor() does not work for switching to square-pixel stereo. For systems that support square-pixel stereo, your code can call /usr/gfx/setmon using the system() function.
Before switching to stereo mode using setmonitor(), use getmonitor() to find out the previous monitor mode:
prev_monitor = (short) getmonitor ();
To switch out of stereo mode, simply setmonitor() to the previous monitor mode:
setmonitor (prev_monitor);
If the previous mode is unknown, or to switch to normal monoscopic display mode regardless of the previous mode, use:
setmonitor (HZ60);
Note again that GL does not provide programming support for STR_TOP or STR_BOT stereo display modes. Programming for these display mode requires using SGI extensions to OpenGL.
5. Opening a Window for Stereoscopic Display
You may create a window for doing stereo using the same methods that you normally do. The differences that are specific to stereo are:
- STR_RECT display mode: The display window needs to be full-screen, since anything else on the display will not view properly while the display is in this mode.
- STR_TOP display mode: The display window should either fill or be entirely contained within the upper half (492 pixel rows) of the normal display, since the remainder of the normal display does not appear in stereo mode. Stereo buffering requires SGI extensions to OpenGL.
- STR_BOT display mode: The display window should either fill or be entirely contained within the lower half (492 pixel rows) of the normal display. Stereo buffering requires SGI extensions to OpenGL.
- Square-pixel stereo display mode (if supported): No restrictions compared to non-stereo display.
To set up a full-screen display window that does not have window borders, using GL:
prefposition (0, getgdesc (GD_XPMAX), 0, getgdesc (GD_YPMAX));
winopen ("");
6. Stereoscopic Viewporting and Buffering
STR_RECT stereo mode:
In the STR_RECT stereo mode, the program should have a full-screen application window (see the section above). The left-eye viewport should then be placed at the top half (the upper 492 out of 1024 pixel rows, which is slightly less than half) of the normal display, and the right-eye viewport should be placed at the bottom half (the lower 492 out of 1024 pixel rows) of the normal display. GL's viewport() function enables this. Assuming a full-screen application window, the following specifies a viewport for the left-eye view:
viewport (0, 1279, 532, 1023);
and the following specifies a viewport for the right-eye view:
viewport (0, 1279, 0, 491);
STR_TOP and STR_BOT stereo modes:
GL's stereo buffering functions do not work with STR_TOP or STR_BOT stereo modes. Buffering for these stereo modes requires SGI extensions to OpenGL.
Square-pixel stereo:
GL provides stereoscopic buffering functions that work with the square-pixel stereo mode that is supported by certain high-end SGI computer systems. These stereo buffering functions work similarly to (and can be used together with) GL's double-buffering functions which enable smooth graphics animation.
To initialize stereoscopic buffering for an existing graphics window, use the GL function stereobuffer(). Like the commonly used doublebuffer() function, this will not take effect until gconfig() is called:
stereobuffer ();
gconfig ();
To disable stereo buffering in a graphics window, call monobuffer() followed by gconfig().
Use GL's leftbuffer() and rightbuffer() functions to draw to the left-eye and/or the right-eye buffer. To draw to the left-eye buffer only (this is the initial default):
leftbuffer (TRUE);
rightbuffer (FALSE);
To draw to the right-eye buffer only:
leftbuffer (FALSE);
rightbuffer (TRUE);
To draw to both eye's buffers at the same time:
leftbuffer (TRUE);
rightbuffer (TRUE);
7. Stereoscopic Projections
To produce stereoscopic graphics that both appear natural and are mathematically correct, render your stereo pairs using two asymmetrical-frustum perspective (also called offset perspective) projections whose projection axes are parallel to each other.
This involves projecting from two slightly different viewpoints in the scene. Naturally, the left eye's viewpoint should be offset somewhat to the left of the right eye's viewpoint. Since GL's perspective projection functions are based on the center of projection (the viewpoint) residing at the coordinate origin, this viewpoint offset is simulated by translating the scene elements in the opposite direction.
Thus, you want to translate the scene to the right for the left-eye projection, and to the left for the right-eye projection. This pre-projection translation is accomplished by using GL's translate() function just after the function that performs the perspective projection.
How much separation should there be between the stereoscopic viewpoints?
For stereoscopic graphics that are both compelling and comfortable, it is important to have enough stereoscopic interaxial separation, but not too much. Generally, you want to have negative parallax (projected display offset that causes something to appear to pop out of the screen) ranging from 0% to 3% of the width of the display window, and you want to have positive parallax (projected display offset that causes something to appear to sink into the screen) ranging from 0% to 3% of the width of the display window. This can usually be achieved using a moderately wide angle field of view, and a camera separation that is about 5% of the distance from the viewpoints to the center of interest in the scene being rendered. Consult the StereoGraphics Handbook for more information about stereoscopic aesthetic issues.
It is extremely important to use asymmetrical-frustum projections for generating stereo pairs. If you use parallel perspective projections without asymmetrical frustums, the result will be uncomfortable graphics that reside entirely in negative parallax viewing space (everything coming out of the screen). GL provides two functions for implementing perspective projections, perspective() and window(). Of these, only window() permits asymmetrical-frustum projections (you may also implement projections by defining transformation matrices; see the StereoGraphics Handbook for more about the appropriate transformation matrices for stereoscopic asymmetrical-frustum projections).
The frustum of the left-eye perspective projection should take in a slightly wider angle to the right of its projection axis than to the left of it, and the frustum of the right-eye projection should take in a slightly wider angle to the left of its projection axis than to the right. Scene elements that reside in plane at which the two frustums intersect will be rendered at zero parallax. Scene elements at zero parallax will appear to reside at the display surface, neither popping out of the screen nor sinking into it. Ideally, scene elements close to the center of interest in the scene should be rendered at or near zero parallax.
Putting all of the above, the viewpoint translation and the asymmetrical-frustum projection, into GL code:
Left eye view:
window (-top * aspect + (half_sep * clip_near / distance), top *
aspect + (half_sep * clip_near / distance), -top, top,
clip_near, clip_far);
translate (half_sep, 0.0, 0.0);
Right eye view:
window (-top * aspect - (half_sep * clip_near / distance), top *
aspect - (half_sep * clip_near / distance), -top, top,
clip_near, clip_far);
translate (-half_sep, 0.0, 0.0);
Where:
- top = half of the vertical frustum dimension as measured at the frustum's intersection with the near clipping plane
- aspect = the ratio between overall display width and overall display height (full-screen STR_RECT stereo), or the ratio between window width and window height (square pixel stereo)
- half_sep = half of the interaxial separation
- clip_near = the distance from the viewpoint to the near clipping plane distance = the distance from the viewpoint to the center of interest in the scene
- clip_far = the distance from the viewpoint to the far clipping plane
Note that, except with square-pixel stereo, stereo projections should be rendered at a vertically squashed aspect ratio of about 0.48 (=1024 / 492), since the stereo display mode expands the display vertically by about a factor of two. The code examples above have already taken aspect ratio into consideration.
Many stereoscopic implementations use a rotation method instead of parallel asymmetrical-frustum projections. With the rotation method, the two viewpoints are offset as they are above. However, rather than using two asymmetrical-frustum projections with parallel projection axes, standard (symmetrical-frustum) projections are done, with projection axes that are angled inward. Typically, the projection axes intersect at or near the center of interest in the scene. The rotation method has the advantage of being easier for most people to conceptualize. In addition, the rotation method is able to render into both positive and negative parallax, without requiring an asymmetrical-frustum projection. However, compared to the methods described in this section, the rotation method has two significant disadvantages: It is not mathematically correct, and the results will not look as good. The rotation method introduces distortion and vertical misalignment. As SGI software libraries and hardware acceleration fully support asymmetrical-frustum perspective projections, there is no good reason to use the rotation method.
8. Cursor and Interface Issues
When using the STR_RECT stereoscopic display mode, it is a good idea to limit the cursor to one eye's viewport of the display. For reasons having to do with pop-up menus, it is generally best to lock the cursor in the right-eye viewport:
setvaluator (MOUSEY, 492 / 2, 0, 492);
To restore the cursor range:
setvaluator (MOUSEY, 1024 / 2, 0, 1024);
With STR_RECT stereo mode, menus, buttons, dialog boxes, scroll bars, text, and anything else that you wish to appear for both eyes, must be separately drawn to both eyes' viewports. All such interface elements will appear vertically stretched compared to how they are drawn, due to the aspect ratio difference between the viewport and the display.
9. Stereo Support Under OpenGL
Writing stereoscopic applications for SGI using OpenGL requires using functions in an SGI extension to OpenGL. These special OpenGL extension functions can be used to develop stereoscopic applications that are windowed using the STR_TOP or STR_BOT stereoscopic display modes. These stereo functions are listed in the include file /usr/include/X11/extensions/SGIStereo.h. The SGI OpenGL extension functions allow you to check whether the display hardware is stereo-ready, switch in and out of stereoscopic display mode, and draw to either or both of the two eyes' buffers. Detailed information relating to the SGI OpenGL extensions is beyond the scope of this document.
Asymmetrical-frustum perspective projections may be done in OpenGL using the standard OpenGL functions glFrustum() and glTranslate(), instead of the similar GL functions window() and translate().
|
|