1.8.2. Shader framework

Framework Overview

VolumeViz applications still use the SoVertexShader SoVertexShader SoVertexShader and/or SoFragmentShader SoFragmentShader SoFragmentShader nodes to load the actual shader function source. This makes it easy to load and reload shader functions. The application can provide shader source code as a character string in memory or as a file on disk.

However instead of using the SoShaderProgram SoShaderProgram SoShaderProgram node to create a shader program, applications will normally use one of its derived classes specific to VolumeViz. For slice rendering, the SoVolumeShader SoVolumeShader SoVolumeShader node provides the basic “slots” for replacing steps in the rendering process. For volume rendering, it’s derived class SoVolumeRenderingQuality SoVolumeRenderingQuality SoVolumeRenderingQuality integrates these slots with the advanced rendering effects like lighting and edge detection . VolumeViz automatically creates some “uniform” parameters to give the shaders general information about the volume. Generally application shaders should access this information through the VolumeViz shader API functions provided because details of these parameters may change in future releases of Open Inventor. The application can pass other information to shaders using application defined uniform parameters. There is an Open Inventor node for each type of parameter supported by GLSL, for example the SoShaderParameter1f SoShaderParameter1f SoShaderParameter1f node passes a single named float value to the shader. If the application changes one of these parameters, for example the blend factor for combining two volumes, Open Inventor will automatically re-render the scene just as for any scene graph change.

Most applications will be creating fragment shaders. The fragment shader stage of the OpenGL pipeline is where most of the work is done for slice and volume rendering. This stage is responsible for computing gradients, color lookup, lighting, blending, clipping and many other things. The following shows how to load a shader function as a fragment shader:

Example 1.1. Load shader function source code

SoFragmentShader* pFragmentShader = new SoFragmentShader();
pFragmentShader->sourceProgram =  shader_filename;
SoFragmentShader FragmentShader = new SoFragmentShader();
FragmentShader.sourceProgram.Value = shader_filename;
SoFragmentShader FragmentShader = new SoFragmentShader();
FragmentShader.sourceProgram.setValue( shader_filename );


SoVolumeShader SoVolumeShader SoVolumeShader and SoVolumeRenderingQuality SoVolumeRenderingQuality SoVolumeRenderingQuality inherit the SoMFNode SoMFNode SoMFNode field shaderObject from SoShaderProgram SoShaderProgram SoShaderProgram . The application can add SoFragmentShader SoFragmentShader SoFragmentShader (and SoVertexShader SoVertexShader SoVertexShader and so on) objects to this field. VolumeViz takes care of linking all the shader functions into a shader program. There are two important differences from SoShaderProgram SoShaderProgram SoShaderProgram . First, OpenGL still requires that one of the shader functions is named “main”, but this function is automatically provided by VolumeViz, not (usually) by the application. Second, instead of being an unordered list of shader objects, as in SoShaderProgram SoShaderProgram SoShaderProgram , the VolumeViz shaderObject field is an ordered list of named “slots”. Each slot has a specific purpose in the rendering pipeline and the application can supply a replacement function by setting a fragment shader object using the name of the slot as the index. For example, to replace the computation of fragment color:

Example 1.2. Set fragment shader for slice rendering

SoVolumeShader* pVolShader = new SoVolumeShader;
pVolShader->shaderObject.set1Value(SoVolumeShader::FRAGMENT_COMPUTE_COLOR, pFragmentShader);
SoVolumeShader VolShader = new SoVolumeShader();
VolShader.shaderObject[(int)SoVolumeShader.ShaderPositions.FRAGMENT_COMPUTE_COLOR] 
    = FragmentShader;
SoVolumeShader VolShader = new SoVolumeShader();
VolShader.shaderObject.set1Value( 
    SoVolumeShader.ShaderPositions.FRAGMENT_COMPUTE_COLOR.getValue(), FragmentShader );


or

Example 1.3. Set fragment shader for volume rendering

SoVolumeRenderingQuality* pVolShader = new SoVolumeRenderingQuality;
pVolShader->forVolumeOnly = TRUE;
pVolShader->shaderObject.set1Value(SoVolumeShader::FRAGMENT_COMPUTE_COLOR, pFragmentShader)
SoVolumeRenderingQuality VolShader = new SoVolumeRenderingQuality();
VolShader.forVolumeOnly.Value = true;
VolShader.shaderObject[(int)SoVolumeShader.ShaderPositions.FRAGMENT_COMPUTE_COLOR] 
    = FragmentShader;
