Texture Mapping

As discussed in the introduction to the Java 3D section, texture mapping is an important technique for efficiently generating realism in a virtual environment. Texture maps can be used to create realistic looking floors, walls, and ceilings and are further useful as a way to reduce complex geometry, especially along the edges of an environment.

As an example of this last use of texture mapping, consider a model of an open space in the center of a city such as Central Park in New York City. Building geometry to represent the buildings in the far distance would take a lot of time to develop and would slow down rendering significantly. It wouldn't detract much from (and could even serve to increase) the realism of the scene to map photographic textures of the skyline on a simpler set of geometry placed in the distance.

A couple of options should be kept in mind while using texture mapping in Java 3D. The first is to understand how the source image will be loaded (or generated) so that it can be put in a format that works with Java 3D's Texture class (either Texture2D or Texture3D). There are only a couple of ways to instantiate a Texture2D or Texture3D object. Once a Texture object has been instantiated, it is included with the Appearance bundle.

Java 3D requires textures to be in an ImageComponent object (either ImageComponent2D or ImageComponent3D, depending on whether the texture is 2D or 3D. 3D texture mapping is covered later). Probably the easiest way to generate an ImageComponent is to use the TextureLoader utility (see the next section).

The more flexible and sophisticated way to get image data into an ImageComponent is by creating a BufferedImage (see Chapters 26 for information on using the BufferedImage class). Recall from those chapters that a BufferedImage is an image created in an accessible memory buffer. Later in this section, we will explore ways in which 2D texture animations can be incorporated into the environment. In order to do this, we will use Java 3D texture by reference capability.

One important aspect of using textures in Java 3D is that the dimension of all images must be a power of 2 (for example, 64x64, 128x128, 256x256, and so on). Note that the x and y dimensions don't have to be the same, however. For example, a 64x256 image is acceptable although this too can cause problems. Although this restriction can be a nuisance, it does provide some important rendering speed improvements. If you are not using images with a dimension that is a power of 2, you must resize the image in a graphics or paint program.

Finally, we will note that many additional texture mapping features can be specified with the TextureAttributes() object. These attributes dictate how the image is applied to the shape and color, for example, blending the texture with the underlying material or placing the texture on top of the material as a decal. The TextureAttributes() object also contains a method to translate, scale, and rotate the texture on the underlying shape.

Using the TextureLoader

To get the textures into Java 3D, the programmer will most often use the TextureLoader utility class. This utility takes the URL for a .jpg, .gif, or .bmp file as input and returns an ImageComponent. The ImageComponent class will be important not only for these more simple applications, but also later when we integrate JAI and JMF into our 3D scenes. Listing 11.9, SimpleTextureExJ3D.java, illustrates the use of the texture loader to perform a simple texturing of a box. The approach outlined in Figure 11.7 is good for placing static images in the environment, but it heavily restricts the techniques available relative to the BufferedImaging approach.

Figure 11.7. Screen shot from SimpleTextureExJ3D.java.


Listing 11.9 SimpleTextureExJ3D.java
import com.sun.j3d.utils.image.TextureLoader;
import com.sun.j3d.utils.behaviors.mouse.MouseRotate;
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;

public class SimpleTextureExJ3D extends Applet {

