1.9.1. Independent volumes

An independent volume does not depend on any other volume’s data values for its rendering. If the volume primitives are all opaque, for example slice type primitives like SoVolumeSkin( C++ | Java | .NET ) or SoOrthoSlice( C++ | Java | .NET ), then no special handling is required to render multiple volumes. However volume rendering (SoVolumeRender( C++ | Java | .NET )) normally uses a transfer function (color map) that makes the volume partially transparent. Even with geometry, rendering multiple transparent objects in the same scene correctly can be complicated. If two volume rendering primitives overlap in space (volumes inter-penetrate) it requires special handling to blend the voxels correctly.

The SoVolumeGroup( C++ | Java | .NET ) node allows multiple independent volumes to be rendered correctly in the same scene. All SoVolumeRender( C++ | Java | .NET ) nodes that are under the same SoVolumeGroup( C++ | Java | .NET ) node are rendered together with correct blending, subject to some limitations. The SoVolumeRender( C++ | Java | .NET ) nodes can represent different volume data sets (see SoVolumeData( C++ | Java | .NET )) or different subvolumes (see SoROI( C++ | Java | .NET )) of the same volume data set. Other types of geometry, for example SoOrthoSlice( C++ | Java | .NET ), are not affected by SoVolumeGroup( C++ | Java | .NET ).

[Warning]

There is a significant performance decrease when multi-volume support is enabled.

The multiVolumes field (TRUE by default) controls whether the SoVolumeGroup( C++ | Java | .NET )'s special handling of SoVolumeRender( C++ | Java | .NET ) nodes is applied. If the (sub)volumes being rendered do not overlap in 3D space or only one of the volumes is transparent, then the SoVolumeGroup( C++ | Java | .NET ) special handling is probably not necessary to get the correct image and this field can be set to false. It may also be desireable to temporarily set this field to false while the user is interacting with the scene.

SoVolumeGroup( C++ | Java | .NET ) is primarily intended for cases where the volumes are sampled on different grids. For example if the volume dimension, size (3D extent) or orientation are different. For volumes that are actually multiple data sets sampled on the same grid, for example seismic attribute volumes, it may be more appropriate to blend the volumes using an SoDataCompositor( C++ | Java | .NET ) node (blending on the CPU) or an SoVolumeShader( C++ | Java | .NET ) node (blending on the GPU).

[Important]
  • Under the SoVolumeGroup( C++ | Java | .NET ) node, the SoTransferFunction( C++ | Java | .NET ) node has to be inserted after the SoVolumeData( C++ | Java | .NET ).

  • Using shaders (i.e. SoVolumeShader( C++ | Java | .NET )) with multiple volumes under an SoVolumeGroup( C++ | Java | .NET ) gives incorrect lighting results.

Example code for rendering multiple independent volumes under an SoVolumeGroup( C++ | Java | .NET ):


C++
// Create data, transfer function, ROI and render nodes for each volume
. . .

// Add the volumes to their respective separators
SoSeparator *pVolSep1 = new SoSeparator;
pVolSep1->addChild(pVolData1);
pVolSep1->addChild(pTransFunc1);
pVolSep1->addChild(pROI1);
pVolSep1->addChild(pVolRender1);

SoSeparator *pVolSep2 = new SoSeparator;
pVolSep2->addChild(pVolData2);
pVolSep2->addChild(pTransFunc2);
pVolSep2->addChild(pROI2);
pVolSep2->addChild(pVolRender2);

// Create VolumeGroup node and add the volumes under it
SoVolumeGroup *pVolGroup = new SoVolumeGroup;
pVolGroup->addChild(pVolSep1);
pVolGroup->addChild(pVolSep2);

.NET
// Create data, transfer function, ROI and render nodes for each volume
. . .
// Add the volumes to their respective separators
SoSeparator VolSep1 = new SoSeparator();
VolSep1.AddChild(VolData1);
VolSep1.AddChild(TransFunc1);
VolSep1.AddChild(ROI1);
VolSep1.AddChild(VolRender1);

SoSeparator VolSep2 = new SoSeparator();
VolSep2.AddChild(VolData2);
VolSep2.AddChild(TransFunc2);
VolSep2.AddChild(ROI2);
VolSep2.AddChild(VolRender2);

// Create VolumeGroup node and add the volumes under it
SoVolumeGroup VolGroup = new SoVolumeGroup();
VolGroup.AddChild(VolSep1);
VolGroup.AddChild(VolSep2);

Java
// Create data, transfer function, ROI and render nodes for each volume
. . .
// Add the volumes to their respective separators
SoSeparator VolSep1 = new SoSeparator();
VolSep1.addChild(VolData1);
VolSep1.addChild(TransFunc1);
VolSep1.addChild(ROI1);
VolSep1.addChild(VolRender1);

SoSeparator VolSep2 = new SoSeparator();
VolSep2.addChild(VolData2);
VolSep2.addChild(TransFunc2);
VolSep2.addChild(ROI2);
VolSep2.addChild(VolRender2);

// Create VolumeGroup node and add the volumes under it
SoVolumeGroup VolGroup = new SoVolumeGroup();
VolGroup.addChild(VolSep1);
VolGroup.addChild(VolSep2);