Manage Custom Nodes with OIV 10


Introduction

Open Inventor render engine

Starting with Open Inventor 10, all graphics API calls are executed by a render engine, a software layer between hardware and Open Inventor public API (see Figure 1.1). This render engine has changed the product design, as the Open Inventor public API is now independent of any graphics API. This paradigm has many advantages, including, maintenance, optimizations and future graphics API support. On the other hand, new concepts introduced in Open Inventor 10 involve changes in user code, especially for custom nodes which require strict guidelines comparing to previous versions.

Figure 1.1: Open Inventor simplifed architecture. Open Inventor public API is the higher layer which represents the user API. The render engine is the intermediate layer between Open Inventor and graphics API which contains a specialized backend layer for communication with graphics API, here the OpenGL API.

 

        This design allows Open Inventor to work asynchronously. Actually, the scene graph traversal and the execution of rendering calls are performed in separate threads (i.e., the render engine runs in its own thread). This means that scene graph traversal and rendering execution can run at the same time, each in its own thread. As shown on Figure 1.2, during a SoGLRenderAction, the scene graph traversal consists of generating a set of commands which represents the description of the scene (e.g., material definition, shapes, and so on). Note that during the graph traversal, there is no call to any graphics API. In the same way, commands have no dependencies on a graphics API. Then, these commands are sent to the render engine for executing the rendering calls, which can be run asynchronously.

Figure 1.2: SoGLRenderAction flow chart. First, the function apply is called on a scene graph which leads to a scene graph traversal. During the traversal, a set of commands is setup that represents an abstraction of the rendering execution for the render engine. Finally, commands are submitted to the render engine in order to be transformed into rendering calls.


Custom Nodes

Introduced with Open Inventor 10, each node contains a SbRenderEngineMode static class member which defines how a node is executed by the Open Inventor render engine. The render mode, of type SoNode::RenderMode, should be defined with one of the following values:

  • OIV_OPENINVENTOR_RENDERING, for custom nodes that use only Open Inventor API (see Section 2).
  • OIV_OPENGL_COMPATIBILITY_RENDERING, for custom nodes that work in OpenGL compatibility profile.
  • OIV_OPENGL_CORE_RENDERING, for custom nodes that work in OpenGL core profile.

        Please refer to Chapters Pure Open Inventor custom nodes and OpenGL custom nodes for details of what the choice of a render mode involves.

        Accessible via the getClassRenderEngineMode static function, the render mode must be initialized during the static class initialization (i.e., initClass function) such as shown in Listing 1.1.

 

void
CustomNode
::initClass()
{
    getClassRenderEngineMode().setRenderMode(OIV_OPENINVENTOR_RENDERING);
    SO_NODE_INIT_CLASS(CustomNode, "CustomNode", SoNode );
}

Listing 1.1: Class initialization function for a custom node, named CustomNode inherited from SoNode which uses only Open Inventor API.

 

        According to the architecture of Open Inventor 10 (see Open Inventor render engine), these rendering modes are mandatory to know how to execute a node. Actually, nodes from the Open Inventor public API generate render engine commands at shape level during scene graph traversal. Custom nodes with a render mode set to OIV_OPENINVENTOR_RENDERING indirectly benefit from the capability of Open Inventor nodes to generate render engine commands.

        However, custom nodes that need to call low-level graphics API functions directly must be threated differently. Indeed, Open Inventor public API and graphics API do not work on the same layer (see Figure 1.1). This means that these custom nodes must be executed at the render engine level in order to communicate with graphics API. Thus, when render mode is set to OIV_OPENGL_COMPATIBILITY_RENDERING or OIV_OPENGL_CORE_RENDERING, custom nodes are executed at the render engine level in order to access to graphics API functions. The most important consequence of this behavior is that Open Inventor and low-level graphics API calls cannot be mixed in the same custom node since these APIs do not work on the same layer. Implementation details, limitations and guidelines for all kind of custom nodes are defined in the remainder of this documentation.

 


Pure Open Inventor custom nodes

