Texture Mapping

Now that you are building some objects in VRML, you have been able to create some nice effects with color, but you could be interested in doing a little more. One of the nicer features of VRML is the ability to attach graphic images to the objects we have created.

To make this work, you simply use a texture node. These go in the Shape object.

Here is a simple example:
brickWall.wrl

#VRML V2.0 utf8
#brickWall.wrl
#andy Harris

# demonstrates use of textures

Shape {
  appearance DEF theTexture Appearance {
    texture ImageTexture {
      url ["brick1.jpg"]
    } # end texture
  } # end appearance

  geometry Box {
    size 1 2 3
  } # end geometry
} # end shape

NavigationInfo {
  type "EXAMINE"
} # end navInfo
As you can see, The texture mode is reminiscent of the material node. It essentially goes in the same place in the appearance node. You can have both a texture and a material, but the texture will over-write the material node. You might choose to still have a material in case something goes wrong with the texture.

The texture node itself is quite simple. It basically consists of another node, which will define exactly what type of texture is mapped to the object. For now, we will be using ImageTexture nodes.

In the imageTexture, the most important node is the URL node. This is a multi-string node. Note the square brackets. Inside the brackets, you place the url of the image you want to have mapped to the shape. The url must be placed inside double quotes, and must be a valid url of a graphic file in standard url format. It can point to a gif or to a jpeg. Animated gifs will NOT work (although later I will show you an alternative.) Gif files with transparent backgrounds are fine, and are used quite frequently.

Here is a slightly more involved example:
an immersive texture map

Demonstrating simple texture mapping in VRML

Click an image to see it wrapped to some objects

Links to examples

Texture mapping on primitive objects

As you can see from the demonstration above, the use of a texture can dramatically improve the appearance of primitive objects. Be sure to look carefully at patterns such as the 'vertical bar' image. You will see that this same image has quite different effects on the three shapes provided. When mapping a rectangular image to a cube, the image is mapped onto every side of the cube. Notice that the image is 'stretched and squashed' to fit the sides exactly. Later we will see how to more carefully control that behavior with a textureTransform node. On the sphere, the center of the picture is mapped to the front of the sphere, and the image is stretched around the sphere. The easiest way to see this is to examine the map.

sidebar - map projections

Note, the map is a Mercator projection; an attempt to map a spherical shape onto a two dimensional rectangular flat surface, which is impossible to do exactly. Interestingly enough, the way VRML works, it maps a flat rectangle onto a sphere, correcting the distortions caused by the mercator projection. Note that the image I used was a very crude map, so the distortion is not completely eliminated. (Greenland is about 1/4 the size of China, but Mercator projections tend to enlarge its size greatly because it is so far north).

You can see that there are three main ways that VRML tends to map images to surfaces. It can do a rectangular 'stretch and squash' as you can see on the box shape. On spheres and parts of cones, it does a 'distortion' to try and map the entire rectangular shape to the non-rectangular surface. As you can see, we can use this distortion to our advantage.

If you examine the bottom of the cone with an image mapped onto it, you will notice that VRML also occasionally 'clips' an image. In effect, it draws a rectangle around an image, and simply shows the parts of the image that are are on the surface. So the bottom of the cone is not distorted, but only the center of it is shown.

As usual, you will really need to look carefully at this yourself to understand the phenomenon. It really isn't that difficult to understand once you've played with it a little bit.

Using transparent backgrounds

Examine this simple example:
tree.wrl
#VRML V2.0 utf8
#tree.wrl

# illustrates how to make a simple tree with textures.
# andy Harris, 12/98

Transform {
  children [

    Shape {
      appearance Appearance {
        texture ImageTexture {
          url [ "tree.gif" ]
        } # end texture
      } # end appearance
      geometry  Box {
        size 5 5 .01
      } # end box
    } # end shape

    Shape {
      appearance Appearance {
        texture ImageTexture {
          url [ "tree.gif" ]
        } # end texture
      } # end appearance
      geometry  Box {
        size .01 5 5
      } # end box
    } # end shape
  ] # end children
} # end tree

This illustrates how you can generate a reasonably nice tree figure with very little effort. The technique can be used in other ways, to quickly make 'cardboard cutouts' of three-dimensional objects. This is best used for things that will not be central to your scene, as it will not stand up to close scrutiny, but it does have its uses.