  public BranchGroup createSceneGraph() {
    // Create the root of the branch graph
    BranchGroup objRoot = new BranchGroup();

    // Create TransformGroup for scaling children
    TransformGroup objScale = new TransformGroup();
    Transform3D t3d = new Transform3D();
    t3d.setScale(0.5);
    objScale.setTransform(t3d);
    objRoot.addChild(objScale);

    // Create the TransformGroup for rotation;
   //  must enable WRITE and READ capability bits

    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

    objScale.addChild(objTrans);

    Appearance app = new Appearance();
    Box b = new Box(1.f,1.f,1.f, Box.GENERATE_TEXTURE_COORDS, app);
    objTrans.addChild(b);

    TextureLoader tex = new TextureLoader("c:/alexjava/texture.jpg", "RGB", this);  app.
setTexture(tex.getTexture());

    BoundingSphere bounds =
      new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

    // Create the rotate behavior node
    MouseRotate behavior = new MouseRotate(objTrans);
    objTrans.addChild(behavior);
    behavior.setSchedulingBounds(bounds);

    return objRoot;
  }

The createSceneGraph() method creates the content subgraph for our texturing example. The vast majority of the method will look quite similar to what we have done in previous examples. We create a BranchGroup to eventually return and place two TransformGroups below it; one for scaling and the other for rotation with the MouseRotate behavior.

The texture mapping part of the scene graph begins after the Box node is added to the objTrans TransformGroup. We must first get our texture into the program using the TextureLoader utility. Note that a more complete program would verify that the texture is loaded by testing for null. For simplicity, we omit this important step. We next use the loaded texture as an argument to the setTexture() method of the Appearance object. This is the only step we need to take. The rest of the scene graph involves adding a MouseRotate behavior so that we can interact with our textured box.

Note

The Box was created with the GENERATE_TEXTURE_COORDS capability bit set. If this bit is not set, the texture will not be mapped and will appear white. No error or warning messages will appear.


Using a BufferedImage for Texturing

The TextureLoader is by far the most common way to load textures for texture mapping. The other approach is to use the BufferedImage class. Buffered imaging is described extensively in Chapters 26. Briefly, a BufferedImage is an area of memory containing pixel data.

Using a BufferedImage is the only way to produce dynamic textures and to incorporate image processing into the texture mapping. We will be utilizing this avenue in many of the examples in this section.

Listing 11.10 creates a BufferedImage and applies it to the same Box that we used in Listing 11.9.

Listing 11.10 (Partial) BufferedImageTextureJ3D.java
import javax.media.j3d.ImageComponent2D;
import java.awt.image.*;
. . .
public class BufferedImageTextureJ3D extends Applet {

  int imgheight=256;
  int imgwidth=256;
. . .

  Appearance app = new Appearance();
  app.setCapability(Appearance.ALLOW_TEXTURE_WRITE);

  Box b = new Box(1.f,1.f,1.f, Box.GENERATE_TEXTURE_COORDS, app);
  objTrans.addChild(b);

  BufferedImage bi = new BufferedImage(imgwidth, imgheight,BufferedImage.TYPE_INT_RGB);

  DataBuffer db = bi.getRaster().getDataBuffer();

//loop over the DataBuffer
  for (int ii=0;ii<imgwidth*imgheight;ii++ ) {
       db.setElem(ii,ii);
    }

Note the similarities with the SimpleTexureExJ3D example in Listing 11.9. The only difference is that we instantiated a BufferedImage and accessed the DataBuffer. The changes we made to the DataBuffer are pretty minor. In essence, we made a gradient paint texture. Of course, we really want to do much more sophisticated things with our texture. An animated texture, for example, would be nice. In Chapter 14, we will use JMF to generate a video texture and use the image processing capabilities of JAI to produce special effects.

In building up to that level of sophistication, we will first generate a simple animation by improving upon the BufferedImageTextureJ3D example in Listing 11.10.

Animating Textures Using Texture-by-reference

Texture-by-reference was introduced in Java 3D 2.1. We will need to use this feature when we bring in JAI and JMF objects into our 3D environment.

The basic idea of using texture-by-reference is that Java 3D may not need to make copies of images internally and can simply maintain a reference to the image. Similar to the Geometry-by-reference class described previously, image stored by-reference can be changed at runtime. This can speed performance considerably and is needed for applications in which a dynamic texture is needed. Listing 11.11 illustrates the use of texture-by-reference. Each time a key is pressed, a number of random dots are added to the texture, as shown in Figure 11.8. Eventually, the texture becomes totally covered by spots.

Figure 11.8. Screenshot from AnimatedTextureJ3D.java.


Listing 11.11 AnimatedTextureJ3D.java
import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.behaviors.mouse.MouseRotate;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.awt.image.*;
import java.awt.image.BufferedImage;
import javax.media.j3d.ImageComponent2D;
import java.awt.image.*;

import java.awt.geom.*;

import java.util.Random;

public class AnimatedTextureJ3D extends Applet{
  int imgwidth = 256;
  int imgheight = 256;
  Texture2D tex;
  Random r;
  BufferedImage bi;

