2 Rendering

2.5 Texture mapping

Some renderers provide support for texture mapping, including color, normal, and bump maps; whether or not they do can be queried via the methods hasColorMapping(), hasNormalMapping(), and hasBumpMapping(). If supported, such mappings may be set up and queried using the methods

void setColorMap (ColorMapProps props);
ColorMapProps getColorMap();
void setNormalMap (NormalMapProps props);
NormalMapProps getNormalMap();
void setBumpMap (BumpMapProps props);
BumpMapProps getBumpMap();

The props argument to the set methods either contains the properties required to set up the mapping, or, if null, disables the mapping. When enabled, color, normal and bump maps will be applied to subsequent draw operations involving triangle primitives for which texture coordinates have been assigned to the vertices.

At present, texture coordinates can be defined for primitives using either draw mode (Section 2.3.4), or by creating a RenderObject (Sections 2.4 and 2.4.6). Texture coordinates are assigned using the OpenGL convention whereby (0,0) and (1,1) correspond to the lower left and upper right of the image, respectively.

Normal and bump mapping will not work if shading is set to Shading.NONE or Shading.FLAT. That’s because both of those shading modes restrict the use of normals when computing primitive lighting.

Renderers based on OpenGL 3 support color, normal and bump mapping. Those based on OpenGL 2 support only color mapping.

2.5.1 Texture mapping properties

The properties specified by ColorMapProps, NormalMapProps, or BumpMapProps contains the source data for the mapping along with information about how to map that data onto drawn primitives given their texture coordinates. These properties include:

enabled

A boolean specifying whether or not the mapping is enabled;

fileName

A string giving the name of the texture source file. This can be any image file in the format supported by the standrad package javax.imageio, which includes JPEG, PNG, BMP, and GIF;

sWrapping

An instance of TextureMapProps.TextureWrapping specifying the wrapping of the s texture coordinate;

tWrapping

An instance of TextureMapProps.TextureWrapping specifying the wrapping of the t texture coordinate;

minFilter

An instance of TextureMapProps.TextureFilter specifing the minifying filter;

magFilter

An instance of TextureMapProps.TextureFilter specifing the magnifying filter;

borderColor

The color to be used when either sWrapping or tWrapping is set to TextureWrapping.CLAMP_TO_BORDER;

colorMixing

For ColorMapProps only, an instance of Renderer.ColorMixing that specifies how the color map is combined with the nominal coloring of the underlying primitive, which is in turn determined by the current diffuse/ambient color and any vertex coloring that may be present (Section 2.3.7). The default value for this is MODULATE, implying that the color map is modulated by the nominal coloring. Not all renderers support all mixing modes; whether or not a particular color mixing is supported can be queried using

   boolean hasColorMapMixing (ColorMixing cmix);
diffuseColoring

For ColorMapProps only, a boolean that specifies whether the color map should respond to diffuse/ambient lighting;

specularColoring

For ColorMapProps only, a boolean that specifies whether the color map should respond to specular lighting;

scaling

For NormalMapProps and BumpMapProps only, a float giving a scaling factor for either the x-y components of the normal map, or the depth of the bump map.

TextureMapProps.TextureWrapping is an enum that describes how texture coordinates outside the canonical range of [0,1] are handled. There are four available methods, which correspond to those available in OpenGL:

Method Description OpenGL equivalent
REPEAT pattern is repeated GL_REPEAT
MIRRORED_REPEAT pattern is repeated with mirroring GL_MIRRORED_REPEAT
CLAMP_TO_EDGE coordinates are clamped to [0,1] GL_CLAMP_TO_EDGE
CLAMP_TO_BORDER out of range coordinates are set to a border color GL_CLAMP_TO_BORDER

REPEAT is implemented by setting the integer part of the coordinate to 0. For MIRRORED_REPEAT, mirroring is applied when the integer part is odd. See Figure 2.26.

Figure 2.26: Wrapping applied to a texture coordinate s using REPEAT (top) and MIRRORED_REPEAT (bottom).

TextureMapProps.TextureFilter is an enum that describes the filtering that is applied when the source image needs to be either magnified or minified. Specifically, for a given pixel being textured, we use the filter to compute a texture value from the texels in the texture image. There are six filter types, corresponding to those available in OpenGL:

Method OpenGL equivalent
NEAREST GL_NEAREST
LINEAR GL_LINEAR
NEAREST_MIPMAP_NEAREST GL_NEAREST_MIPMAP_NEAREST
LINEAR_MIPMAP_NEAREST GL_LINEAR_MIPMAP_NEAREST
NEAREST_MIPMAP_LINEAR GL_NEAREST_MIPMAP_LINEAR
LINEAR_MIPMAP_LINEAR GL_LINEAR_MIPMAP_LINEAR

NEAREST uses the texel nearest to the pixel center, while LINEAR uses a weighted average of the four texels nearest to the pixel center. The remaing four MIPMAP values perform the filtering with the aid of mipmaps, which are images of diminishing size used to accomodate lower resolution rendering of the primitive. The OpenGL documentation should be consulted for details. Mipmaps are generated automatically if one of the MIPMAP values is selected.

2.5.2 Texturing example using draw mode

Figure 2.27: From top to bottom, images for texture_map.jpg, foil_normal_map.png, and egyptian_friz.png, used to create the color, normal and bump maps in Listing 10.

Color, normal, and bump maps can set up independently or combined with each other. Listing 10 gives a complete example, showing all three maps applied to a simple planar rectangle to make it look like a shiny brass plate embossed with an Egyptian friz pattern. A color map adds character to the brass appearance, a normal map adds a ”crinkled” effect, and a bump map adds the friz pattern.

