Areas
  • J3D FAQ
  • Tutorials
  • Utilities
  • Books
  • News
  • Other Sites
  •  Chapters
  • TOC
  • Geometry
  • Lighting
  • Textures
  • Behaviours
  • User Feedback
  • Navigation
  • Audio
  • Input Devices
  • No Monitors
  •  Sections
  • Chapter TOC
  • A Window to the World
  • Scene Graph Basics
  • Geometry
  • Event Model
  • Cameras and Viewing
  •   

    Textures

    © Justin Couch 1999

    As we've just mentioned, textures get applied to objects to make them more realistic. These days, it is almost impossible to find a game that uses flat colored objects. They almost always have textures of some kind, even if only to make the walls look like old stone (a la Quake). For most terrestrial based applications, use of textures is essential at least somewhere. Even for abstract data spaces, textures may be used to present text in 3D space. Therefore it is important to have a good understanding of how they work.

    The texture capabilities under Java3D offer three areas of management:

    • Image Management: Specifies how to place an image on an object, such as how to blend edges together and how to relate a point in the image to a point on the object.
    • MipMapping: Keeping the resolution of the texture to an acceptable level to handle both better performance and better appearances
    • XLinear Filtering: Keeping track of the warped image when it has been applied to an object and making it maintain a sense of realism.
    We'll now expand on each of these areas.

    Texture Mapping Basics

    An image is represented as a bunch of 2D pixels: X by Y pixels in width and height. Your objects in the scene are represented in some world space 3D coordinates that have no relationship at all to pixels. When we come to mapping pixels to world space coordinates to put an image on an object, we have to make some sort of calculations about how to do that mapping. We could make an arbitrary guess, but obviously this is not acceptable because we, the programmer, want control of how to put the image into the world. Instead, we map the image to the object using texture coordinates.

    A very simplistic description of texture coordinates is that the bottom left of the image is considered to be (0, 0) in S (width) and T (height) coordinate axes respectively. The top right of the image is considered to be (1, 1). A coordinate of (0.5, 0.5) would place you in the center of the image, and (2, 0) would place you at two widths of the image to the right and on the baseline.

    To relate a texture to a piece of geometry, you use texture coordinates. You can't just place arbitary texture coordinates into a collection of geometry and hope things turn out right. The only place that you can place a texture coordinates is on one of the geometry vertices. If you need more control over the image mapping, then you can simply add extra vertices to the appropriate places in the underlying geometry.

    Using this mapping, the texture always stays relative to the object. Thus, when you stretch a polygon, the texture will increase in size so that the points of the texture stay the same relative to the vertices on the object.

    Java3D allows 3D textures too. That is, an extra coordinate R is added to S and T to allow warps in the depth direction (relative to the texture image) as well. In this case, the texture is treated as a 3D block of image. If you consider that in a 2D image, there are square areas defined by a pixel, you can now make this a volume that has depth as well. Typically these are procedurally generated, but you can do it by laying multiple images over the top of each other. One image is considered to be a depth to the equivalent value that a pixel is width and height. A "unit volume" of 3D texture space would be defined as one pixel by one pixel by one image.

    Creating a Texture

    Java3D defines two types of textures - 2D and 3D. A 2D texture is your standard image like a JPEG that you want to put over the object. For this, there are two separate classes: Texture2D and Texture3D. These are used internally to represent the images and extract the appropriate information from them. To texture an object, you will first need to create a texture and a set of texture coordinates.

    Typically 2D textures come from an external file. What we need to do is load that image and turn it into a form that Java3D wants (the Texture2D object). Unfortunately, the process is a bit convoluted with Java3D. You cannot just provide the texture with a URL, you need to do all of the image loading and processing yourself. Here's one method of approaching it.

    1. Use the AWT toolkit to get the default toolkit and use it's createImage() method to load the basic image for you.
        Toolkit tk = Toolkit.getDefaultToolkit();
        Image src_img = tk.createImage(url);
        BufferedImage buf_img = null;
        
    2. Notice that the object returned is a java.awt.Image. Texture2D uses an ImageComponent2D to represent an image, and that requires a java.awt.image.BufferedImage as the source. The tricky bit in all of this work is doing this conversion step. There are many approaches to this. The simplest solution is to create a new BufferedImage and then just draw the old image straight over the top of it.
            if(!(src_img instanceof BufferedImage)) {
              // create a component anonymous inner class to give us the
              // image observer we need to get the width and height of
              // the source image.
              Component obs = new Component() { };
      
              int width = src_img.getWidth(obs);
              int height = src_img.getHeight(obs);
      
              // construct the buffered image from the source data.
              buf_img =
                      new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
      
              Graphics g = buf_img.getGraphics();
              g.drawImage(src_img, 0, 0, null);
              g.dispose();
            }
            else
              buf_img = (BufferedImage)src_img;
            
    3. Create the new ImageComponent2D for use by the texture using our newly created BufferedImage.
          ImageComponent img_comp =
              new ImageComponent2D(ImageComponent.FORMAT_ARGB, buf_img);
          
    4. Create the Texture2D object. Notice that we've set the image type to be ARGB because we have no idea what the source image really is. For all we know at the code level it could be a GIF image with transparency.
          texture = new Texture2D(Texture.BASE_LEVEL,
                                  Texture.RGB,
                                  img_comp.getWidth(),
                                 img_comp.getHeight());
          
    5. Add the texture to the appearance used for the object and cleanup.
          appearance.setTexture(texture);
          buf_img.flush();
          
       Note
      The method shown is not the best way to grab a texture. If the source image has transparency it will be lost in the process of copying it to the internal image. It is better to use the AffineTransform code from Java 2D to resize the image to the correct size. This is covered in much greater detail in Chapter 4 Textures.
    This is enough to get you started with a loaded texture. There are much more accurate ways of doing this, but this will get you started. The next step is to apply it to your object. Since we already have an appearance node set, that should automatically apply the texture. If you want more control, you will need to set the appropriate Texture Coordinates in the IndexedQuadArray that we created previously and match the indices with the vertices of the geometry. More complex management issues are handled in the next section.

    Image Management

    One objective of texture mapping is to provide realistic looking surfaces like a tiled floor. In your house, there may be a number of different sized rooms with that tiled floor so you'll want to reuse the image as much as possible. If you just made the texture sized proportionally to fit one room, all the other rooms will end up looking wrong. One way to overcome this is to play with the texture coordinates to make sure the room scales the texture properly.

    For example, say the second room is twice as long as the first, but the same width, you could set the texture coordinate for "far" corners to be (0, 2) and (1,2) so that we get the same size tiles across the room. What these greater-than-one coordinates mean is that the texture must be tiled across the room. This might not be your desired result. Instead, you might want is to have a picture frame type effect where the entire texture sits inside the bounds of the object, but does not tile, leaving the raw object color around the borders (between the image and the boundaries of the object/polygon).

    Boundary Mode
    In texture mapping parlance, this is called the boundary mode. The boundary of the texture has one of two modes - clamped or tiled. A clamped mode means that the texture coordinates always move with the object. No tiling is involved and the image takes on the picture frame effect described in the previous paragraph.

    For Tiled mode, the image is automatically tiled the appropriate number of times defined by the texture coordinates to make the completed texture on the object. You can set these modes independently for each axis. You can set the texture

    Boundary Color
    If you've decided to use a clamped boundary mode, then the boundary color is used as the fill color between the edges of the texture and the ploygon(s).

    MipMapping

    MipMaps are about controlling both the complexity and size of your textures. When an object is far off, you can't see much of the detail due to the resolution of the screen. There is no real point in having a highly detailed image as the texture because the effects are lost. On the other hand, when you are really close to an object, the textures can look really pixellated.

    To control these problems, one solution is to create a number of images of different resolution and then apply them to the object depending on the level of detail needed. When an object is really close, load the high resolution images, if the object is far away or moving quickly, use lower resolution.

    In the mipmapping process you may supply any number of images that you want. The only requirement is that for each level of greater resolution, you need to provide an image of double the resolution. That is, each level is a power of 2 greater in detail in each dimension.

    The setMipMapMode() method is used to control how the mipmapping is setup for the object. Basically the option is to have either one image (no mipmapping), or many images (mipmapping on). If you are using mipmapping then you need to set all the images using the setImage() method.

    Filtering

    The final option for control of your textures is to use one of the various filtering modes. These filtering modes are used to control how to map your images to objects. In particular, these effects are most apparent when you are looking at a non-linear scale of a texture across a polygon. For example, the road disappearing off into the distance with the perspective making the road taper is usually used as a test of the accuracy of hardware filtering implementations.

    There are a number of different types of filtering available. You're probably quite familiar with the terms Bi-linear and Tri-linear filtering as used in all the marketing brochures used on consumer grade 3D accelerated video cards. These are two approaches to solving the problem, and Java3D lets you choose the mode to use with the setMinFilter() and setMaxFilter() methods. These control filtering when one texel (a pixel after it has been massaged by the texture mapping process) is smaller than the original pixel and when one texel is larger than one pixel, respectively.

      

    [ TOC ] [ Home ] [ FAQ ] [ Books ] [ Tutorials ] [ Utilities ] [ Contact Us ]