GLUT-Example

In this tutorial I will show you how other programs can use the Voreen library to display volume datasets. The following example uses the GLUT-library. GLUT is short for OpenGL Utility Toolkit. It is a programing interface for writing window system independent OpenGL programs. Using GLUT offers various advantages:
- programs are independent from window system
- multiple windows for OpenGL rendering
- callback driven event processing
GLUT
But how can you react on events triggered by the system or user? Let’s take a closer look at GLUT’s callback driven event processing capability. It distinguishes between three types of callbacks:
- window callbacks indicate interaction between input devices and the window. It covers reshaping or visibility changes of the window as well.
- menu callbacks respond to events dealing with the GLUT-menu structure (not further described here)
- global callbacks manage the passing of time and menu usage (e.g. timer)
All callbacks in detail are explained on the GLUT project website [1].
The events are caught by GLUT by a never ending processing loop by calling glutMainLoop(). In pseudocode GLUT’s event processing looks like this:
void glutMainLoop() {
while (true) {
if (content of the window has changed)
call display callback function
if (keyboard or mouse event)
call input callback function
if (nothing has to be done)
call idle call back function
// ...
}
} So what do you have to do when you want to catch, e.g., a mouse event? That’s
quite easy to implement. You just have to write a function that handles mouse
events. GLUT will call it, once it knows about it. Here is an example:
Example code
void myMouseMotionEventHandler(int x, int y) {
// your code
}
int main (...) {
// ...
glutMotionFunc(myMouseMotionEventHandler); // register function
glutMainLoop(); //enters event processing loop
return 0;
} Voreen-GLUT-Example
In apps/glutexample you find the glutexample. We will now step through glutexample.cpp and explain the important parts.
tgt::GLUTCanvas - A Wrapper class for the canvas
To further abstract rendering, voreen uses a tgt canvas for its output to the screen. Since tgt has the class GLUTCanvas defining how to swap front/back buffers, how to get the OpenGL context with GLUT and how to handle GLUT mouse events we don’t need to implement an own wrapper fitting the needs for this example.
GLUT-Example initialization
We now switch to the end of the file to the GLUT-Example’s main function. It initializes tgt, Voreen, and GLUT and starts the main loop.
Before tgt (tiny graphics toolkit) can be used, it has to be initializied by calling tgt::init. If you want to know more details what exactly is done while initializing, please have a look at the tgt wiki. The next step is to initialize the GLUT-Library. Therefore you have to call glutInit() as in the code below. Command line arguments can be passed to GLUT as well. They are then evaluated by GLUT.
The most important step is to tell GLUT which OpenGL display mode to use. GLUT supports many display modes and for all see the GLUT documentation [2]. In this case we want to use color information in RGB format plus alpha value (GLUT_RGBA | GL_ALPHA) and to enable double- and depth buffer (GLUT_DOUBLE | GLUT_DEPTH). This is all done by initializing GlutCanvas. Also a window titled „Voreen – The Volume Rendering Engine“ is created and its size set to 512 × 512. At last we call our own init() method for initializing our own code, in this case the Voreen environment.
- tgt has to be initialized before it can be used. See the tgt wiki for more details.
- The GLUT init functions are explained in the example.
- canvas_ has to be initialized before calling GLUT methods. In this case ‘RGBADD’ enables rgb, alpha, depth and double buffer. Also a 512 × 512 window will be created . For this the tgt wiki provides further information as well.
- The init() function initalizes Voreen.
main
int main(int argc, char** argv) {
tgt::init();
glutInit(&argc, argv);
// initialize canvas
canvas_ = new tgt::GLUTCanvas("Voreen - The Volume Rendering Engine", tgt::ivec2(512, 512), tgt::GLCanvas::RGBADD);
canvas_->init();
glutKeyboardFunc(keyPressed);
init();
glutMainLoop();
tgt::deinit();
return 0;
}init() function
GLUT-Example’s init function initializes Voreen and sets OpenGL parameters.
The init() method is used to set all settings Voreen needs to work. The depth buffer is already enabled and some light and material constants are set by GLUTCanvas. Hence only the OpenGL dependent part of tgt needs to be initialized and the path to the shaders need to be set. Then the message distributor for the messaging system is started.
Now we connect the canvas with a camera, a trackball navigation, and a painter. The trackball process mouse movements and change the camera accordingly. Finally a painter is attached to the canvas. The painter paints all output to a canvas — it contains the actual volume renderer. We will load a network of processors out of the “standard” network, which consists of a pretty fancy raycaster which supports phong lighting and much more.
After that we load a sample transfer function and set a threshold — which can be modified lateron.
At last we want to load the “nucleon” file as an example dataset. We can do this with a VolumeSerializer, but we must feed that VolumeSerializer with the VolumeReaders we might need. The VolumeSerializerPopulator cares about that problem by giving us access to a fully functional VolumeSerializer.
init()
void init() {
// initialize OpenGL part of tgt
tgt::initGL();
// add shader path to the shader manager
ShdrMgr.addPath("../../src/core/vis/glsl");
// initialize the MessageDistributor
tgt::Singleton<MessageDistributor>::init(new MessageDistributor());
// init camera and connect it to the canvas
tgt::Camera* camera_ = new tgt::Camera(tgt::vec3(0.0f, 0.0f, 3.75f),
tgt::vec3(0.0f, 0.0f, 0.0f),
tgt::vec3(0.0f, 1.0f, 0.0f));
canvas_->setCamera(camera_);
// init trackball and connect it to the canvas
tgt::Trackball* trackball_ = new tgt::Trackball(canvas_, true);
trackball_->setCenter(tgt::vec3(0.f));
// init painter and connect it to the canvas
VoreenPainter* painter_ = new VoreenPainter(canvas_, trackball_);
canvas_->setPainter(painter_);
// inform the message distributor about the new painter
MsgDistr.insert(painter_);
// init trackball navigation add a listener for that
TrackballNavigation* trackNavi_ = new TrackballNavigation(trackball_, true, 0.05f, 15.f);
canvas_->getEventHandler()->addListenerToBack(trackNavi_);
// initialize the network serializer used for loading the network file
NetworkSerializer* networkSerializer_ = new NetworkSerializer();
// load processors from the network
ProcessorNetwork net = networkSerializer_->readNetworkFromFile
("../../data/networks/standard.vnw");
std::vector<Processor*> processors = net.processors;
// each processor should use the camera we created above
for (size_t i = 0 ; i < processors.size() ; ++i)
processors.at(i)->setCamera(camera_);
// initialize the network evaluator, which -among others- tests the network for errors
NetworkEvaluator* networkEvaluator_ = new NetworkEvaluator();
// give the processors to the network evaluator
networkEvaluator_->setProcessors(processors);
// create a texture container with the final rendering target "20"
TextureContainer* textureContainer_ = networkEvaluator_->initTextureContainer(20);
// inform the message distributor about the network evaluator
MsgDistr.insert(networkEvaluator_);
// tests if the network has any loops, all ports are connected and so on
networkEvaluator_->analyze();
// connect the painter with the evaluator
painter_->setEvaluator(networkEvaluator_);
// create a new transfer function object
TransFuncIntensityKeys* transferFunction_ = new TransFuncIntensityKeys();
// load the transfer function
transferFunction_->load("../../data/transferfuncs/pet_sample.tfi");
// build a texture out of the data contained in the transfer function
transferFunction_->updateTexture();
// every interested reciever should be informed that we have a new transfer function
MsgDistr.postMessage(new TransFuncPtrMsg(VolumeRenderer::setTransFunc_, transferFunction_));
// initialize a new volumeset container
VolumeSetContainer* volumeSetContainer_ = new VolumeSetContainer();
// initialize the volume serializer populator which will supply a volume serializer
// that will be able to load a wide range of volumes
VolumeSerializerPopulator* volumeSerializerPopulator_ = new VolumeSerializerPopulator();
// get the volume serializer
VolumeSerializer* volumeSerializer_ = volumeSerializerPopulator_->getVolumeSerializer();
// load the volume...
VolumeSet* volumeSet_ = volumeSerializer_->load
("../../data/nucleon.dat");
// ... and add it to the volume
volumeSetContainer_->addVolumeSet(volumeSet_);
// inform everybody about the new threshold we'd like to use
MsgDistr.postMessage(new FloatMsg(VolumeRenderer::setLowerThreshold_, threshold_));
}- Line 03+05: initialize OpenGL part of tgt and add path to the shaders used in GLUT-Example.
- Line 09: initialize the MessageDistributor as a singleton object
- Line 13-30: initialize and connect the camera, the trackball with its navigation and the painter with the canvas
- Line 34-60: Load the processor network, test it and use it
- Line 64-70: Load the transfer function and use it
- Line 74-85: Load the volume
- Line 89: Set the threshold
keyPressed() function
Hence GLUT events are translated into tgt events by tgt::GLUTCanvas we do not need do add code to callback functions like display(), reshape() , mousePressed() and mouseMotion(). We will only override the keyPressed() function, which is used when a key is pressed. GLUT Example offers you the possibility to change the lower threshold and resolution of the canvas by pressing specific keys. The changed state are then passed to the raycaster. When a key is pressed, the method keyPressed decides what to do. Is for example the button “-” or “+” pressed, then GLUT Example tells the raycaster to change the threshold. The communication to the raycaster is done via the messaging system. You just have to send a FloatMsg with the id Identifier::setLowerThreshold and the new threshold value.
Here the GLUT-Example uses only the id for the lower threshold. If you want to change the upper threshold just pass Identifier::setUpperThreshold to the FloatMsg.
Example image

