This chapter describes the OpenGL Volumizer file loader and consists of the following topics:
Applications based on OpenGL Volumizer have the option of creating volumetric shapes using different components. These components form the descriptive part of the OpenGL Volumizer API. The OpenGL Volumizer file loader provides an easy-to-use interface for creating and modifying volumetric shapes using a shape description file. This interface allows applications to modify the shape nodes at run time using the Volume Data Description Language (VDDL), a simple XML-based text file. This prevents application writers from having to modify the application for every such change.
The file format greatly reduces the effort involved in inserting OpenGL Volumizer nodes in existing applications. These applications might be based on other toolkits such as Open Inventor or OpenGL Performer. These scene graph toolkits treat the volumetric nodes as leaf nodes (see Chapter 9, “Advanced Topics”) and usually do not need to have any knowledge of the contents of these nodes. With the new interface, the leaf nodes can be modified simply by changing the XML-based, node description text file, which is loaded at run time by the application. Additionally, the interface supports using plug-in modules to allow application-defined loaders for loading different components in the data description hierarchy.
The file format supports loading of multiple OpenGL Volumizer shapes into a collection of shapes (see “The vzShapeSet Class”). This collection of shapes can then be rendered using vzClipRenderAction, described in the following sections.
See the following file for a minimal OpenGL Utility Toolkit (GLUT) application that uses the new file interface to load and render OpenGL Volumizer shape nodes:
VZROOT/src/apps/simple/xml/viewer.cxx |
The application is a slightly modified version of the example program used in Chapter 2, “Getting Started” and uses the file loader interface instead. The code can be compiled using the following commands:
(On Linux)
% g++ -c viewer.cxx % g++ -o viewer viewer.o -lvz -lvzxml -lglut -lGLU -lGL -lXmu -lX11 |
(On Windows)
C:\> cl.exe /c viewer.cxx /I ..\..\..\..\include /D VISUALCXX C:\> link.exe /out: viewer viewer.obj /libpath: ..\..\..\..\lib vz.lib vzxml.lib opengl32.lib glut32.lib |
Note that you need to run the VCVARS32.BAT batch file provided by Microsoft Visual Studio before compiling the sample code. Alternatively, you can create your own project in Microsoft Visual Studio to compile the application. Also, note that you need to have libtiff for Windows installed on your system in order to run this application.
The following are the two main components that change:
Loading a shape set
The following code loads a shape set and prints some debugging information about the shape set:
vzShapeSet *shapeSet = vzShapeSet::load(filename);
if(!shapeSet) {
cerr<<"Error loading shape set from "<<filename<<endl;
} else {
cerr<<"Shape set has "<<shapeSet->getNumShapes()<<" shapes"<endl;
}
|
Rendering a shape set
The following code demonstrates how to use vzClipRenderAction to render a shape set. It uses the draw(vzShapeSet *) method, which performs the management, sorting, and rendering of the shapes in the shape set.
vzClipRenderAction *renderAction = new vzClipRenderAction(); renderAction->draw(shapeSet); |
The following classes are closely tied to the use of the XML file loader:
vzShapeSet
vzClipRenderAction
vzGraphicsState
The vzShapeSet class is a collection of vzShapes and can be loaded directly using the method vzShapeSet::load(). The load method accepts the filename for the XML file, parses and validates the file, and then creates and returns the appropriate shape set. If the file format is invalid or if an error occurred while loading the file, the method returns NULL.
In addition to the file loader, the vzShapeSet class provides utility routines for querying the bounding box of the collection of shapes and for sorting the shape set in a back-to-front visibility-sorted order. Other routines are provided to add, remove, and retrieve individual shapes in the set. This allows applications to create and modify shape sets using a programmatic interface.
The vzClipRenderAction class, described in Chapter 6, “The Large-Data API: 3D Clip Textures”, can be used to render shape sets directly. A shape set can be rendered either using the draw(vzShapeSet *) method (as in the example described in the section “A Simple Code Example”) or using the regular manage, unmanage, and draw interfaces provided by the render action. In the former case, all the shape management, sorting, and rendering of the shapes in the set is handled by the render action. This scheme allows rendering of shapes with simple volume textures and those with clip textures in a unified manner using the same render action. The render action first allocates the resources required for rendering volume textures and then uses the remaining resources to render any clip textures in the shape set.
A shape set is associated with its own graphics state, which is useful for controlling dynamic rendering parameters for the set. This encapsulation is provided by the vzGraphicsState class, which is used by applications to pass the necessary viewing and other graphics state-related information to the shape set. For example, the graphics state associated with a vzShapeSet is used by the graphics state of vzParameterClipTexture in the set to update the centers of interest when the view direction changes.
The following code shows how to update the modelview matrix for the graphics state by querying the current matrix from OpenGL:
GLdouble modelview[16]; glGetDoublev(GL_MODELVIEW_MATRIX, modelview); shapeSet->getGraphicsState()->setModelviewMatrix(modelview); |
The preceding code needs to be called by the application to notify the shape set of changes to the viewing parameters when relevant. For example, if the viewing direction changes and the render action is using the multiresolution mode, the preceding code should be called.
The Volume Data Description Language (VDDL) allows you to change volume data descriptions at run time by simply editing a text file.Using XML tags, VDDL mimics the hierarchy of an OpenGL Volumizer shape description. The file loader uses appropriate default values for unspecified components in the file. For the complete syntax of the file format, see the vzxml.dtd file in the directory VZROOT/data/XML/. This directory also contains other files that can help you get started. The XMLViewer application installed in directory VZROOT/src/apps/XMLViewer loads and renders the sample VDDL files installed in directory VZROOT/data/XML.
The following topics are described in this section:
The following VDDL file is used to represent a shape with an appearance containing a single volume texture parameter, specified using a 3D TIFF file. In the following example, a vzBlock will be used as the default geometry and a vzTMSimpleShader as the default shader.
<?xml version="1.0"?> <!-- DTD file for the XML file format --> <!DOCTYPE vzShapeSet SYSTEM "vzxml.dtd"> <vzShapeSet> <vzShape> <!-- Appearance --> <vzAppearance> <!-- Parameter: name - 'volume', type - vzParameterVolumeTexture --> <vzParameterVolumeTexture name="volume"> <filename>VZROOT/data/medical/Phantom/CT.Head.char.tif</filename> </vzParameterVolumeTexture> </vzAppearance> </vzShape> </vzShapeSet> |
The file shown in the preceding section “Simple Shape Example” can be modified to define more complex shape sets containing multiple shapes, more sophisticated shaders, parameters, and so on. Augmenting the appearance description in the preceding file to add a new shader and the required parameters allows rendering the same shape with a volumetric lighting shader applied to it, as shown in the following code:
<!-- Tangent space shader --> <vzTMTangentSpaceShader/> <!-- Param: name - 'lookup_table', type - vzParameterLookupTable --> <vzParameterLookupTable name="lookup_table"/> <!-- Parameter: name - 'lightdir', type - vzParameterVec3f --> <vzParameterVec3f name="lightdir"/> |
On Silicon Graphics Prism systems, the VDDL can be used to load and initialize appearances with fragment shaders. The vzTMFragmentShader class interface requires the application to set parameters as part of the current appearance. The XML loader, however, takes care of setting up the parameters for the shader automatically.
The following example illustrates the use of an XML file to initialize a fragment shader and the required parameters:
(See file VZROOT/data/XML/fsWithBiasAndAdd.vz.)
<vzAppearance>
<!-- Shader -->
<vzTMFragmentShader>
<filename>VZROOT/src/shaders/BiasAndAdd.fs</filename>
</vzTMFragmentShader>
<!-- Parameters -->
<vzParameterVec3f name=”bias”>
<value>1.0 0.0 0.0</value>
</vzParameterVec3f>
<vzParameterLookupTable name=”lookup_table”/>
<vzParameterVolumeTexture name=”volume”>
<filename>VZROOT/data/medical/Phantom/CT.Head.char.tif
</filename>
</vzParameterVolumeTexture>
<vzParameterVolumeTexture name=”volume2”>
<filename>VZROOT/data/medical/Phantom/CT.Head.Bone.char.tif
</filename>
</vzParameterVolumeTexture>
</vzAppearance>
|
The following operations occur when the preceding file is loaded:
The loader binds the parameters that require a texture to be bound as follows:
| Texture 0 | lookup_table | |
| Texture 1 | volume | |
| Texture 2 | volume2 |
The loader sets up a uniform variable for each vzParameterVec3f attached to appearance:
uniform vec3f bias; |
The fragment shader and the XML file need to be written so that the textures used in the shader are the ones specified in the XML file.
The following code corresponds to the fragment shader referred to in the preceding XML file:
(See file VZROOT/src/shader/BiasAndAdd.fs.)
void main()
{
vec4 color , colorlut , color2;
// Accessing texture unit bound to “volume” for color value
color = texture3D(volume,volume_TexCoord.stp);
// Using the “color” from sampling “volume” to look up the
// “lookup_table”
colorlut = texture1D(lookup_table,color.x);
// Accessing texture unit bound to “volume” for color value
color2 = texture3D(volume2,volume2_TexCoord.stp);
// Multiplying bias and color2 value
color2.rgb = bias * vec3(color2);
// Adding the color from lookup table and color from multiplying
// bias and“volume2” color
gl_FragColor = colorlut + color2;
}
|
| Note: While writing the fragment shader, do not declare the parameters (textures and uniform variables) as they are declared internally by the vzTMRenderAction class. |
On Silicon Graphics Prism systems, the VDDL can be used to load and initialize appearances with fragment programs. The TMFragmentProgram interface requires the application to set texture unit callbacks to set up each texture unit appropriately. The XML loader, however, takes care of setting up the callbacks and other parameters for the shader automatically.
The following example illustrates the use of an XML file to initialize a fragment program and the required parameters :
(See file VZROOT/data/XML/fpWithBiasAndAdd.vz.)
<vzAppearance>
<!-- Shader -->
<vzTMFragmentProgram>
<filename>VZROOT/src/shaders/BiasAndAdd.fp</filename>
</vzTMFragmentProgram>
<!-- Parameters -->
<vzParameterVec3f name=”bias”>
<value>1.0 0.0 0.0</value>
</vzParameterVec3f>
<vzParameterLookupTable name=”lookup_table”/>
<vzParameterVolumeTexture name=”volume”>
<filename>VZROOT/data/medical/Phantom/
CT.Head.char.tif</filename>
</vzParameterVolumeTexture>
<vzParameterVolumeTexture name=”volume2”>
<filename>VZROOT/data/medical/Phantom/
CT.Head.Bone.char.tif</filename>
</vzParameterVolumeTexture>
</vzAppearance>
|
The following operations occur when the preceding file is loaded:
The loader sets up a per-texture unit callback for each parameter that requires a texture to be bound—that is, vzParameterLookupTable, vzParameterVolumeTexture, and vzParameterClipTexture:
| Texture 0 | lookup_table | |
| Texture 1 | volume | |
| Texture 2 | volume2 |
The loader sets up a local parameter for each vzParameterVec3f attached to appearance:
| Local 0 | bias |
The fragment program and the XML file need to be written so that the textures used in the program are the ones specified in the XML file.
The following code corresponds to the fragment program referred to in the preceding XML file (see file VZROOT/src/shader/BiasAndAdd.fp):
!!ARBfp1.0 PARAM bias = program.local[0]; # Temporary variables TEMP volume, volume2, lut; # Sample texture bound on unit 1 - CT Head “volume” TEX volume, fragment.texcoord[1], texture[1], 3D; # Lookup 1D texture on unit 0 “lookup_table” with output of unit 1 TEX lut, volume, texture[0], 1D; # Sample texture bound on unit 2 - CT Bone “volume2” TEX volume2, fragment.texcoord[2], texture[2], 3D; # Apply the bias to output of texture unit 2 MUL volume2.rgb, bias, volume2; # Add the result of volume with value in volume2 # Highlight the Bone structure in rendered output using the bias color ADD result.color, lut, volume2; END |
The following example defines a new block geometry that is scaled to a range of (–1, –1, –1) – (1, 1, 1):
<!-- Geometry --> <vzBlock> <offsets>-1 -1 -1</offsets> <dimensions>2 2 2</dimensions> </vzBlock> |
In order to match the geometry in this example, the geometry ROI for the texture can also be updated to the following:
<vzParameterVolumeTexture name="volume"> <filename>VZROOT/data/medical/Phantom/CT.Head.char.tif</filename> <geometryROI>-1 -1 -1 1 1 1</geometryROI> </vzParameterVolumeTexture> |
The XML loader allows you to get a well-formatted proprietary dataset into an application without too much programming. The loader has built-in modules for loading the following data formats:
IFL-based datasets, such as Tag Image File Format (TIFF) files, are quite common. The example shown in the earlier section “Simple Shape Example” shows the use of the built-in loader module for such files.
Raw binary datasets have a data header followed by an array of binary data values. The following example loads a volume texture provided in raw binary format using a built-in module:
<vzParameterVolumeTexture name="volume"> <filename>VZROOT/data/misc/bonsai.raw</filename> <!-- Built-in Module named "bin" --> <module name="bin"> <!-- User string for passing additional info to the loader --> <!-- Header size, data dimensions, data format, data type, --> <!-- endianess--> <userString>0 256 256 256 luminance ubyte</userString> </module> </vzParameterVolumeTexture> |
Note for advanced users regarding endianess:
To account for cross-platform datasets, you can specify an additional flag as part of the XML userString to indicate the endianess of the data. The valid values for endianess field are intel and mips. For example, this is important if your dataset was computed on an SGI IRIX system with MIPS processors and you are loading and rendering it on an Silicon Graphics Prism system with Intel Itanium2 processors. Note that this is only relevant if the data type for the volume data is larger than one-byte. The following example shows the appropriate userString for a dataset with unsigned short data type stored on a MIPS/IRIX system to be loaded on a Silicon Graphics Prism system:
<userString>0 256 256 256 luminance ushort mips</userString> |
If the endianess is not specified, the loader assumes that the endianess of the system used to compute the dataset is the same as that of the current system. Hence, the loader does not perform any byte swapping.
The built-in module for DICOM datasets allows you to load a series of DICOM files into a 3D texture. The data can also be processed using different image processing parameters passed as a user string. See the dicom.vz example in the directory VZROOT/data/XML.
The following example loads a set of DICOM files using the built-in DICOM loader module:
<vzParameterVolumeTexture name=”volume”>
<filename>dicom/picker/I.%03d</filename>
<module name=”dicom”>
<userString>-s 1 -e 87 +Wh 1 +obt3</userString>
</module>
</vzParameterVolumeTexture>
|
The XML file can specify the DICOM filenames and any other data processing options using the filename and userString tags. The same options are accepted by the dicomToIFL utility as command-line options. You can use the –h option to get a complete list of options.
For loading proprietary data formats that have no built-in loader in OpenGL Volumizer, you can write custom (plug-in) modules. These modules can be invoked from the XML file. A custom loader module is basically a shared library or a dynamic shared object (DSO) that provides a loader routine for loading the dataset in a format compliant to OpenGL Volumizer.
The following example shows how to use an application-provided plug-in module to load polygonal geometry from a data file using the appropriate DSO. On Windows, replace the DSO file in the example with the DLL file vzLoaders.dll.
<!-- Polygonal Geometry --> <vzPolyGeometry> <!-- Geometry data file name --> <filename>VZROOT/data/polygonal/knot.sgo</filename> <module> <!-- Module DSO's file name --> <filename>VZROOT/lib/libvzLoaders.so</filename> </module> </vzPolyGeometry> |
The loader module is responsible for implementing the callback routine createObjectClassName, where ObjectClassName specifies the name of the class without the vz prefix. The callback is defined as the following:
typedef vzObject * (*vzObjectLoaderCB)(const char *filename, const char *userString); |
For the preceding example, the callback routine might look like the following:
extern "C" vzObject *createPolyGeometry(const char *filename,
const char *userString)
{
vzPolyGeometry *geom = SimplePolyGeometry::open(filename);
if(!geom) {
cerr<<"Could not open file "<<filename<<" for vzPolyGeometry"<<endl;
return NULL;
}
return (vzObject *)geom;
}
|
For sample code, see file VZROOT/src/XMLPlugins.cxx.