  public BranchGroup createSceneGraph() {
    // Create the root of the branch graph
      BranchGroup objRoot = new BranchGroup();

    // Create TransformGroup for scaling children
    TransformGroup objScale = new TransformGroup();
    Transform3D t3d = new Transform3D();
    t3d.setScale(0.5);
    objScale.setTransform(t3d);
    objRoot.addChild(objScale);

    // Create the TransformGroup for rotation; must enable WRITE and READ capability bits

    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

    objScale.addChild(objTrans);

    Appearance app = new Appearance();
    app.setCapability(Appearance.ALLOW_TEXTURE_WRITE);

    bi = new BufferedImage(imgwidth,imgheight,BufferedImage.TYPE_INT_RGB);
    Sphere sphere = new Sphere(1.f, Sphere.GENERATE_NORMALS | Sphere.GENERATE_ 
TEXTURE_COORDS, 60, app);

    objTrans.addChild(sphere);
    tex = new Texture2D(Texture2D.BASE_LEVEL,Texture2D.RGB,imgwidth,imgheight);

    tex.setCapability(Texture2D.ALLOW_IMAGE_WRITE);
    tex.setCapability(Texture2D.ALLOW_IMAGE_READ);

    this.genTexture();
    app.setTexture(tex);

    BoundingSphere bounds =
      new BoundingSphere(new Point3d(0.0,0.0,0.0), 100.0);

    // Create the rotate behavior node
    MouseRotate behavior = new MouseRotate(objTrans);
    objTrans.addChild(behavior);
    behavior.setSchedulingBounds(bounds);

    return objRoot;
  }

  public AnimatedTextureJ3D (){

    r = new Random();

    setLayout(new BorderLayout());
    GraphicsConfiguration config =
       SimpleUniverse.getPreferredConfiguration();

    Canvas3D c = new Canvas3D(config);
    add("Center", c);

    BranchGroup scene = createSceneGraph();

    KeyHandler kh = new KeyHandler(this);
    c.addKeyListener(kh);
    SimpleUniverse u = new SimpleUniverse(c);

    u.getViewingPlatform().setNominalViewingTransform();

    u.addBranchGraph(scene);
  }

  public void genTexture() {

    //instantiate a new ImageComponent; choose byRef and yUp
      ImageComponent2D ic = new ImageComponent2D(ImageComponent2D.FORMAT_RGB,
                                               256,256,true,true);
      //set bi as ImageComponenet object
      ic.set(bi);

      // BufferedImage bi = ic.getImage();
      Graphics2D g2d = bi.createGraphics();

      RenderingHints antialiasHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, 
RenderingHints.VALUE_ANTIALIAS_ON);
      g2d.setRenderingHints(antialiasHints);

      for (int ii=0;ii<1000;ii++) {
        g2d.setPaint(Color.red);
          //generate a random dot size
        double dotsize = r.nextDouble();

        //paint blue and red dots
        g2d.setPaint(Color.red);
        g2d.fill(new Ellipse2D.Double(r.nextDouble()*256,r.nextDouble()*256,
                                        dotsize, dotsize));
        g2d.setPaint(Color.blue);
        g2d.fill(new Ellipse2D.Double(r.nextDouble()*256,r.nextDouble()*256,
                                        dotsize, dotsize));
      }

      ic.set(bi);
      tex.setImage(0,ic);
  }


 public static void main(String argv[]) {
    new MainFrame(new AnimatedTextureJ3D(), 750, 750);
 }
} //create an event handler to respond to key presses
class KeyHandler implements KeyListener {
     AnimatedTextureJ3D at;