Properties for the mappings are created by the method createMaps(), using the raw images shown in Figure 2.27, and stored in the member variables myColorMap, myNormalMap, and myBumpMap. It uses a method getDataFolder(), not shown, which returns the path to the folder containing the image files. Whether or not specific mappings are enabled is controlled by the member variables myColorMapEnabled, myNormalMapEnabled, and myBumpMapEnabled.

Listing 10: Rendering code to set up color, normal and bump maps.
import java.awt.Color;
import maspack.render.*;
import maspack.render.Renderer.DrawMode;
import maspack.render.Renderer.FaceStyle;
...
   ColorMapProps myColorMap = null;
   NormalMapProps myNormalMap = null;
   BumpMapProps myBumpMap = null;
   boolean myColorMapEnabled = true;
   boolean myNormalMapEnabled = true;
   boolean myBumpMapEnabled = true;
   public void createMaps() {
      // create color mapping
      myColorMap = new ColorMapProps ();
      myColorMap.setFileName (getDataFolder()+"/texture_map.jpg");
      myColorMap.setEnabled (true);
      // create normal mapping
      myNormalMap = new NormalMapProps ();
      myNormalMap.setFileName (getDataFolder()+"/foil_normal_map.png");
      myNormalMap.setScaling (1f);
      myNormalMap.setEnabled (true);
      // create normal mapping
      myBumpMap = new BumpMapProps ();
      myBumpMap.setFileName (getDataFolder()+"/egyptian_friz.png");
      myBumpMap.setScaling (2.5f);
      myBumpMap.setEnabled (true);
   }
   public void render (Renderer renderer, int flags) {
      float[] greenGold = new float[] {0.61f, 0.77f, 0.12f};
      float[] yellowGold = new float[] {1f, 0.44f, 0f};
      renderer.setShininess (10);                       // increase shininess
      renderer.setFaceStyle (FaceStyle.FRONT_AND_BACK); // see both sides
      renderer.setColor (greenGold);                    // base color
      renderer.setSpecular (yellowGold);                // reflected color
      // set color, normal and bump mappings if they are enabled
      if (myColorMapEnabled) {
         renderer.setColorMap (myColorMap);
      }
      if (myNormalMapEnabled) {
         renderer.setNormalMap (myNormalMap);
      }
      if (myBumpMapEnabled) {
         renderer.setBumpMap (myBumpMap);
      }
      // use draw mode to draw the plate, which is a simple 6 x 2 plane,
      // centered on the origin, created from two triangles, with texture
      // coordinates assigned to each vertex.
      renderer.beginDraw (DrawMode.TRIANGLES);
      renderer.setNormal (0, 0, 1f);
      // first triangle
      renderer.setTextureCoord (0, 0);
      renderer.addVertex (-3f, -1f, 0f);
      renderer.setTextureCoord (1, 0);
      renderer.addVertex ( 3f, -1f, 0);
      renderer.setTextureCoord (1, 1);
      renderer.addVertex ( 3f,  1f, 0);
      // second triangle
      renderer.setTextureCoord (0, 0);
      renderer.addVertex (-3f, -1f, 0f);
      renderer.setTextureCoord (1, 1);
      renderer.addVertex ( 3f,  1f, 0);
      renderer.setTextureCoord (0, 1);
      renderer.addVertex (-3f,  1f, 0);
      renderer.endDraw();
   }

The render() method does the actual rendering. It begins by increasing the shininess (10 being shininer that the default of 32), and setting the base and specular colors. Setting a separate specular color is necessary for creating specular effects that stand out from the base color. Mappings are then set if enabled, and the renderer’s draw mode is then used to draw the plate using two triangles with texture coordinates assigned to the vertices. Figure 2.28 shows the rendered plate with different mapping combinations applied.

Figure 2.28: Rendered plate, shown at an angle to enhance specular effect, with different mappings applied. Top row, left to right: no mappings; color mapping only; normal mapping only. Bottom row: bump mapping only; bump and normal mappings; all mappings.

2.5.3 Texturing example using a render object

The example described in Section 2.5.2 can also be implemented using a render object. The modification involves adding code to create the render object, and then using it to perform the draw operation in the render() method:

import maspack.render.*;
...
   RenderObject myRenderObj;
   RenderObject createPlateRenderObject() {
      // create render object for the plate:
      RenderObject robj = new RenderObject();
      robj.addNormal (0, 0, 1f);
      robj.addTextureCoord (0, 0);
      robj.vertex (-3f, -1f, 0f);
      robj.addTextureCoord (1, 0);
      robj.vertex ( 3f, -1f, 0);
      robj.addTextureCoord (1, 1);
      robj.vertex ( 3f,  1f, 0);
      robj.addTextureCoord (0, 1);
      robj.vertex (-3f,  1f, 0);
      robj.addTriangle (0, 1, 2);
      robj.addTriangle (0, 2, 3);
      return robj;
   }
   public void prerender (RenderList list) {
      // create render object if necessary.
      if (myRenderObj == null) {
         myRenderObj = createPlateRenderObject();
      }
   }
   public void render (Renderer renderer, int flags) {
      ... set up colors and mappings as in previous example ...
      // draw render object triangles
      renderer.drawTriangles (myRenderObj);
   }

The method createPlateRenderObject() creates a render object for the plate, using the same vertex and texture coordinate definitions found in Listing 10. prerender() then uses this method to creates the render object once, on demand. Because the render object in this example is fixed, it is not actually necessary to create it inside prerender(), but we do so because this is where it is recommended that render objects be maintained, particularly if they are being continuously updated with application data.

For an example of this mapping being implemented directly using a PolygonalMesh object and RenderProps, see Section 2.6.