To render a shape, you must develop an approximation of it constructed of a collection of like primitives, typically csTriFans or csTriStrips. The tool that translates a shape into a mesh of contiguous triangles is called a tessellator.
Tessellation is interpretive; there is necessarily a difference between the original surface and the tessellated mesh. You can control how closely you want the mesh to resemble the surface.
Close resemblance, requiring many triangles, produces a realistic shape but incurs slow graphic processing.
A gross approximation of the original surface results in fast processing.
Applications often create a series of tessellated representations of a shape, each one called a level of detail (LOD). High resolution LODs are used when shapes are close to the viewer and low resolution LODs are used when shapes are far from the viewer. Because distance obscures detail, high resolution LODs are not necessary to represent distant shapes.
This chapter describes how to control the tessellation of shapes in the following sections:
Tessellators generate a sequence of straight-line segments to approximate an edge curve of a surface, then cover the surface with triangular tiles. With each triangle vertex it creates, a tessellator also stores the normal vector at the point from original surface. The normal vectors are necessary for lighting and shading calculations.
Tessellations necessarily burden the entire graphics pipeline; they provide a first definition of the rendering task by specifying a maximal set of vertices to be sent to the graphics hardware. You can redefine and simplify the rendering task by using the tools discussed in Part II, “High-Level Strategic Tools for Fast Rendering.”
Ideally you would quickly generate the simplest tessellation that adequately represents surfaces of interest. What is adequate depends on your particular rendering task. You may want to generate several tessellations with varying degrees of complexity and accuracy for one opRep and place them in level-of-detail nodes, as discussed in Chapter 4, “Rendering Appropriate Levels of Detail.” The tessellators include accessor functions to help you assess the load they create for the graphics hardware.
The control parameter for tessellations specifies the maximum deviation from the exact surface. Figure 11-2 illustrates the effects of varying the deviation. The upper left image is appropriate for accurate representation of the surface, the lower right image would be appropriate if the object were in the distant background of a scene.
The surface shown in Figure 11-2 was made with the repTest application using an opFrenetSweptSurface as follows (see “opFrenetSweptSurface” and “Rendering Higher Order Reps—repTest”):
opReal profile( opReal t ) { return 0.5*cos(t*6.0) + 1.25; };
opSuperQuadCurve3d *cross =
new opSuperQuadCurve3d( 0.75, new opVec3(0.0, 0.0, 0.0), 3.0 );
opCircle3d *path = new opCircle3d( 1.75, new opVec3(0.0, 0.0, 0.0) );
opFrenetSweptSurface *fswept =
new opFrenetSweptSurface( cross, path, profile );
fswept->setHandednessHint( true );
|
The number of triangles in Figure 11-2 decreases as the maximum-deviation parameter chordalDevTol varies from .001 to .01 to .1 to .5 (see “Tessellating Parametric Surfaces”). These numbers should be compared to the scale of the object, which has a maximum diameter of 6.125 = 2(1.75 + 1.75 × .75), a minimum diameter of .875 = 2(1.75 - 1.75 × .75), a maximum height of 2.625 = 2(1.75 × .75), and a minimum height of 1.125 = 2(.75 × .75).
You can apply a tessellator either to a scene graph or to just one node. The tessellators produce a csGeoSet from an opRep and place that csGeoSet in the csShape that holds the opRep.
A tessellation begins with a discrete set of vertices at surface edges. To prevent cracks from appearing between adjacent surfaces, the same set of vertices should be used to tessellate both surfaces.
To address the crack problem, you have several options, which are discussed in “Building Topology: Computing and Using Connectivity Information”. Table 10-1 lists the different approaches to topology building, and the methods to use for each.
The important methods of opTessellateAction are apply() and mpApply(), which tessellate all opReps below the csNode that is their only argument. They perform single-process (apply()) or multiple-process (mpApply()) traversal of the scene graph. If the csNode is a csShape holding an opRep, then only that opRep is tessellated. If you supply a csNode argument that is inappropriate for a particular opTessellateAction subclass, nothing happens.
Subclasses of opTessellateAction, which are described in the subsequent sections of this chapter, provide tessellators for specific opReps. Each subclass has a pair of public functions, tessellate() and tessellator(), which implement a tessellation for a specific opRep. Although these functions are public, you should not need them if you use any OpenGL Optimizer opTessellateAction; call one of the apply functions, apply() or mpApply(), to tessellate.
If you create several subclasses of opTessellateAction and call opTessellateAction::apply(), then for each surface encountered during the tessellation traversal, the algorithm used to perform the tessellation is that of the most derived instance of opTessellateAction that is appropriate for the surface. Thus, a call to the base class method will do the right thing for each opParaSurface, if you create instances of subclasses that provide the algorithms for doing so.
A tessellator won't tessellate an opRep if getGeometryCount is not zero. If you want to retessellate an opRep, you must call clearTessellation() for opParaSurface and call removeGeometry() for opCurve2d or opCurve3d. See the example in /Optimizer/src/apps/removetess/main.cxx for details.
The class has the following main methods:
class opTessellateAction : public csDispatch
{
public:
// Creating and destroying
opTessellateAction( void );
~opTessellateAction( void );
// Accessor functions
void setExtSize( int s );
int getExtSize( )
int getTriangleCount()
int getTriStripCount()
int getTriFanCount()
void setReverseTrimLoop( opBool enable )
opBool getReverseTrimLoop()
void setBuildTopoWhileTess(opBool _buildTopoWhileTess)
opBool getBuildTopoWhileTess()
void setTopo(opTopo * _topo)
opTopo *getTopo( void )
// Recursive action application
void apply ( csNode *node );
void mpApply( csNode *node );
};
|
| apply() and mpApply() |
| |
| getTriangleCount() |
| |
| getTriStripCount() and getTriFanCount() |
| |
| setBuildTopoWhileTess() and getBuildTopoWhileTess() |
If TRUE, before tessellating each surface, the connectivity of all previously tessellated surfaces is used to avoid cracks when tessellating. Notice that the final tessellations of the surfaces in the scene graph may still have cracks because of unforeseen junctions between surfaces. If FALSE, no topology is constructed while tessellating. This leads to two very different possible results:
| |
| setExtSize() and getExtSize() |
| |
| setReverseTrimLoop() and getReverseTrimLoop() |
| |
| setTopo() and getTopo() |
|
The class opTessCurve3dAction provides methods to develop a discrete approximation to an opCurve3d.
The class has the following main methods:
class OP_DLLEXPORT opTessCurve3dAction : public opTessellateAction
{
public:
// Creating and destroying
opTessCurve3dAction( );
opTessCurve3dAction( opReal chordalDevTol,
opBool scaleTolByCurvature,
int samples );
~opTessCurve3dAction();
// Accessor functions
void setChordalDevTol( const opReal chordalDevTol );
opReal getChordalDevTol( );
void setScaleTolByCurvature( const opReal scaleTolByCurvature );
opBool getScaleTolByCurvature( );
void setSampling( const int samples );
int getSampling( );
};
|
The opTessCuboidAction class tessellates an opCuboid. opTessCuboidAction is a minimal example of a tessellator.
The class has the following main methods:
class opTessCuboidAction : public opTessellateAction
{
public:
opTessCuboidAction( );
~opTessCuboidAction( );
// Tessellate action
static void tessellate( csDispatch *action, csObject *object);
// The actual cuboid tessellator
void tessellator( opCuboid &c);
};
|
| apply() and mpApply() |
|
The methods tessellate() and tessellator() occur for all subclasses of opTessellateAction; you will rarely need to use them (see “Base Class opTessellateAction” for more details about these functions).
This section discusses the two classes OpenGL Optimizer provides for tessellating parametric surfaces. The class opTessParaSurfaceAction has methods for any parametric surface. The class opTessNurbSurfaceAction takes advantage of OpenGL NURBS routines.
The opTessParaSurfaceAction class develops tessellations of any opParaSurface. If a surface has boundary curves, the tessellator starts there and specifies vertices at the edges of the surface. The tessellator then covers the surface with csTriStripSets or csTriFanSets, using the boundary vertices to “pin” the edges of the tessellation. If necessary, the tessellator creates edge vertices by constructing a discrete version of the boundary curve associated with each of the surface's opEdges. An advantage of starting all tessellations at boundaries is easy coordination of tessellations by several processors.
As part of the tessellation process, you can generate the u-v coordinates for each vertex created by the tessellator.
To control the accuracy of a tessellation, you specify a chordal deviation parameter which constrains the distance of edges in the tessellation from the original surface.
The class has the following main methods:
class opTessParaSurfaceAction : public opTessellateAction
{
public:
opTessParaSurfaceAction();
opTessParaSurfaceAction( opReal chordalDevTol,
opBool scaleTolByCurvature, int samples);
~opTessParaSurfaceAction();
// Accessor functions
void setChordalDevTol( const opReal chordalDevTol );
opReal getChordalDevTol( );
void setScaleTolByCurvature( const opReal scaleTolByCurvature )
opBool getScaleTolByCurvature()
void setSampling( const int samples )
int getSampling( )
void setNonUniformSampling(const opBool samples);
opBool getNonUniformSampline();
void setGenUVCoordinates( const opBool genUVCoordinates );
opBool getGenUVCoordinates( );
opBool capUbegin;
opBool capUend;
opBool capVbegin;
opBool capVend;
};
|
| apply() and mpApply() |
| |
| opTessParaSurface() |
| |
| setChordalDevTol() and getChordalDevTol() |
| |
| setGenUVCoordinates() and getGenUVCoordinates() |
| |
| setSampling() and getSampling() |
| |
| setScaleTolByCurvature() and getScaleTolByCurvature() |
| |
| capUbegin, capUend, capVbegin, capVend |
|
The methods tessellate() and tessellator(), which are not shown in the declaration above, occur for all subclasses of opTessellateAction; you will rarely need to use them (see “Base Class opTessellateAction” for more details about these functions).
The sample code in this section not only illustrates the main code elements for tessellating an opParaSurface but describes the steps in the rendering process. The lines of code perform the following procedures:
Submitting the scene graph to an opViewer. This is part of the main program loop.
Creating an instance of an opTessParaSurfaceAction.
Creating and tessellating an opSphere.
Developing the Cosmo3D scene-graph nodes.
The code in this section comes mainly from the functions main(), in the file /usr/share/Optimizer/src/apps/repTest/main.cxx, and makeShape() and makeObjects() in the file /usr/share/Optimizer/apps/repTest/repTest.cxx.
From main() |
|
|
The main routine of repTest, which is similar to the application opviewer, creates an opViewer, calls makeObjects() to get the tessellations, and starts the rendering event loop. makeObjects() fills the scene graph with tessellated reps. It calls setupShape() to tessellate the reps. |
| opViewer *viewer = new
opViewer(“repTest”,x,y,w,h); |
Create Tessellators, Set Accuracy |
|
|
tc is a tessellator included so setupShape() can accept an opCuboid in addition to an opParaSurface. |
| // Generic parametric surface // Set up the cuboid tessellator // Set the tolerance from the t->setChordalDevTol( tol ); |
Define setUpShape |
|
|
The function setupShape() creates a new csShape, applies an appearance, places an opRep in the csShape, places the csShape at a position specified by the arguments, and tessellates the opRep.
|
| // A helper function which attaches
// a rep to a newly created shape { // Get the current origin of the // Add the incoming offset to it // Now reset the origin to include
// the incoming offset // Set the appearance of this shape
// to be a random color // Attach the geometry and // Attach the shape to the scene globalTransform->addChild( rep ); // Tessellate the individual shape } |
Define makeObjects() |
|
|
The function makeObjects() sets up the scene graph, defines and tessellates the grid of reps, and places the tessellated surfaces in the scene graph. The code here shows the initial lines of makeObjects() (omitting code that controls the grid definition) and the example of defining on opParaSurface, a trimmed and untrimmed opSphere. See the file /usr/share/Optimizer/src/apps/repTest.cxx for more details on the parameters nVersions, OP_XDIST, OP_VIEWDIST, and numObject. |
| csGroup *makeObjects() // Add the global light to the // Attach the global transform // Set the tolerance from the t->setChordalDevTol( tol ); ... ... // Sphere///////////////////////////////// opSphere *sphere = new opSphere( 3 ); if ( nVersions <= 0 ) { opCircle2d *trimCircle2d = sphere-> } setUpShape( sphere, |
The opTessNurbSurfaceAction class tesselates surfaces using OpenGL NURBS utilities. As a result, the tessellation developed by opTessNurbSurfaceAction is well tuned for rendering. For more details about the OpenGL utilities, see the section “The GLU NURBS Interface” in Chapter 12 of the OpenGL Programming Guide, Second Edition.
The only member function of note is the constructor, which takes a chordal deviation parameter that has the same effect as that for opTessParaSurfaceAction.
To facilitate visualization of discrete data sets, OpenGL Optimizer provides four tessellators for various types of the template class opRegMesh. The tessellators accept opRegMeshType opConstant and opVariable. These are brief descriptions of the tessellation classes discussed in this section:
The last two mesh tessellators return what are known as “hedgehog” plots of the vector fields. They are both trivial derivations of the base class opTessVecAction:
The opTessIsoAction class interprets discrete versions of opReal-valued functions defined on three-dimensional space. That is, opTessIsoAction acts on an opRegMesh<opReal> and tessellates the mesh function's iso-surfaces.
The class has the following main methods:
class opTessIsoAction : public opTessellateAction
{
public:
// Creating and destroying
opTessIsoAction ();
opTessIsoAction (opReal threshold, int stride = 1);
~opTessIsoAction ();
// Accessor functions
void setThreshold (opReal thresh)
opReal getThreshold ()
void setStride (int _stride)
int getStride ()
};
|
| apply() and mpApply() |
| |
| opTessIsoAction() |
|
The opTessSliceAction class interprets discrete versions of opReal-valued functions defined on three-dimensional space. That is, opTessSliceAction acts on an opRegMesh<opReal> and shows, by a simple rainbow map, values of the functions that lie on a plane. opTessSliceAction uses one of three possible planes perpendicular to the coordinate axes.
The class has the following main methods:
class opTessSliceAction : public opTessellateAction
{
public:
opTessSliceAction();
opTessSliceAction (opReal position, char axis);
~opTessSliceAction();
// Accessor functions
void setPosition (opReal _position)
opReal getPosition ()
void setAxis (int _axis)
char getAxis ()
};
|
| apply() and mpApply() |
| |
| opTessSliceAction(position, axis) |
| |
| setAxis() and getAxis() |
| |
| setPosition() and getPosition() |
|
opTessVecAction is the base class for the tessellators that act on an opRegMesh<opVec2> or an opRegMesh<opVec3>. The latter are trivial derivations from an opTessVecAction.
The class has the following main methods:
class opTessVecAction : public opTessellateAction
{
public:
opTessVecAction( );
~opTessVecAction( );
// --- Accessors
void setMagScale (opReal _scale)
void setInitialColor (csVec4f _iColor)
void setTerminalColor (csVec4f _tColor)
opReal getMagScale()
csVec4f getInitialColor()
csVec4f getTerminalColor()
};
|
The opTessVec2dAction and opTessVec3dAction classes provide tessellators for the two mesh classes opRegMesh<opVec2> and opRegMesh<opVec3>. They are derived from opTessVecAction, and each contains no public member functions other than a constructor, a destructor, and the necessary tessellate() and tessellator() functions. If the opRep passed to one of the tessellators is not of the correct type, the tesselator returns NULL.
| apply() and mpApply() |
|
The following discussion highlights the basic structure of the opviz sample application, to orient you when you look at the source files.
The application opviz uses calls to OpenGL Optimizer's three-dimensional opRegMesh tessellators, and uses the opVizViewer class, which is derived from opViewer, to control scene graph interactions and rendering. The application opviz can read Plot3D data files, (three samples are included in the OpenGL Optimizer library to illustrate mesh tessellation). For more information on Plot3D data format, see, for example, http://www.nas.nasa.gov/NAS/FAST/RND-93-010.walatka-clucas/htmldocs/
chp_21.formats.html.
The application opviz runs tessellators on an opThreadManager, which uses an opFunctionAction to distribute tessellation tasks. For more information on opThreadManager and opFunctionAction, see “Overview of the Thread Manager”.
The following sections first present controls added to opViewer by the class opVizViewer, and then cover these components of opviz:
The main rendering routine and data loading
Creating a tessellator and a csShape to hold the tessellation
Applying the tessellator to an opRegMesh using an opThreadMgr
The opVizViewer class extends the functionality of opViewer by defining the function opVizViewer::keyHandler() to manipulate three tessellators.
The class opVizViewer allows you to perform these actions from the keyboard, in addition to those provided by opViewer:
| i | Runs an opTessIso. DOWN decreases the function value and tessellates the new isosurface. | |
| c | Runs an opTessSlice. LEFT moves slice in the negative direction along the appropriate axis and tessellates the new slice. x sets the slice plane perpendicular to the x axis. y sets the slice plane perpendicular to the y axis. z sets the slice plane perpendicular to the y axis. | |
| g | Runs an opTessVec3d. - decreases the size of the plotted vectors. | |
| 0,1... | Selects the mesh to act on. |
The opviz main loop parses command-line arguments, calls a data loader, and then calls eventLoop(), which is inherited from opViewer, to handle interaction with the data.
The data loader can read the three sample meshes (two scalar meshes and a vector mesh) that are included in the library. These meshes are discussed in Chapter 9 in these sections:
The data loader calls the opVizViewer methods addScalarMesh() and addVectorMesh(), which bring in the mesh data and modify the scene graph for convenient viewing. The add functions use the methods of the classes ScalarVizPacket and VectorVizPacket to control the tessellators.
The function ScalarVizPacket::init_isosurface(), from which the following lines are taken, is an example of how to begin using a tessellator. Tessellating slices of a vector field or a scalar mesh requires similar lines of code.
Create the tessellator
iso = new opTessIsoAction (); |
Create a csShape node to hold the tessellation.
For this application the node is placed under the root node group.
material-> setShininess (.0078125f * 116.0f); material->setTransparency (0.5); material-> setDiffuseColor (0.08, 0.0, 1.0); material-> setSpecularColor (0.75, 0.75, 1.0); appear->setMaterial (material); appear->setLightEnable (1); appear->setTranspEnable (1); appear->setTranspMode( csContext::BLEND_TRANSP); iso_shape->setAppearance (appear); group->addChild (iso_shape); |
When you enter i after starting opviz, the application calls ScalarVizPacket::run_isosurface(), which tessellates the sample data set. The application opviz, via subsequent calls in eventLoop(), then renders the isosurface. run_isosurface() uses the tessellator created by init_isosurface() and obtains tessellation parameters from the data management structure developed by addScalarMesh().
Although run_isosurface() creates a multi-thread framework, opviz uses only one thread. The application provides a framework that is easily extended to a multiprocess tessellation controlled by an opMPFunListAction (see “opMPFunListAction: Many Tasks, Many Processes”). For opviz, tessellations are performed by instances of an opFunctionAction called IsoAction. See the section “opFunctionAction: One Task, One Process”.
The function run_isosurface(), from which this code is taken, provides a multi-threaded environment.
The function checks the number of available processors and creates an opThreadManager, which runs only one thread; see “Overview of the Thread Manager”.
int numThreads = opGetProcessorCount();
// ...error checking code deleted
tm = new opThreadMgr(numThreads);
// --- Create action array. Currently the action array only
// contains one action: isosurface generation
// create array
int numActions = 1;
opFunctionAction **actions =
(opFunctionAction **) new
opFunctionAction [ numActions ];
// insert action(s) in the array
for (int i=0; i < numActions; i++)
|
IsoAction is an opFunAction. Its method function() performs the tessellation. See “opFunctionAction: One Task, One Process”.
// the action objects take a mesh and tessellator actions[i] = new IsoAction (mesh, iso, iso_shape); // --- the thread manager runs the // action(s) on separate threads tm-> SchedMPFunList (new opMPFunListAction( numActions, actions) ); |
Because this procedure may occur while another process is in a rendering traversal, the code from IsoAction::function() first removes the iso_shape node from the scene graph by submitting an opTransaction::removeChild() to the transaction manager. Then function() tessellates iso_shape, and submits an opTransaction::addChild() to the transaction manager, placing the newly tessellated shape back in the scene graph. (See “opTransaction” in Chapter 14).
Here shape is the member of IsoAction that corresponds to iso_shape in the lines of code above from ScalarVizPacket::init_isosurface() and scalarMesh is the member that corresponds to mesh.
int pc = shape->getParentCount();
for ( int i = 0; i < pc; i++ )
{
csGroup *parent =
(csGroup *)shape->getParent(i);
int place = parent->findChild (shape);
// --- extract existing geometry,
// delete and replace old one
opTransaction *trans1 =
new opTransaction;
trans1->removeChild(parent, shape);
opBlockingCommit(trans1);
isosurface->
tessellator(*scalarMesh, shape);
opTransaction *trans2 =
new opTransaction;
trans2->addChild(parent, shape);
opCommit(trans2);
}
|
To recover memory, function() has the IsoAction deleted.
return opDeleteThis; |