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
// 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 );
// 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 );
// 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.
Example 1.12. CombineData shader
//!oiv_include <VolumeViz/vvizGetData_frag.h> //!oiv_include <VolumeViz/vvizCombineData_frag.h> uniform VVizDataSetId dataId1; uniform VVizDataSetId dataId2; VVIZ_DATATYPE VVizCombineData(vec3 tcoord) { VVIZ_DATATYPE d1 = VVizGetData(dataId1, tcoord); VVIZ_DATATYPE d2 = VVizGetData(dataId2, tcoord); return d1-d2; }
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.
Example 1.13. GetData shader
//!oiv_include <VolumeViz/vvizGetData_frag.h> VVIZ_DATATYPE VVizGetData(in VVizDataSetId dataset, vec3 coord) { vec3 voxelDim = VVizGetVoxelDimensions(dataset); vec3 du = vec3(voxelDim[0], 0., 0.); vec3 dv = vec3(0., voxelDim[1], 0.); vec3 dw = vec3(0., 0., voxelDim[2]); return 1./6.*(VVizGetRawData(dataset, coord-du).w+ VVizGetRawData(dataset, coord+du).w+ VVizGetRawData(dataset, coord-dv).w+ VVizGetRawData(dataset, coord+dv).w+ VVizGetRawData(dataset, coord-dw).w+ VVizGetRawData(dataset, coord+dw).w); }
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.
Example 1.14. ComputeFragmentColor shader (color * intensity)
//!oiv_include <VolumeViz/vvizGetData_frag.h> //!oiv_include <VolumeViz/vvizTransferFunction_frag.h> uniform VVizDataSetId data1; uniform VVizDataSetId data2; // Compute fragment color for slice primitives vec4 VVizComputeFragmentColor(VVIZ_DATATYPE vox, vec3 coord) { VVIZ_DATATYPE value1 = VVizGetData( data1, coord ); VVIZ_DATATYPE value2 = VVizGetData( data2, coord ); vec4 color = VVizTransferFunction( value2, 1 ); // Combine color and intensity color.rgb *= value1; return color; }
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.
Example 1.15. ComputeFragmentColor shader (multi-attribute co-blending)
//!oiv_include <VolumeViz/vvizGetData_frag.h> //!oiv_include <VolumeViz/vvizTransferFunction_frag.h> uniform VVizDataSetId data1; uniform VVizDataSetId data2; uniform float blendFactor; // Common blending code vec4 Blend(in vec3 texCoord) { // Get voxel value and color for each volume VVIZ_DATATYPE value1 = VVizGetData(data1, texCoord); vec4 color1 = VVizTransferFunction(value1, 0); VVIZ_DATATYPE value2 = VVizGetData(data2, texCoord); vec4 color2 = VVizTransferFunction(value2, 1); // Blend colors color2.rgb = mix( color1.rgb, color2.rgb, blendFactor ); color2.a = max( color1.a, color2.a ); return color; } // Implement VVizComputeFragmentColor for slice primitives vec4 VVizComputeFragmentColor(VVIZ_DATATYPE vox, vec3 texCoord) { return Blend(texCoord); } // Implement VVizComputeFragmentColor for SoVolumeRender nodes vec4 VVizComputeFragmentColor(VVizDataSetId data, vec3 rayDir, inout VVizVoxelInfo voxelInfoFront, VVizVoxelInfo voxelInfoBack, int maskId) { return Blend(voxelInfoFront.texCoord); }
The following image shows the result of blending multiple frequency decomposition volumes in a seismic interpretation application.