SoVolumeRenderingQuality VolShader = new SoVolumeRenderingQuality();
VolShader.forVolumeOnly.setValue( true );
VolShader.shaderObject.set1Value( 
    SoVolumeShader.ShaderPositions.FRAGMENT_COMPUTE_COLOR.getValue(), FragmentShader );


The shader function supplied by the application must implement the corresponding function in the VolumeViz shader framework that it is replacing. For example, the shader function set in the FRAGMENT_COMPUTE_COLOR slot must implement VVizComputeFragmentColor(). All the VolumeViz shader framework function names begin with the string “VViz”. Applications should not use this prefix except to specifically replace a VolumeViz framework function. The slots and corresponding shader framework functions are listed below. Implementing the actual GLSL code for the shader functions is discussed in the next subsection.

SoVolumeShader SoVolumeShader SoVolumeShader and SoVolumeRenderingQuality SoVolumeRenderingQuality SoVolumeRenderingQuality are property nodes that apply to volume primitives following them in the scene graph. They should be added to the scene graph after the volume data node(s), but before the rendering nodes. Note that the shaders loaded by these nodes will affect core Open Inventor rendering nodes (usually in a bad way), so normally Open Inventor geometry and VolumeViz geometry should be kept under separate SoSeparator SoSeparator SoSeparator nodes.

Shader slots

The slot names are defined in the enumeration ShaderPosition.

Slot name GLSL function name
DATA_COMBINE_FUNCTION VVizCombineData
GET_DATA_FUNCTION VVizGetData
FRAGMENT_COMPUTE_COLOR VVizComputeFragmentColor
VERTEX_POSTPROCESSING VVizVertexPostProcessin
CUSTOM_SHADER,CUSTOM_SHADER+1,... Any application function

NOTE that there are named slots for the geometry, vertex and fragment “main” functions, for example “FRAGMENT_MAIN”. However replacing the vertex or fragment "main" is not compatible with the raycasting mode for volume rendering. If you must use one of these slots for volume rendering, set the raycasting field to false. Replacing the vertex or fragment "main" is possible for slice rendering, but not recommended.

The following list explains the purpose of each named slot. An example for each slot is shown later.

  • DATA_COMBINE_FUNCTION

    This slot can be used for combining data values from multiple volumes. The specified shader must (at least) re-implement function VVizCombineData().

  • GET_DATA_FUNCTION

    This slot can be used to do “on the fly” computation on the voxel value. The specified shader must (at least) re-implement function VVizGetData().

  • FRAGMENT_COMPUTE_COLOR

    This slot can be used to compute the base color and opacity of a voxel. The specified shader must (at least) re-implement function VVizComputeFragmentColor().

  • VERTEX_POSTPROCESSING

    This slot can be used to compute and set custom “varying” parameters at the vertex shader stage for use in the fragment shader stage. The specified shader must re-implement function VVizVertexPostProcessing(). This function is called at the end of the VolumeViz vertex shader. It has no parameters and no return value. The default implementation does nothing.

  • CUSTOM_SHADER

    This slot and all following slots CUSTOM_SHADER+1, etc, are reserved for the application to supply additional shader functions to be linked into the final shader program. This can be useful, for example, if the application has a GLSL helper function that is used by multiple shaders. Instead of duplicating this source code in multiple files, put it in a separate file and load this file in one of the CUSTOM_SHADER slots. These slots are completely optional and have no predefined meaning. Applications should always use the CUSTOM_SHADER constants to set these shaders in the list, because additional predefined slot names may be added in future releases of VolumeViz.

Shader Rendering Pipeline

The following figure shows the relationship of the shader framework objects in the rendering pipeline.

Shader Rendering Pipeline

Figure 1.81. Shader Rendering Pipeline



Slice vs Volume Rendering

The forVolumeOnly field specifies if this shader program should be used for slice rendering (false) or volume rendering (true). The default is false (slice rendering). There can only be one shader node in the traversal state (i.e. traversing a shader node replaces the previous shader in the state). If the application needs a shader for slice primitives and a shader for volume rendering, the slice and volume rendering nodes (with their respective shader node) should be kept under separate SoSeparator nodes. In many cases if the same blend/computation should be applied to both slice and volume rendering, it is possible to keep the shader functions for slice and volume rendering in the same source file. This is convenient for code maintenance. However the application must still create two shader nodes, one for slices and one for volume rendering, even if they load the same source file. This is because the shader must be compiled with different macros depending on how it will be used.