A pure Open Inventor custom node implements its custom actions -- including rendering -- using a set of other nodes. It presents an abstraction that hides the traversal of those nodes from the user. The initialization of a custom node is shown in Listing 1.1.

       The recommended usage is to build a scene graph that corresponds to the expected rendering result. This scene graph will be traversed during a SoAction using the forwardTraversal function, for instance during the render action:

 

void
CustomNode::GLRender(SoGLRenderAction* action)
{
    // supposed m_sceneGraph is a group, previously initialized
    action->forwardTraversal(m_sceneGraph);
}

 

       One of the main advantages of using the forwardTraversal function instead of calling the GLRender fonction of the embedded scene graph is that paths are preserved. Paths (i.e., SoPath) are a key point for Open Inventor scene graph management (e.g., picking, cache and so on) and thus, it's mandatory to use this function for traversing a scene graph. Calling the GLRendere function of a node may lead to unexpected results.


OpenGL custom nodes

Guidelines

Common principles

A custom node that makes OpenGL calls must set its render mode to

  • OIV_OPENGL_COMPATIBILITY_RENDERING or,
  • OIV_OPENGL_CORE_RENDERING

for using OpenGL calls. These calls are allowed only in the custom node class implementation of the virtual SoNode::GLRender function. The effect of OpenGL calls outside this function is undefined. The GLRender function of such nodes is not executed during the SoGLRenderAction traversal; its execution is postponed later for an execution by the Open Inventor render engine, as explained in Section Custom nodes. This imposes some guidelines and limitations when you consider using OpenGL API.

     Mixing OpenGL and Open Inventor API calls is not allowed. This means that the GLRender function of an OpenGL custom node must contain only OpenGL calls. However, Open Inventor base types (e.g., SbVec3f, SbColor) can be used as well as queries of Open Inventor state elements. The Open Inventor state (i.e., SoState) is available via the SoGLRenderAction and can be queried to determine the current graphics configuration. During the execution of such node, the OpenGL state is initialized according to the Open Inventor state (see Section A Open Inventor state against OpenGL state for more details). This allows one to determine the rendering state bye querying the Open Inventor state instead of the OpenGL state, which would require using the expensive glGet functions.

     The execution of a custom node is self-contained. As a result, this kind of nodes is executed using its own render action and thus, its own Open Inventor state. In this way, modification of the Open Inventor state is only valid within the GLRender function but has no effect on the rendering state. The OpenGL state is set up according to the Open Inventor state before the traversal of this node and restored after the execution of the GLRender function. So, a change of state has no effect outside the node, both for Open Inventor and OpenGL states. Note that the ability to run OpenGL calls has a cost, and the execution of such nodes may be time consuming.

     OpenGL version 4.1 is the minimum requirement for Open Inventor 10. All functionnalities defined by this version of OpenGL are available in an OpenGL custom node. Extensions and features from higher versions are available if the hardware used for your application supports them.

OpenGL render modes

Depending on the render mode, the setup of an OpenGL custom node should fulfill OpenGL specification for compatibility and core profiles. The specifications for the two possible render modes are described in the following.

 

OIV_OPENGL_COMPATIBILITY_RENDERING
In this compatibility mode, the behavior for OpenGL is the one defined by OpenGL specification version 4.1 compatibility profile. In this mode, Open Inventor initializes fixed-function vertex transformations, and fixed-function vertex lighting, and color properties according the Open Inventor state. In this way, no shader is requiered for rendering but, if a user-defined GLSL shader is set on the state, built-in language variables are accessible. Note that the Open Inventor shader API is also available. It is recommended to use this API to be independent from the render mode.

     This compatibility mode is time-consuming since both fixed-function properties and Open Inventor shader API are initialized. However, this rendering mode facilitates porting an existing application from OpenGL to Open Inventor.

 

OIV_OPENGL_CORE_RENDERING
The OpenGL core rendering mode behavior is described by OpenGL specification version 4.1 core profile. This mode is qualified as a full shader pipeline since fixed-function helpers are no longer available. Therefore, a user-defined shader is mandatory when using this render mode. The Open Inventor shader API covers all the fixed-function OpenGL state supported by Open Inventor.

     Note that only core profile OpenGL calls can be used here. This mode is mandatory if you plan to use OpenGL custom nodes on Mac OS.