     public KeyHandler(AnimatedTextureJ3D at) {
         this.at = at;
     }
     public void keyPressed(java.awt.event.KeyEvent p1) {
        at.genTexture();
     }
  . . .
}

Notice the genTexture() method in the previous example. The genTexture() method is a simple example of Java 2D graphics. Each time the KeyHandler object kh registers a key press, the genTexture() method is called, and the texture is progressively updated.

We will build on the use of texture by reference in our 3D shopping mall example.

Providing Texture Coordinate Information

Regardless of the initial size of the texture (say 128x128, for example), Java 3D maps textures onto the (s, t) coordinate system that goes from 0.f to 1.f.

Two basic modes are used when determining how to treat points outside of the boundary. The first mode is termed CLAMP and dictates that points outside the boundaries use a particular color (specified during creation of the boundaries). The other mode, WRAP, is used for creating repeating patterns such as wall and floor surfaces. In WRAP mode, the texture repeats continuously until the shape is covered.

Methods for specifying which mode to use are provided with the setBoundaryModeS() and setBoundaryModeT() methods. Note that the default is WRAP. You can thus specify WRAP in one direction and CLAMP in the other.

For example, to set the texture boundary mode to CLAMP along the horizontal dimension of a texture, you would use the following:

tex.setBoundaryModeS(Texture,CLAMP);

Note further that it is possible to use an affine transform to scale, translate, or rotate the (s, t) coordinates. This can have the effect of moving the texture across or down the shape in the case of translation, reorienting the image in the case of rotate, or enlarging or shrinking the texture in the case of scale.

The TextureAttributes object is used to specify the texture transformation and other information about the texture map to use. The getTextureTransform(Transform3D t) method will place the current texture transform in the Transform3D t. Similarly, the texture transform can be set with the setTextureTransform(Transform3D t) method.

One of the often used attributes in the TextureAttributes is related to the rendering quality versus speed tradeoff for texture rendering. The attributes are set with the following:

TextureAttributes.FASTEST
TextureAttributes.NICEST

It is also possible, through the TextureAttributes object, to have some control over the blending of textures on a shape. This is done by specifying an alpha value for the textures. Finally, several important flags can be set in the TextureAttributes object. For example,

TextureAttributes.ALLOW_TRANSFORM_READ
TextureAttributes.ALLOW_TRANSFORM_WRITE

are important when using the get and set TextureTransform methods.

MIPMapping

The rationale behind using MIPMapping is presented in Chapter 10.

Java 3D allows two approaches to MIPMapping. The easiest avenue is to specify a single base image and allow Java 3D to automatically generate the rest of the MIPMap images. This is generally acceptable for most applications.

In situations in which this first approach is not usable or in situations in which the programmer wants to use MIPMapping for a different purpose, Java 3D allows the programmer to explicitly set the MIPMap array. Listing 11.12 demonstrates a simple example of explicitly setting the MIPMap array.

Listing 11.12 MIPMapExample.java
import com.sun.j3d.utils.behaviors.mouse.MouseRotate;

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.geometry.Box;
import java.awt.font.*;

import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.awt.image.*;

import java.awt.image.BufferedImage;
import javax.media.j3d.ImageComponent2D;
import java.awt.image.*;

import java.awt.geom.*;

import java.util.Random;
import java.awt.geom.*;

public class MIPMapExample extends Applet{
  int imgwidth = 256;
  int imgheight = 256;
  Texture2D tex;
  BufferedImage bi;

  VirtualUniverse universe;
  Locale locale;
  TransformGroup vpTrans;
  View view;
  Bounds bounds;
  public BranchGroup createSceneGraph() {
    // Create the root of the branch graph
      BranchGroup objRoot = new BranchGroup();

    // Create TransformGroup for scaling children
    TransformGroup objScale = new TransformGroup();
    Transform3D t3d = new Transform3D();
    t3d.setScale(0.5);
    objScale.setTransform(t3d);
    objRoot.addChild(objScale);

    // Create the TransformGroup for rotation; must enable WRITE and READ capability bits

    TransformGroup objTrans = new TransformGroup();
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
    objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);

    objScale.addChild(objTrans);

    Appearance app = new Appearance();
    app.setCapability(Appearance.ALLOW_TEXTURE_WRITE);