keyPressed()
void keyPressed(unsigned char key, int /*x*/, int /*y*/) {
switch (key) {
case '': // = ESC
exit(0);
break;
case '+':
// increase threshold
threshold_ = ((threshold_+0.02)<(1.0))?(threshold_+0.1):(1.0);
// send the new threshold to the renderer
MsgDistr.postMessage(new FloatMsg(VolumeRenderer::setLowerThreshold_, threshold_));
// cause the renderer to rerender the scene
MsgDistr.postMessage(new Message(VoreenPainter::repaint_) , VoreenPainter::visibleViews_);
break;
case '-':
// decreasing threshold
threshold_ = ((threshold_-0.02)>(0.0))?(threshold_-0.1):(0.0);
// send the new threshold to the renderer
MsgDistr.postMessage(new FloatMsg(VolumeRenderer::setLowerThreshold_, threshold_));
// cause the renderer to rerender the scene
MsgDistr.postMessage(new Message(VoreenPainter::repaint_));
break;
case '1':
glutReshapeWindow(128,128);
break;
case '2':
glutReshapeWindow(256,256);
break;
case '3':
glutReshapeWindow(512,512);
break;
case '4':
glutReshapeWindow(1024, 1024);
break;
}
}- Line 3-5: if escape is pressed leave the program.
- Line 6-13: pressing the “+”-key increases the threshold.
- Line 14-21: pressing the “-”-key decreases the threshold.
- Line 22-33: dependent on the key, set four different resolutions.
References
[1] GLUT callbacks
[2] GLUT documentation
© VisCG WWU Münster