9.5. Bump Mapping

Key Concepts

Bump mapping is a way of simulating fine details such as bumps or wrinkles in a surface by modifying the surface normals. Bump mapping can be used to simulate features that would otherwise take a large number of polygons to model.

This technique is a normal-perturbation rendering technique for simulating lighting effects caused by patterned irregularities on an otherwise locally smooth surface.

This technique is a multi-pass per-pixel lighting computation using pixel shaders and vertex shader hardware features, thus classical OpenGL Gouraud lighting is deactivated when a shape is rendered using the bump mapping technique.

Depending on your hardware capabilities, bump mapping may not be available. This technique uses standard OpenGL 1.3 features but can also work with previous OpenGL versions if specific OpenGL extensions are available. The method SoBumpMapping::isSupported() will indicate if bump mapping is supported by your graphics board.

Normal map, height map, bump map

The main idea of bump mapping is that, instead of using a texture to modify the color of a surface, we apply a texture, called a normal map, height map, or simply bump map (generic term), to modify the surface normals. The surface patterns are encoded in the bump map texture.

  • The texture is called a normal map if it stores normals. The bump map texture is in this case an RGB texture, with x, y, and z corresponding to the red, green, and blue components respectively.

  • The texture is called a height map if it stores heights. The bump map texture is in this case a monochrome texture where each monochrome value represents a height, with white used for high areas and black for low areas. A height map is converted internally to a normal map.

A surface perturbed by a height map

Figure 9.10. A surface perturbed by a height map



A height map (left) and a normal map (right)
A height map (left) and a normal map (right)

Figure 9.11. A height map (left) and a normal map (right)



Bump map coordinate system and texture coordinates

As previously described, a bump map is a texture where heights (a height map) or normals (normal map) are encoded.

Like classical texture mapping, texture coordinates (s and t) are assigned to each vertex of a polygon, to control how the bump map is mapped onto the geometry.

The following figure illustrates this.

Texture coordinates assigned to a height map

Figure 9.12. Texture coordinates assigned to a height map



Additionally, each normal stored within the normal map can be expressed either in the space of the object called “modeling space” or in the space of the polygon called “tangent space.”

The “tangent space” coordinate system is convenient for applying repetitive wrinkle patterns.

Tangent space coordinate system

Figure 9.13. Tangent space coordinate system



Self Shadowing

When bump mapping, there are really two surface normals that should be considered for lighting: the unperturbed normal N based on the shape geometry, and the perturbed N', which is based on the surface’s small-scale microstructure stored within the normal map.

Self-shadowing corrects lighting which would normally only be based on the perturbed normal N' by taking into account the unperturbed normal N.

Bump mapping without and with seft-shadowing

Figure 9.14. Bump mapping without and with seft-shadowing



How to Use Bump Mapping in Your Application

Nodes used for bump mapping

Figure 9.15. Nodes used for bump mapping.



The bump mapping feature is not yet compatible with the shadow casting feature. Trying to combine these two features will give you an incorrect visual result.

The following nodes are involved in bump mapping:

SoBumpMappingProperty

Specifies the current bump mapping properties for all subsequent shapes.

The properties described in the section called “Key Concepts” can be set by this node, namely:

  • Bump map coordinate system with the field normalMapSpace ( SoSFEnum)

  • Bump map format (normal map or height map) with the field textureFormat ( SoSFEnum SoSFEnum )

  • Self-shadowing with the field isShadowing ( SoSFBool)

  • Eye distance attenuation with the field isEyeDistanceAttenuation ( SoSFBool SoSFBool SoSFBool )

SoTextureUnit SoTextureUnit SoTextureUnit

Specifies the current texture unit and the associated mapping method for all subsequent 2D texture nodes.

If the graphics board supports multitexturing, then multiple textures, each defined by a texture unit, can be applied to the same shape. The maximum number of texture units depends on the board and can be queried using getMaxTextureUnit.

In FXViz 1.1, a maximum of two texture units is supported, with two different mapping methods (e.g., the first texture unit could be an image map and the second one a bump map).

A texture unit identified by the field unit ( SoSFUInt32 SoSFUInt32 SoSFUInt32 ) is defined by the following texture properties:

SoTexture2 SoTexture2 SoTexture2

Specifies a 2D texture map to be used, and associated parameters for bump mapping and also for classical texture mapping (image mapping).

This node is described in detail in the “Textures” chapter in The Inventor Mentor (see Section 7.3, “Texture Nodes”).

SoTextureCoordinate2

Explicitly defines the set of 2D texture coordinates to be used by subsequent vertex shapes for bump mapping, but also for classical texture mapping (image mapping).

Automatic texture coordinate generation ( SoTextureCoordinateFunction SoTextureCoordinateFunction SoTextureCoordinateFunction and its derived classes) and 2D transformation ( SoTexture2Transform SoTexture2Transform SoTexture2Transform ) are not supported by bump mapping.

This node is described in detail in the “Textures” chapter in The Inventor Mentor (see the section called “Nodes Used for Texture Mapping”).

Applying bump maps: Two scenarios

There are two general scenarios for using bump mapping. The first consists of applying a bump map to a shape in order to generate a wrinkled surface. The second one consists of combining bump mapping with image mapping in order to obtain a wrinkled surface, with an image texture mapped on top of the wrinkles.

The following two figures illustrate these two possibilities.

Bump mapping onto a shape

Figure 9.16. Bump mapping onto a shape



Bump mapping and image mapping onto a shape

Figure 9.17. Bump mapping and image mapping onto a shape



The demo program $OIVHOME/src/FXViz/demos/BumpMapping illustrates the use of bump mapping. Additionally, some simple Open Inventor files combining bump mapping and image mapping are supplied in the directory $OIVHOME/src/FXViz/data/BumpMapping.

Same geometry, without and with bump mapping.
Same geometry, without and with bump mapping.

Figure 9.18. Same geometry, without and with bump mapping.



Appearance Preserving Simplification

Using the appearance preserving simplification feature, you can create more realistic looking shapes with fewer triangles.

Starting with a complex model, the first step is to create a simplified (i.e., lower polygon count) version of the model using the existing simplification tools ( SoSimplifyAction SoSimplifyAction SoSimplifyAction , etc.). The next step is to pass the two models to the new SoAppearancePreserver SoAppearancePreserver SoAppearancePreserver class which computes a normal map that attempts to reproduce the appearance of the complex model on the simplified model. The final step is to apply the resulting bump maps to the simple model.

Appearance preserving simplification process

Figure 9.19. Appearance preserving simplification process