Examples of OpenGL Open Inventor custom node

OpenGL compatibility profile

The header file of an OpenGL compatibility custom node is shown in Listing 3.1. In this example, a node, inherited from SoShape, is defined with appropriate function definitions. This header is similar to the header of a classic custom node, except for the OpenGL types defined in private members.

#pragma once
#include <Inventor/nodes/SoShape.h>
#include <Inventor/sys/SoGL.h>

/**
* @brief Custom OpenGL (OpenGL 2) node
*/
class CustomNodeOpenGLCompatibility : public SoShape
{
    SO_NODE_HEADER(CustomNodeOpenGLCompatibility );

public :
    /**
    * Default constructor
    */
    CustomNodeOpenGLCompatibility ();

protected :
    // destructor
    virtual ~ CustomNodeOpenGLCompatibility ();

    // not implemented here
    void generatePrimitives(SoAction* /*action*/) {}

    // create OpenGL resources
    void initialize();

    // destroy OpenGL resources
    void destroy();

SoEXTENDER public :

    // Rendering stuff
    virtual void GLRender ( SoGLRenderAction* action );

    // BBox computation
    virtual void computeBBox(SoAction* action , SbBox3f &box , SbVec3f& center);

SoINTERNAL public :

    static void initClass ();
    static void exitClass ();

private :
    bool m_initialized ;
    GLuint m_vertexBuffer ;
    GLuint m_colorBuffer ;
    GLuint m_normalBuffer ;
};

Listing 3.1: Header file for OpenGL compatibility custom node.

 

       The implementation first defines the render mode during the class initialization as shown in Listing 3.2.

void
CustomNodeOpenGLCompatibility::initClass()
{
    getClassRenderEngineMode().setRenderMode(SbRenderEngineMode::
      OIV_OPENGL_COMPATIBILITY_RENDERING );
    SO__NODE_INIT_CLASS(CustomNodeOpenGLCompatibility,
      "CustomNodeOpenGLCompatibility", SoShape );
}

Listing 3.2: Class initialization function for OpenGL compatibility custom node.

 

       The GLRender function of this node uses OpenGL functions as shown in Listing 3.3. Note that, here, the state from the SoGLRenderAction is queried for conditionnal rendering based on lighting properties.

void
CustomNodeOpenGLCompatibility::GLRender(SoGLRenderAction* action)
{
    // create resources
    initialize();
    
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    
    SoState* state = action->getState();
    SoLightModelElement::Model model = SoLightModelElement::get(state);
    
    if (model == SoLightModelElement::PHONG || model == SoLightModelElement::PER_VERTEX_PHONG)
    {
        glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
        glColorMaterial(GL_FRONT_AND_BACK , GL_DIFFUSE);
        glEnable(GL_COLOR_MATERIAL);
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(3, GL_FLOAT , 0, 0);
    
        glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT , 0, 0);
    }
    glDrawArrays ( GL_TRIANGLES , 0, numVertices );
    
    if (model == SoLightModelElement::PHONG || model == SoLightModelElement::PER_VERTEX_PHONG)
    {
        glDisable(GL_COLOR_MATERIAL);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
    }
    glDisableClientState(GL_VERTEX_ARRAY);
}

Listing 3.3: GLRender function for OpenGL compatibility custom node.

 

       The complete source code for this node is available in Appendix B.

OpenGL core profile

The header file of an OpenGL core custom node is described in Listing 3.4. In this example, a node, inherited from SoShape, is defined with appropriate function definitions. This header is similar to the header of a classic custom node, except for the OpenGL types defined in private members.

#pragma once
#include <Inventor/nodes/SoShaderProgram.h>
#include <Inventor/nodes/SoShape.h>
#include <Inventor/sys/SoGL.h>

/**
* @brief Custom OpenGL (OpenGL 4 Core) node
*/
class CustomNodeOpenGLCore : public SoShape
{
    SO_NODE_HEADER(CustomNodeOpenGLCore);

public :
    /**
    * Default constructor
    */
    CustomNodeOpenGLCore();

protected :
    // destructor
    virtual ~CustomNodeOpenGLCore();

    // not implemented here
    void generatePrimitives(SoAction* /*action*/) {}

    // create OpenGL resources
    void initialize();

    // destroy OpenGL resources
    void destroy();

SoEXTENDER public :

    // Rendering stuff
    virtual void GLRender (SoGLRenderAction* action);

    // BBox computation
    virtual void computeBBox(SoAction* action , SbBox3f &box , SbVec3f& center);

SoINTERNAL public :

    static void initClass();
    static void exitClass();

private :
    bool m_initialized;
    GLuint m_vertexBuffer;
    GLuint m_colorBuffer;
    GLuint m_normalBuffer;
};

Listing 3.4: Header file for OpenGL core custom node.

 

       The implementation first defines the render mode during the class initialization as shown in Listing 3.5.

void
CustomNodeOpenGLCore::initClass()
{
    getClassRenderEngineMode().setRenderMode(SbRenderEngineMode::
      OIV_OPENGL_CORE_RENDERING);
    SO__NODE_INIT_CLASS(CustomNodeOpenGLCore,
      "CustomNodeOpenGLCore", SoShape );
}

Listing 3.5: Class initialization function for OpenGL core custom node.

 

       The GLRender function of this node uses OpenGL functions as shown in Listing 3.6. Here, the functions to manage attributes are part of the core profile instead of the legacy ones in the compatibility examples (see Listing 3.3).

void
CustomNodeOpenGLCore::GLRender(SoGLRenderAction* action)
{
    // create resources
    initialize();
    
    glEnableClientState(0);
    glEnableClientState(1);
    glEnableClientState(2);
    
    // vertex positions
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer );
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
    // vertex colors
    glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
    // vertex normals
    glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);
    glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
    
    glDrawArrays(GL_TRIANGLES, 0, numVertices);
    
    glDisableClientState(2);
    glDisableClientState(1);
    glDisableClientState(0);
}

Listing 3.6: GLRender function for OpenGL core custom node.

 

       Note that this kind of node needs a shader program during rendering. Therefore an SoShaderProgram node must lie in the scene graph before this node. The vertex shader header can specify the layout location of the attributes (i.e., positions, colors and normals) as shown in Listing 3.7 to avoid link issues with the underlying GLSL program.

layout(location = 0) in vec3 position; layout(location = 1) in vec3 color; layout(location = 2) in vec3 normal;

Listing 3.7: GLSL vertex shader attribute declarations for OpenGL core custom node example.

 

       The complete C++ and shader source code for this node is available in Appendix C.

SoGLCallback

The SoGLCallback class provides another way of making OpenGL calls, using an interface similar to SoCallback. However, it has a significant limitation: the type of the render mode must be OIV_OPENGL_COMPATIBILITY_RENDERING. The prototype of the callback function is the same as SoCallback's (see Listing 3.8). The main difference between SoGLCallback and SoCallback classes is that SoGLRenderAction is the only action that can traverse an SoCallback object.

void
CustomNodeGLCallback::callback(void* data, SoAction* action)
{
    CustomNodeGLCallback* cb = static_cast<CustomNodeGLCallback*>(data);
    // create resources
    cb->initialize();
    
    glBindBuffer(GL_ARRAY_BUFFER, cb->m_vertexBuffer);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    
    SoState* state = action->getState();
    SoLightModelElement::Model model = SoLightModelElement::get(state);
    
    if (model == SoLightModelElement::PHONG || model == SoLightModelElement::PER_VERTEX_PHONG)
    {
        glBindBuffer(GL_ARRAY_BUFFER, cb->m_colorBuffer);
        glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
        glEnable(GL_COLOR_MATERIAL);
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(3, GL_FLOAT, 0, 0);
        
        glBindBuffer(GL_ARRAY_BUFFER, cb->m_normalBuffer);
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT, 0, 0);
    }
    
    glDrawArrays(GL_TRIANGLES, 0, numVertices);
    
    if (model == SoLightModelElement::PHONG || model == SoLightModelElement::PER_VERTEX_PHONG)
    {
        glDisable(GL_COLOR_MATERIAL);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
    }
    glDisableClientState(GL_VERTEX_ARRAY);
}