   // Sphere sphere = new Sphere(1.f, Sphere.GENERATE_TEXTURE_COORDS, 32, app);
    Box box = new Box(5.f,5.f,5.f, Box.GENERATE_TEXTURE_COORDS, app);
    objTrans.addChild(box);
    tex = new Texture2D(Texture2D.MULTI_LEVEL_MIPMAP,Texture2D.RGB, imgwidth,imgheight);
    //tex = new Texture2D(Texture2D.BASE_LEVEL,Texture2D.RGB,imgwidth, imgheight);
    //tex.setCapability(Texture2D.ALLOW_IMAGE_WRITE);
    //tex.setCapability(Texture2D.ALLOW_IMAGE_READ);

    tex.setMagFilter(Texture2D.BASE_LEVEL_POINT);
    tex.setMinFilter(Texture2D.MULTI_LEVEL_POINT);

    this.genMIPMap();

    app.setTexture(tex);
     MouseRotate behavior = new MouseRotate(objTrans);
    objTrans.addChild(behavior);
    behavior.setSchedulingBounds(bounds);

    return objRoot;
  }

. . .
  public void genMIPMap() {
      System.out.println("genMIPMap");
    //bi = new BufferedImage(imgwidth,imgheight, BufferedImage.TYPE_INT_RGB);

    //Generate a series of n mipmaps
      int [] miplevels = {256, 128, 64, 32, 16, 8, 4, 2, 1};

      for (int ii=0; ii< miplevels.length; ii++) {
          System.out.println("miplevel: ii: " + miplevels[ii] + " " + ii );
          BufferedImage bi = new BufferedImage(miplevels[ii],miplevels[ii], BufferedImage.
TYPE_INT_RGB);

          ImageComponent2D ic = new ImageComponent2D (ImageComponent2D.FORMAT_RGB,
                                               miplevels[ii],miplevels[ii]);

      // BufferedImage bi = ic.getImage();
          Graphics2D g2d = bi.createGraphics();

          RenderingHints antialiasHints = new RenderingHints(RenderingHints.
KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
          g2d.setRenderingHints(antialiasHints);
          g2d.setPaint(Color.blue);
          g2d.fill(new Rectangle2D.Double(0, 0,bi.getWidth(),bi.getHeight()));
          Ellipse2D r = new Ellipse2D.Double(50,50,100,100);
          g2d.setPaint(Color.red);
          g2d.fill(r);
          g2d.setPaint(Color.white);
          Font f1 = new Font("Helvetica", Font.BOLD, 24/(ii+1));
          g2d.setFont(f1);
          String s = "SIZE: " + bi.getHeight() + " x " + bi.getHeight();
          g2d.drawString(s, 2,bi.getHeight()/2);

          ic.set(bi);
          tex.setImage(ii,ic);
      }
  }

Note the interesting and enlightening error that can occur in this example (shown in Figure 11.9). When the object is not “dead on” to the viewer, there exists transitionary zones that have a part of one level of the MIPMap and another part covered by the neighboring level of the MIPMap. When object views are at an angle, two different levels of the MIPMap will apply to individual surfaces at certain distances from the object. Although there are some ways to mitigate this artifact by changing the interpolation methods used, we are in essence exchanging one artifact for another.

Figure 11.9. Screen shot from MIPMapExample.java.


Using Large Textures

Generally, small textures are used for texture mapping. In some cases, however, a large texture is desired. One technique that uses large textures is when a large digital photograph is pasted in the far distance of an environment.

Large textures can be problematic. Most of the problems are hardware related and not a specific limitation of Java 3D. Many graphics accelerators will only work with textures that are less than 256x256. In order to write the most generic program, textures should be kept at less than 256x256. Should a larger texture be needed, it might be necessary to split the texture and the object to be textured into smaller segments. Even if the graphics card will allow big textures, they will tend to eat up all the memory on the card anyway.

Java 3D 1.3 will most likely include methods for querying the maximum texture size supported by a device; therefore, textures that are too large can be scaled appropriately.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset