1.8.5. Examples

The following application code sets up two volumes to be combined on the GPU. In this example each volume has its own data range node, but if the volumes had the same range it would be sufficient to provide a single data range node. In this case the application is overriding the FRAGMENT_COMPUTE_COLOR slot in order to combine two volumes. It could override additional slots if necessary. The application is using SoVolumeRenderingQuality( C++ | Java | .NET ) instead of SoVolumeShader( C++ | Java | .NET ) in order to enable volume rendering effects like lighting. Note that we set the forVolumeOnly field because we are using the shader for volume rendering (for slice rendering we would not set this field).

Example 1.11. Application code to set up for volume combining


C++
// Data sets to be blended
SoVolumeData* pVolData1 = new SoVolumeData;
pVolData1->fileName  = filename1;
pVolData1->dataSetId = 1;

SoVolumeData* pVolData2 = new SoVolumeData;
pVolData2->fileName  = filename2;
pVolData2->dataSetId = 2;

SoDataRange* pRange1 = new SoDataRange;
pRange1->dataRangeId = pVolData1->dataSetId.getValue();

SoDataRange* pRange2 = new SoDataRange;
pRange2->dataRangeId = pVolData2->dataSetId.getValue();

SoTransferFunction* pTF1 = new SoTransferFunction;
pTF1->predefColorMap     = SoTransferFunction::INTENSITY;
pTF1->transferFunctionId = 1;

SoTransferFunction* pTF2 = new SoTransferFunction;
pTF2->predefColorMap     = SoTransferFunction::BLUE_WHITE_RED;
pTF2->transferFunctionId = 2;

// Base material for rendering
SoMaterial* pMaterial  = new SoMaterial;
pMaterial->diffuseColor.setValue( 1, 1, 1 );

// Load the fragment shader code
SoFragmentShader* pFragmentShader = new SoFragmentShader;
pFragmentShader->sourceProgram.setValue( SHADER_FILENAME );

// Set the shader parameters (data set ids).
pFragmentShader->addShaderParameter1i( "data1", 1 );
pFragmentShader->addShaderParameter1i( "data2", 2 );

// Volume rendering options + custom shader
SoVolumeRenderingQuality* pVolQual = new SoVolumeRenderingQuality;
pVolQual->preIntegrated = TRUE;
pVolQual->lighting      = TRUE;
pVolQual->lightingModel = SoVolumeRenderingQuality::OPENGL;
pVolQual->forVolumeOnly = TRUE;  // Apply shader to volume rendering
pVolQual->shaderObject.set1Value( SoVolumeShader::FRAGMENT_COMPUTE_COLOR, pFragmentShader );

SoVolumeRender *pVolRend = new SoVolumeRender;

// Build scene graph
SoSeparator* pRoot = new SoSeparator;
  SoMultiDataSeparator* pVolSep = new SoMultiDataSeparator;
  pVolSep ->addChild( pVolData1 );
  pVolSep ->addChild( pVolData2 );
  pVolSep ->addChild( pRange1 );
  pVolSep ->addChild( pRange2 );
  pVolSep ->addChild( pTF1 );
  pVolSep ->addChild( pTF2 );
  pVolSep ->addChild( pMaterial );
  pVolSep ->addChild( pVolQual );
  pVolSep ->addChild( pVolRend );
pRoot->addChild( pVolSep );

.NET
// Data sets to be blended
SoVolumeData VolData1 = new SoVolumeData();
VolData1.fileName.Value  = filename1;
VolData1.dataSetId.Value = 1;

SoVolumeData VolData2 = new SoVolumeData();
VolData2.fileName.Value  = filename2;
VolData2.dataSetId.Value = 2;

SoDataRange Range1 = new SoDataRange();
Range1.dataRangeId.Value = VolData1.dataSetId.Value;

SoDataRange Range2 = new SoDataRange();
Range2.dataRangeId.Value = VolData2.dataSetId.Value;

SoTransferFunction TF1 = new SoTransferFunction();
TF1.predefColorMap.Value     = SoTransferFunction.PredefColorMaps.INTENSITY;
TF1.transferFunctionId.Value = 0;

SoTransferFunction TF2 = new SoTransferFunction();
TF2.predefColorMap.Value     = SoTransferFunction.PredefColorMaps.BLUE_WHITE_RED;
TF2.transferFunctionId.Value = 1;

// Base material for rendering
SoMaterial Material  = new SoMaterial();
Material.diffuseColor.SetValue( 1, 1, 1 );

// Load the fragment shader code
SoFragmentShader FragmentShader = new SoFragmentShader();
FragmentShader.sourceProgram.Value = shader_filename;

// Set the shader parameters (data set ids).
SoShaderParameter1i Param1 = new SoShaderParameter1i();
Param1.name.Value  = "data1";
Param1.value.Value = 1;
SoShaderParameter1i Param2 = new SoShaderParameter1i();
Param2.name.Value  = "data2";
Param2.value.Value = 2;
FragmentShader.parameter[0] = Param1;
FragmentShader.parameter[1] = Param2;

// Volume rendering options + custom shader
SoVolumeRenderingQuality VolQual = new SoVolumeRenderingQuality();
VolQual.preIntegrated.Value = true;
VolQual.lighting.Value      = true;
VolQual.lightingModel.Value = SoVolumeRenderingQuality.LightingModels.OPENGL;
VolQual.forVolumeOnly.Value = true;
VolQual.shaderObject[(int)SoVolumeShader.ShaderPositions.FRAGMENT_COMPUTE_COLOR] = FragmentShader;

SoVolumeRender VolRend = new SoVolumeRender();

// Build scene graph
SoSeparator Root = new SoSeparator();
    SoMultiDataSeparator VolSep = new SoMultiDataSeparator();
    VolSep.AddChild( VolData1 );
    VolSep.AddChild( VolData2 );
    VolSep.AddChild( Range1 );
    VolSep.AddChild( Range2 );
    VolSep.AddChild( TF1 );
    VolSep.AddChild( TF2 );
    VolSep.AddChild( Material );
    VolSep.AddChild( VolQual );
    VolSep.AddChild( VolRend );
Root.AddChild( VolSep );

Java
// Data sets to be blended
SoVolumeData VolData1 = new SoVolumeData();
VolData1.fileName.setValue( filename1 );
VolData1.dataSetId.setValue( 1 );

SoVolumeData VolData2 = new SoVolumeData();
VolData2.fileName.setValue( filename2 );
VolData2.dataSetId.setValue( 2 );

SoDataRange Range1 = new SoDataRange();
Range1.dataRangeId.setValue( VolData1.dataSetId.getValue() );

SoDataRange Range2 = new SoDataRange();
Range2.dataRangeId.setValue( VolData2.dataSetId.getValue() );

SoTransferFunction TF1 = new SoTransferFunction();
TF1.predefColorMap.setValue( SoTransferFunction.PredefColorMaps.INTENSITY );
TF1.transferFunctionId.setValue( 0 );

SoTransferFunction TF2 = new SoTransferFunction();
TF2.predefColorMap.setValue( SoTransferFunction.PredefColorMaps.BLUE_WHITE_RED );
TF2.transferFunctionId.setValue( 1 );

// Base material for rendering
SoMaterial Material  = new SoMaterial();
Material.diffuseColor.setValue( 1, 1, 1 );

// Load the fragment shader code
SoFragmentShader FragmentShader = new SoFragmentShader();
FragmentShader.sourceProgram.setValue( shader_filename );

// Set the shader parameters (data set ids).
SoShaderParameter1i Param1 = new SoShaderParameter1i();
Param1.name.setValue( "data1" );
Param1.value.setValue( 1 );
SoShaderParameter1i Param2 = new SoShaderParameter1i();
Param2.name.setValue( "data2" );
Param2.value.setValue( 2 );
FragmentShader.parameter.addShaderParameter( Param1 );
FragmentShader.parameter.addShaderParameter( Param2 );

// Volume rendering options + custom shader
SoVolumeRenderingQuality VolQual = new SoVolumeRenderingQuality();
VolQual.preIntegrated.setValue( true );
VolQual.lighting.setValue( true );
VolQual.lightingModel.setValue( SoVolumeRenderingQuality.LightingModels.OPENGL );
VolQual.forVolumeOnly.setValue( true );
VolQual.shaderObject.set1Value( 
    SoVolumeShader.ShaderPositions.FRAGMENT_COMPUTE_COLOR.getValue(), FragmentShader );

SoVolumeRender VolRend = new SoVolumeRender();

// Build scene graph
SoSeparator Root = new SoSeparator();
      SoMultiDataSeparator VolSep = new SoMultiDataSeparator();
      VolSep.addChild( VolData1 );
      VolSep.addChild( VolData2 );
      VolSep.addChild( Range1 );
      VolSep.addChild( Range2 );
      VolSep.addChild( TF1 );
      VolSep.addChild( TF2 );
      VolSep.addChild( Material );
      VolSep.addChild( VolQual );
      VolSep.addChild( VolRend );
Root.addChild( VolSep );

The following example shader re-implements the VVizCombineData() function (DATA_COMBINE_FUNCTION slot). The data set ids of the two volumes have been defined by the application as uniform variables dataId1 and dataId2 (as shown in the application example above). The combine function fetches a value from each volume using the specified position in normalized volume coordinates. Finally it returns the difference between the two values.


The following example shader re-implements the VVizGetData() function (GET_DATA_FUNCTION slot). This version applies a smoothing filter to remove noise in the data set. First it gets the size of a voxel for this data set using the VVizGetVoxelDimensions() function and constructs offset vectors one voxel long in each dimension. Then it uses the voxel coordinate, plus or minus the offset, to fetch the values of six neighboring voxels and return the average value. In case no uniform parameters are needed because only one volume is being rendered. Uniform parameters could still be useful in this kind of shader, for example to control parameters of the filter.


The following example shader re-implements the VVizComputeFragmentColor() function (FRAGMENT_COMPUTE_COLOR slot). In this case the goal is to visualize two different data sets at the same time using color to represent one value and brightness to represent another. This might be useful, for example, to visualize seismic amplitude and velocity together in a seismic application. The shader treats the first volume as intensity values, does a color lookup for the second volume then modifies the color based on the intensity. As in the CombineData example, the application provides the data set ids as uniform parameters. This shader can only be used for rendering slice primitives, but could be extended as shown in the next example.


The following example re-implements the VVizComputeFragmentColor() function (FRAGMENT_COMPUTE_COLOR slot) to blend the colors from two volumes based on a blendFactor set by the application in a uniform parameter. Typically the application would provide a slider that allows the user to change the blending factor. In this case we do the color lookup for both volumes and the GLSL built-in function "mix" to combine the RGB values according to the blend factor. There are several valid algorithms for combining the alpha values. In fact, if we know the opacity curves are the same for both volumes we could just use either alpha value. This example is also interesting because it shows one way to implement a shader for both slice and volume rendering in the same source file.


The following image shows the result of blending multiple frequency decomposition volumes in a seismic interpretation application.