Listing 3.8: Callback function for SoGLCallback.

 

       Using a custom node instead of an SoGLCallback  is recommended in new code. This gives better control of all node behavior in addition to rendering, such as bounding box computation, ray picking, etc. The complete source code is available in Appendix D.


Appendices

Appendix A: A Open Inventor state against OpenGL state

Categories Inventor elements OpenGL related functions
Rasterizer SoDrawStyleElement
SoShapeHintsElement

SoLineWidthElement
SoPointSizeElement
SoPolygonOffsetElement
SoLinePatternElement
 
glPolygonMode
glCullFace
glFrontFace
glLineWidth
glPointSize
glPolygonOffset
glLineStipple
 
Viewport and scissor SoUpdateAreaElement
SoViewportRegionElement
glScissor
glViewport
 
Blending and color SoBlendElement


SoColorMaskElement
SoLogicOperationElement
glBlendEquation
glBlendFunc
glBlendColor
glColorMask
glLogicOp
 
Depth/stencil buffer SoDepthBufferElement

SoStencilBufferElement
 
glDepthMask
glDepthFunc
glStencilOpSeparate
glStencilMaskSeparate
glStencilMaskSeparate
 
Transform SoProjectionMatrixElement
SoViewMatrixElement
SoModelMatrixElement
SoTextureMatrixElement

So*MatrixElement
 
glMatrixMode -> GL PROJECTION
glMatrixMode -> GL MODELVIEW
glMatrixMode -> GL MODELVIEW
glActiveTexture
glMatrixMode -> GL TEXTURE
glLoadMatrixf
 
Material & Light SoMaterialElement

SoLightModelElement

SoLightElement
SoEnvironmentElement
 
glShadeModel
glMaterial
glLight
glLightModel
glLightModel
glLightModel (-> ambient)
 
Texture SoTextureImageElement



SoTexGenElement
 
glActiveTexture
glTexEnv
glBindTexture
glGenerateMipmap
glActiveTexture
glTexGen
 
Misc SoClipPlaneElement
SoAlphaTestElement
 
glAlphaFunc
glClipPlane
 

Table A.2: OpenGL shader state for OIV_OPENGL_COMPATIBILITY_RENDERING using legacy OpenGL functions.

 

Appendix B: OpenGL compatibility custom node source code example

Listing B.1: Header fi le for OpenGL compatibility custom node.

#pragma once
#include <Inventor/nodes/SoShape.h>
#include <Inventor/sys/SoGL.h>

/**
* @brief Custom OpenGL (OpenGL 2) node
*/
class CustomNodeOpenGLCompatibility : public SoShape
{
    SO_NODE_HEADER(CustomNodeOpenGLCompatibility );

public :
    /**
    * Default constructor
    */
    CustomNodeOpenGLCompatibility ();

protected :
    // destructor
    virtual ~ CustomNodeOpenGLCompatibility ();

    // not implemented here
    void generatePrimitives(SoAction* /*action*/) {}

    // create OpenGL resources
    void initialize();

    // destroy OpenGL resources
    void destroy();

SoEXTENDER public :

    // Rendering stuff
    virtual void GLRender ( SoGLRenderAction * action );

    // BBox computation
    virtual void computeBBox(SoAction* action , SbBox3f &box , SbVec3f& center);

SoINTERNAL public :

    static void initClass ();
    static void exitClass ();

private :
    bool m_initialized ;
    GLuint m_vertexBuffer ;
    GLuint m_colorBuffer ;
    GLuint m_normalBuffer ;
};

 

Listing B.2: Implementation file for OpenGL compatibility custom node.