To do this, make an image (or two,) with a transparent background. Try to center the image along the y axis.

When you design the object in VRML, make it two boxes in the same space. Make one of the boxes REALLY tiny in the z axis, and the other really small in the X axis. Map your texture onto both of the boxes.

Billboards

Another solution is to create a tree image (perhaps use a digital camera) with a transparent background, place it on a rectangle, and put it in a billboard with a y axis-of-rotation. This will always rotate the tree around the y axis. It works great if the trees are a minor part of the scene, and the user does not get too close. However, if the user is allowed to fly, the illusion will be lost.
Billboard.wrl
#VRML V2.0 utf8
#billboard.wrl

# illustrates how to make a simple tree with textures.
# adding the billboard node to make the tree appear dimensional
# andy Harris, 12/98

Billboard {
  axisOfRotation 0 1 0
  children [

    Shape {
      appearance Appearance {
        texture ImageTexture {
          url [ "tree.gif" ]
        } # end texture
      } # end appearance
      geometry  Box {
        size 5 5 .01
      } # end box
    } # end shape
  ] # end children
} # end tree

Texture transformations

Textures are pretty interesting, but we often need to manipulate them in more interesting ways. To see one particular problem, take a look at the following vr world: brickTrans.wrl
#VRML V2.0 utf8

#brickTrans.wrl
#illustrates texture transformation

#default, no transformation of textures
Transform {
  translation 0 0 0
  children [
    Shape {
      appearance Appearance {
        texture ImageTexture {
          url ["brick1.jpg"]
        } # end texture
      } # end appearance
    
      geometry Box {
        size 1 1 1
      } # end geometry
    } # end shape
  ] # end children
} # end transform

#left hand box 
#tries to stretch texture to new size.  Note the distortion
Transform {
  translation -2 0 0
  children [
    Shape {
      appearance Appearance {
        texture ImageTexture {
          url ["brick1.jpg"]
        } # end texture
      } # end appearance
    
      geometry Box {
        size .75 5 1
      } # end geometry
    } # end shape
  ] # end children
} # end transform

#right hand box
#use texture transform to add scale to texture map
#corrects the texture
Transform {
  translation 2 0 0
  children [
    Shape {
      appearance Appearance {
        texture ImageTexture {
          url ["brick1.jpg"]
        } # end texture

        #note the addition of a textureTransform node here
        textureTransform TextureTransform {
          scale .75 5
        } # end textureTransform
      } # end appearance
    
      geometry Box {
        size .75 5 1
      } # end geometry
    } # end shape
  ] # end children
} # end transform

#bottom box
#default size geometry, but 'zooms in' the texture for more detail
Transform {
  translation 0 -2 0
  children [
    Shape {
      appearance Appearance {
        texture ImageTexture {
          url ["brick1.jpg"]
        } # end texture

        textureTransform TextureTransform {
          scale .5 .5
        } # end textTrans
      } # end appearance
    
      geometry Box {
        size 1 1 1
      } # end geometry
    } # end shape
  ] # end children
} # end transform

#top box
#shows how we can also rotate a texture

Transform {
  translation 0 2 0
  children [
    Shape {
      appearance Appearance {
        texture ImageTexture {
          url ["brick1.jpg"]
        } # end texture

        textureTransform TextureTransform {
          scale .5 .5
          rotation -0.5
        } # end textTrans
      } # end appearance
    
      geometry Box {
        size 1 1 1
      } # end geometry
    } # end shape
  ] # end children
} # end transform

The box in the center shows the default mapping of the brick graphic onto a 1x1x1 box. The graphic was designed to be square, so it works out pretty well on a regular cube. However, the left hand box is designed to be a fraction of the width and five times the height of the default. When we map exactly the same graphic onto the 'tall and skinny' box, we see that the default behavior of the texture causes us some problems. The texture is 'squashed and stretched' to fit the new size, giving us tall and skinny bricks. With some textures, this behavior is fine, but other textures such as bricks really demand more control over size. If we put the two shapes in the same world, they would clearly not appear to be made of the same kind of bricks.

It would be nice if we had some ability to transform a texture just like we can a shape. If we had this ability, we could repeat a texture, choose a smaller subset of it, translate the origin of the texture, and even rotate the texture. This would give us a number of very interesting capabilities.