Using SoAppearancePreserver

  1. Compute a simplified version of your complex model using the simplification tools.

    For more information about simplication tools see the section called “Large Model Visualization (LMV)”.

    SoDecimator *decimator = new SoDecimator;
    // Bump mapping could be applied only on non-indexed shapes
    decimator->setSimplifiedShapeType(SoFaceSet::getClassTypeId());
    
    float level = 2000;              // We want to get a simplified model with 2000 triangles
    globalSimplifyAction = new SoGlobalSimplifyAction(decimator);
    globalSimplifyAction->setSimplificationLevels(1, &level);
    globalSimplifyAction->apply(myHighLevelModel);
    // Retrieves the simplified model
    myLowLevelModel = globalSimplifyAction-getSimplifiedSceneGraph();
    
    delete decimator;               // No longer needed
    delete globalSimplifyAction;    // No longer needed
          
    SoDecimator decimator = new SoDecimator();
    // Bump mapping could be applied only on non-indexed shapes
    decimator.SetSimplifiedShapeType(typeof(SoFaceSet));
    
    float[] level = new float[1]{2000}; // We want to get a simplified model with 2000 triangles
    SoGlobalSimplifyAction globalSimplifyAction = new SoGlobalSimplifyAction(decimator);
    globalSimplifyAction.SetSimplificationLevels(level);
    globalSimplifyAction.Apply(myHighLevelModel);
    // Retrieves the simplified model
    myLowLevelModel = globalSimplifyAction.GetSimplifiedSceneGraph();
        
    SoDecimator decimator = new SoDecimator();
    // Bump mapping could be applied only on non-indexed shapes
    decimator.setSimplifiedShapeClass(SoFaceSet.class);
    
    float[] level = new float[]{2000}; // We want to get a simplified model with 2000 triangles
    SoGlobalSimplifyAction globalSimplifyAction = new SoGlobalSimplifyAction(decimator);
    globalSimplifyAction.setSimplificationLevels(level);
    globalSimplifyAction.apply(myHighLevelModel);
    // Retrieves the simplified model
    myLowLevelModel = globalSimplifyAction.getSimplifiedSceneGraph();
        
  2. Initialize FXViz module and create an instance.

    This initialization must be called after initializing Open Inventor

    SoFXViz::init();
    SoAppearancePreserver *myAPS = new SoAppearancePreserver;
          
    SoAppearancePreserver app = new SoAppearancePreserver();
        
    SoAppearancePreserver app = new SoAppearancePreserver();
       
  3. Specify the high resolution and low resolution scene graphs with the method SoAppearancePreserver::setSceneGraphs().

    myAPS->setSceneGraphs(myHighLevelModel, myLowLevelModel);
          
    myAPS.SetSceneGraphs(myHighLevelModel, myLowLevelModel);
          
    myAPS.setSceneGraphs(myHighLevelModel, myLowLevelModel);
          
  4. Specify the requested normal map texture size with the method SoAppearancePreserver::setTextureSize().

    The quality of the final rendering using the bump mapping technique is closely linked to the size of the normal map texture.

    myAPS->setTextureSize(512, 512);      // 512 x 512
          
    myAPS.SetTextureSize(512, 512);      // 512 x 512
        
    myAPS.setTextureSize(512, 512);      // 512 x 512
        
  5. Retrieve the normal map texture and the bump mapping texture coordinates with the methods SoAppearancePreserver::getNormalMapTexture() and SoAppearancePreserver::getBumpCoords()

    SoTexture2 *bumpMapTex = myAPS->getNormalMapTexture("myBumpMapTex.png");
    SoTextureCoordinate2 *bumpCoord = myAPS->getBumpCoords();
    delete myAPS;           // No longer needed
          
    SoTexture2 bumpMapTex = myAPS.GetNormalMapTexture("myBumpMapTex.png");
    SoTextureCoordinate2 bumpCoord = myAPS.GetBumpCoords();
        
    SoTexture2 bumpMapTex = myAPS.getNormalMapTexture("myBumpMapTex.png");
    SoTextureCoordinate2 bumpCoord = myAPS.getBumpCoords();
        
  6. Build a scene graph using bump mapping

    SoTextureUnit *texUnit0 = new SoTextureUnit;
    texUnit0->unit.setValue(0);
    texUnit0->mappingMethod = SoTextureUnit::BUMP_MAPPING;
    
    SoSeparator *bumpMappingSep = new SoSeparator;
    bumpMappingSep->addChild(texUnit0);
    bumpMappingSep->addChild(bumpCoord);
    bumpMappingSep->addChild(bumpMapTex);
    bumpMappingSep->addChild(myLowLevelModel);
          
    SoTextureUnit texUnit0 = new SoTextureUnit();
    texUnit0.unit.Value = 0;
    texUnit0.mappingMethod.Value = SoTextureUnit.MappingMethods.BUMP_MAPPING;
    
    SoSeparator bumpMappingSep = new SoSeparator();
    bumpMappingSep.AddChild(texUnit0);
    bumpMappingSep.AddChild(bumpCoord);
    bumpMappingSep.AddChild(bumpMapTex);
    bumpMappingSep.AddChild(myLowLevelModel);
        
    SoTextureUnit texUnit0 = new SoTextureUnit();
    texUnit0.unit.setValue(0);
    texUnit0.mappingMethod.setValue(SoTextureUnit.MappingMethods.BUMP_MAPPING);
    
    SoSeparator bumpMappingSep = new SoSeparator();
    bumpMappingSep.addChild(texUnit0);
    bumpMappingSep.addChild(bumpCoord);
    bumpMappingSep.addChild(bumpMapTex);
    bumpMappingSep.addChild(myLowLevelModel);
        

The demo program $OIVHOME/src/FXViz/demos/BumpMapping illustrates the use of the appearance preserving simplification technique.