#include "CustomNodeOpenGLCompatibility.h"
#include <Inventor/elements/SoLightModelElement.h>
#include <Inventor/actions/SoGLRenderAction.h>

// external stuff for data access
extern uint32_t numVertices;
extern float vertex [];
extern float normal [];
extern float color [];
SO_NODE_SOURCE(CustomNodeOpenGLCompatibility);

CustomNodeOpenGLCompatibility::CustomNodeOpenGLCompatibility()
: m_initialized (false)
{
    SO_NODE_CONSTRUCTOR(CustomNodeOpenGLCompatibility);
}

CustomNodeOpenGLCompatibility::~CustomNodeOpenGLCompatibility()
{
    destroy();
}

void
CustomNodeOpenGLCompatibility::initClass()
{
    getClassRenderEngineMode().setRenderMode(
        SbRenderEngineMode::OIV_OPENGL_COMPATIBILITY_RENDERING);
    SO__NODE_INIT_CLASS (CustomNodeOpenGLCompatibility,
        "CustomNodeOpenGLCompatibility", SoShape);
}

void
CustomNodeOpenGLCompatibility::exitClass()
{
    SO__NODE_EXIT_CLASS (CustomNodeOpenGLCompatibility);
}

void
CustomNodeOpenGLCompatibility::initialize()
{
    if (m_initialized)
    {
        return;
    }
    glGenBuffers(1, &m_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &vertex [0], GL_STATIC_DRAW);
    glGenBuffers(1, &m_colorBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &color [0], GL_STATIC_DRAW);
    glGenBuffers(1, &m_normalBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &normal [0], GL_STATIC_DRAW);
    m_initialized = true;
}

void
CustomNodeOpenGLCompatibility::destroy ()
{
    if (! m_initialized)
        return ;
    glDeleteBuffers(1, &m_vertexBuffer);
    glDeleteBuffers(1, &m_normalBuffer);
    glDeleteBuffers(1, &m_colorBuffer);
    m_initialized = false;
}

void
CustomNodeOpenGLCompatibility::GLRender(SoGLRenderAction* action)
{
    // create resources
    initialize();
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    SoState* state = action->getState();
    SoLightModelElement::Model model = SoLightModelElement::get(state);
    if (model == SoLightModelElement::PHONG || model == SoLightModelElement :: PER_VERTEX_PHONG)
    {
        glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
        glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
        glEnable(GL_COLOR_MATERIAL);
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(3, GL_FLOAT, 0, 0);
        glBindBuffer(GL_ARRAY_BUFFER , m_normalBuffer);
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT, 0, 0);
    }
    glDrawArrays(GL_TRIANGLES , 0, numVertices);
    if (model == SoLightModelElement :: PHONG || model == SoLightModelElement::PER_VERTEX_PHONG)
    {
        glDisable(GL_COLOR_MATERIAL);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
    }
    glDisableClientState(GL_VERTEX_ARRAY);
}

void
CustomNodeOpenGLCompatibility::computeBBox(SoAction* action, SbBox3f &box, SbVec3f ¢er)
{
    SbBox3f bbox ;
    float* vert=vertex ;
    for (uint32_t i = 0; i < numVertices; ++i)
    {
        bbox.extendBy(SbVec3f(vert[0], vert[1], vert [2]));
        vert += 3;
    }
    box = bbox ;
    center = box.getCenter();
}

 

Appendix C: OpenGL core custom node source code example

Listing C.1: Header file for OpenGL core custom node.

#pragma once
#include <Inventor/nodes/SoShaderProgram.h>
#include <Inventor/nodes/SoShape.h>
#include <Inventor/sys/SoGL.h>

/**
* @brief Custom OpenGL (OpenGL 4 core) node
*/
class CustomNodeOpenGLCore : public SoShape
{
    SO_NODE_HEADER(CustomNodeOpenGLCore);
public:
    /**
    * Default constructor.
    */
    CustomNodeOpenGLCore();

protected:
    // destructor
    virtual ~CustomNodeOpenGLCore();
    // not implemented here
    void generatePrimitives(SoAction* /* action */){}
    // create OpenGL resources
    void initialize();
    // destroy OpenGL resources
    void destroy();

SoEXTENDER public:
    // Rendering stuff
    virtual void GLRender(SoGLRenderAction* action);
    // BBox computation
    virtual void computeBBox(SoAction* action, SbBox3f &box, SbVec3f& center);

SoINTERNAL public:
    static void initClass();
    static void exitClass();

private:
    bool m_initialized;
    GLuint m_vertexBuffer;
    GLuint m_colorBuffer;
    GLuint m_normalBuffer;
};

 

Listing C.2: Implementation file for OpenGL core custom node.

#include "CustomNodeOpenGLCore.h"
#include <Inventor/elements/SoLightModelElement.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/sys/SoGL.h>

// external stuff for data access
extern uint32_t numVertices;
extern float vertex [];
extern float normal [];
extern float color [];
SO_NODE_SOURCE(CustomNodeOpenGLCore);

CustomNodeOpenGLCore::CustomNodeOpenGLCore()
: m_initialized (false)
{
    SO_NODE_CONSTRUCTOR(CustomNodeOpenGLCore);
}

CustomNodeOpenGLCore::~CustomNodeOpenGLCore()
{
    destroy();
}

void
CustomNodeOpenGLCore::initClass()
{
    getClassRenderEngineMode().setRenderMode(
        SbRenderEngineMode::OIV_OPENGL_CORE_RENDERING);
    SO__NODE_INIT_CLASS (CustomNodeOpenGLCore,
        "CustomNodeOpenGLCore", SoShape);
}

void
CustomNodeOpenGLCore::exitClass()
{
    SO__NODE_EXIT_CLASS (CustomNodeOpenGLCore);
}

void
CustomNodeOpenGLCore::initialize()
{
    if (m_initialized)
    {
        return;
    }
    glGenBuffers(1, &m_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &vertex [0], GL_STATIC_DRAW);
    glGenBuffers(1, &m_colorBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &color [0], GL_STATIC_DRAW);
    glGenBuffers(1, &m_normalBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &normal [0], GL_STATIC_DRAW);
    m_initialized = true;
}

void
CustomNodeOpenGLCore::destroy ()
{
    if (! m_initialized)
        return ;
    glDeleteBuffers(1, &m_vertexBuffer);
    glDeleteBuffers(1, &m_normalBuffer);
    glDeleteBuffers(1, &m_colorBuffer);
    m_initialized = false;
}

void
CustomNodeOpenGLCore::GLRender(SoGLRenderAction* action)
{
    // create resources
    initialize();
    glEnableClientState (0);
    glEnableClientState (1);
    glEnableClientState (2);

    // vertex positions
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    // vertex colors
    glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, 0);
    // vertex normals
    glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);     glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glDrawArrays(GL_TRIANGLES, 0, numVertices);

    glDisableClientState(2);
    glDisableClientState(1);
    glDisableClientState(0);
}

void
CustomNodeOpenGLCore::computeBBox(SoAction* action, SbBox3f &box, SbVec3f ¢er)
{
    SbBox3f bbox ;
    float* vert=vertex ;
    for (uint32_t i = 0; i < numVertices; ++i)
    {
        bbox.extendBy(SbVec3f(vert[0], vert[1], vert [2]));
        vert += 3;
    }
    box = bbox ;
    center = box.getCenter();
}

 

Listing C.3: GLSL vertex shader for OpenGL core custom node.

# version 410 core

//! oiv_include <Inventor/oivShaderState.h>

layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 2) in vec3 normal;

out vec3 normal;
out vec4 color;

void
main ()
{
    normal = OivNormalMatrix() * normal;
    color = vec4(color, 1.0);
    gl_Position = OivModelViewProjectionMatrix() * vec4(position, 1.0);
}

 

Listing C.4: GLSL fragmant shader for OpenGL core custom node.

# version 410 core

//! oiv_include <Inventor/oivShaderState.h>

in vec3 normal;
in vec4 color;