You're way ahead of me... We do have exactly such a capacity, and it's called the TextureTransform node. Take a look at the right-hand box, and you'll see that the bricks maintain the same proportion as the center box. When you look at the code, you'll see that the Shape node contains a textureTransform field, which contains a TextureTransform node. The node has a reasonably familiar field in it, a scale field. However, this is NOT exactly like a traditional scale, because it does NOT contain a vector of three floats (SFVec3F). Instead, it is a scale in TWO dimensions. This is because a texture is a two -dimensional entity, attached to a plane. We do not have to worry about the third dimension in textures.

We scaled the textureTransform at .75 5, which corresponds to the x and y scale factors of the tall skinny box. The appropriate way to think of texture scale is 'how much of the original texure do I want to show in each axis?' For example, we are showing 75 percent of the width of the graphic. Since we have scaled the box we are using also to 75 percent of the original width, the graphic will stay at the same scale. We are showing 5 times the original height, which means that we are repeating the height five times. This also corresponds to the size of the object we are texturing, so it keeps the entire texture consistent.

If you look at scale in another way, these values would not be intuitive. You might think that a Y factor of 5 would make the texture five times larget, but in fact it will force the graphic to fit five times on the shape. If the shape size does not change, a scale of larger than one will make the graphic SMALLER, not Larger.

The bottom box illustrates a different use of the textureTransform scale field. In this situation, we left the box at the original size, but scaled the texture by .5 .5. This caused us to show only one quarter of the original texture, but to map it onto the entire surface, effectively 'zooming in' on the texture.

The top box illustrates another field of the textureTransform, rotation. As you can see, the bricks appear to be at an angle. This is because we have rotated the texture. Rotations are simpler in texture transforms than in shapes, because textures are two dimensional. There is only one sensible axis of rotation, which is the (virtual) z axis. So, all we need is an angle of rotation, and we can rotate the texture.

Finally, the textureTransform node supports translation of a texture. This allows us to displace where the upper left hand corner of the texture will be placed on the surface. If we give positive values, the corner will be moved down and to the right, usually wrapping the image. We can also give negative values, moving the texture 'off' the shape to the left or top. This is especially effective when we want to show only part of a texture.

Texture coordinates and indexed face sets

This is ugly but powerful. Take a look at this texPal.wrl
#VRML V2.0 utf8

#texPal.wrl

#Attaching a texture palette to an indexed faceset

Shape {
  appearance Appearance {
    material Material {
      diffuseColor 1 1 1
    } # end material
    texture ImageTexture {
      url ["texPal.gif"]
    } # end texture
  } # end appearance

  geometry IndexedFaceSet {

    solid TRUE
    coord Coordinate {
      point [
        # the pointset hasn't changed
        #base of pyramid
        -2 0  2,     # point 0, left and close
        -2 0 -2,     # point 1, left and back
         2 0 -2,     # point 2, right and back
         2 0  2,     # point 3, right and close
        #top of pyramid
         0 2  0,     #point 4, top of pyramid
        ]  # end point
    } # end coord
    coordIndex [
      #base
      #note that now it is CRUCIAL that I go counter-clockwise
      #from the OUTSIDE.

      0, 1, 2, 3, 0, -1, #bottom
      0, 4, 1, -1,   # left side
      1, 4, 2, -1,   # back side
      2, 4, 3, -1,   # right side
      3, 4, 0, -1,   # front side
    ] # end coordIndex

    texCoord TextureCoordinate {
      point [
        0  1,  #0 upper left
        .5 1,  #1 upper middle
        1, 1,  #2 upper right
        0  .5, #3 mid left
        .5 .5, #4 center
        1, .5  #5 mid right
        0  0,  #6 lower left
        .5 0,  #7 lower middle
        1, 0,  #8 lower right
      ]
    } # end textcoord

      texCoordIndex[
        #Photo on bottom
        1 4 5 2 1 -1,
        
        #wood on left
        4 0 3   -1,

        #sky on back
        7 3 6  -1,

        #grass on right
        8 4 7  -1,

        #wierd on front
        8 1 6  -1
      ] # end 
  } # end geometry
} # end shape

NavigationInfo{
  type "EXAMINE"
} # end NavigationInfo

© Andy Harris
Indiana University / Purdue University, Indianapolis
email: aharris@cs.iupui.edu
homepage: www.cs.iupui.edu/~aharris