void
main ()
{
    vec3 l = normalize(OivLightSourcePosition (0).xyz);
    vec3 n = normalize(normal);
    gl_FragColor = max(0.0, dot(n, l)) * color;
    gl_FragColor.a = 1.0;
}

 

Appendix D: SoGLCallback source code example

Listing D.1: Header file for OpenGLSoGLCallback.

#pragma once
#include <Inventor/actions/SoAction.h>
#include <Inventor/sys/SoGL.h>

/**
* @brief Custom OpenGL (OpenGL 4 core) node
*/
class CustomNodeGLCallback
{
public:
    /**
    * Default constructor.
    */
    CustomNodeGLCallback();

public:
    /**
    * @brief Callback for SoGLCallback
    */
    static void callback(void* data, SoAction* action );

protected:

    // Destructor
    ~CustomNodeGLCallback();
    // Create resources
    void initialiaze();
    // destroy OpenGL resources
    void destroy();

private:
    bool m_initialized;
    GLuint m_vertexBuffer;
    GLuint m_colorBuffer;
    GLuint m_normalBuffer;
};

 

Listing D.2: Implementation file for OpenGLSoGLCallback.

#include "CustomNodeGLCallback.h"
#include <Inventor/elements/SoLightModelElement.h>
#include <Inventor/actions/SoGLRenderAction.h>
#include <Inventor/sys/SoGL.h>

// external stuff for bunny data access
extern uint32_t numVertices;
extern float vertex [];
extern float normal [];
extern float color [];

CustomNodeGLCallback::CustomNodeGLCallback()
    : m_initialized (false)
{

}

CustomNodeGLCallback::~CustomNodeGLCallback()
{
    destroy();
}

void
CustomNodeGLCallback::initialize()
{
    if (m_initialized)
    {
        return;
    }

    glGenBuffers(1, &m_vertexBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &vertex[0], GL_STATIC_DRAW);

    glGenBuffers(1, &m_colorBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &color[0], GL_STATIC_DRAW);

    glGenBuffers(1, &m_normalBuffer);
    glBindBuffer(GL_ARRAY_BUFFER, m_normalBuffer);
    glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(float) * 3, &normal[0], GL_STATIC_DRAW);
    m_initialized = true;
}

void
CustomNodeGLCallback::callback(void* data , SoAction* action)
{
    CustomNodeGLCallback* cb = static_cast<CustomNodeGLCallback*>(data);

    // create resources
    cb->initialize();
    
    glBindBuffer(GL_ARRAY_BUFFER, cb->m_vertexBuffer);
    glEnableClientState(GL_VERTEX_ARRAY);
    glVertexPointer(3, GL_FLOAT, 0, 0);
    
    SoState* state = action->getState();
    SoLightModelElement::Model model = SoLightModelElement::get(state);
    
    if (model == SoLightModelElement::PHONG || model == SoLightModelElement::PER_VERTEX_PHONG)
    {
        glBindBuffer(GL_ARRAY_BUFFER, cb->m_colorBuffer);
        glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
        glEnable(GL_COLOR_MATERIAL);
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(3, GL_FLOAT, 0, 0);
        
        glBindBuffer(GL_ARRAY_BUFFER, cb->m_normalBuffer);
        glEnableClientState(GL_NORMAL_ARRAY);
        glNormalPointer(GL_FLOAT, 0, 0);
    }
    
    glDrawArrays(GL_TRIANGLES, 0, numVertices);
    
    if (model == SoLightModelElement::PHONG || model == SoLightModelElement::PER_VERTEX_PHONG)
    {
        glDisable(GL_COLOR_MATERIAL);
        glDisableClientState(GL_NORMAL_ARRAY);
        glDisableClientState(GL_COLOR_ARRAY);
    }

    glDisableClientState(GL_VERTEX_ARRAY);
}

 

Listing D.3: Usage for SoGLCallback.

CustomNodeGLCallback* callback = new CustomNodeGLCallback;
SoGLCallback* glcb = new SoGLCallback;
glcb->setCallback(&CustomNodeGLCallback::callback, callback);
// add glcb to scene graph