Software Development

Create graphics applications with Java 3D

The Java 3D API allows you to develop 3D graphics applications that have a high degree of visual realism. This article describes a scene that is constructed using a scene graph, which is structured as a tree containing several elements that are necessary to display the objects.

The Java 3D API allows you to develop 3D graphics applications that have a high degree of visual realism. Since version 1.2, Java 3D is developed under the Java Community Process. Java 3D runs on top of OpenGL or Direct3D; it's also an interface that encapsulates graphics programming using a real, object-oriented concept. In addition, Java 3D offers extensive 3D sound support.

This article describes a scene that is constructed using a scene graph, which is a representation of the objects that have to be shown. This scene graph is structured as a tree containing several elements that are necessary to display the objects.

3D world mechanisms

The design of Java 3D is significantly different from popular 3D graphics APIs such as OpenGL and Direct3D, which are low-level procedural APIs that are closely tied to the design of 3D hardware. With Java 3D, you set up all your graphics objects (also called geometry objects) in a scene graph, which is a hierarchical model containing all the information about the objects in your scene and how they will be rendered. Then, you hand the scene graph over to Java 3D for rendering. You don't have to write any code to handle displaying your data -- Java 3D does it for you. You get to program at a higher level with the many built-in power tools.

Java 3D can take advantage of any 3D acceleration that your graphics adapter provides. Java 3D ultimately generates OpenGL calls in a JNI layer that can be accelerated by your graphics card. OpenGL accelerated adapters are common in newer workstations, so your Java 3D programs should be hardware accelerated.

In a three-dimensional coordinate (x,y,z), the z component specifies distance from the viewer. Java 3D uses z values to remove nonvisible surfaces of distance obscured objects. The z values of the red torus in Figure A are small because it is close to the viewer. It will obscure portions of the blue torus when the z values of both tori are compared during rendering. Figure A

Java 3D API

3D objects

A 3D object contains a collection of coordinates rendered together. The Primitive class is an abstract class for geometry objects that can be used as simple building blocks in your scene graph. Java 3D includes four useful concrete subclasses of Primitive -- Sphere, Box, Cone, and Cylinder -- that allow you to easily create basic objects without having to specify a lot of data. For example, when using the Sphere class, you simply specify a radius, and all the vertex data is generated for you. If you are not using one of the Primitive classes, you'll have to use the Shape3D class to specify all of the vertex data. You can specify data as triangles, quadrilaterals, lines, and points. A geometric representation of a sphere would be defined as a polygonal mesh, typically using strips of connected triangles or quads.

At a minimum, every vertex must have a location value (coordinate). In addition to location values, you can specify other items for each vertex, such as a color value, normal vector, and texture coordinates. Normal vectors are used for lighting effects, and texture coordinates are used when applying textures to the surface via texture mapping. Each vertex could also have an alpha, or transparency, value specified with its color. When you use the Primitive classes, vertex normals and texture coordinates are generated for you.

While you can specify a great deal of data with each vertex, many of your graphics effects are applied using the Appearance object. This object describes the overall attributes of an object's surface. Each Shape3D and Primitive object will have its own Appearance object, and each Appearance object contains several attribute objects. For example, an Appearance object can contain both a ColoringAttributes object and a RenderingAttributes object.

Transformations, lightning, and texturing

When the objects are ready and you want them to be displayed, you can move and scale them by using 3D transformations -- in essence, animating the objects. The location, direction, and orientation of your view is called the viewpoint. Transformations are specified as matrices in the powerful Transform3D class. Transform3D has many helper functions for specifying common transformations, such as translations, rotations, and scaling.

The setTranslation(Vector3f trans) method translates (moves) an object by replacing the translate values of this transform with the x, y, and z values in the trans argument. The setScale(double scale) method sets the scale of this transform. You should use this function to resize an object. The rotX(double angle) method sets the rotational component to a counterclockwise rotation around the X axis.

In addition to specifying what objects appear in your scene, you can also control how they appear by specifying lighting effects. You can specify the type of lighting effect, like a spotlight, and the color of the light. You can also apply fog effects to your scene and set up automated behaviors of your objects.

Texture mapping (commonly referred to as wallpapering) is used to provide more realism to a scene. For instance, you can apply a wood grain image on an object's surface to simulate an oak table top. To use texture mapping, you need to specify the image, where to paste it on the object, and what to do if the image doesn't fit quite right, such as when applying a rectangular bitmap to a non-rectangular polygon. Java 3D has simplified the process of loading texture images. The TextureLoader class is in the Java 3D utility classes.

TextureLoader texLoader = new TextureLoader(url,imageobserver)
appearance.setTexture(texLoader.getTexture());

You should always set the width and height of your texture image to a power of 2 for a realistic look. Other values will cause TextureLoader to squish the texture.

Lights are used to illuminate the geometry objects in your scene. There are four types of lights, which are all subclasses of the abstract Light class. All lights have a color value, an on/off bit, and a bounding object that describes what areas of the scene it illuminates. In the real world, the objects around you are lit by several light sources. For instance, sunlight and an overhead light will affect the color and appearance of objects in a room. In Java 3D, you can simulate realistic lighting effects by using multiple light sources. You can use the following types of light in your application:

  • An AmbientLight is everywhere in the scene. It does not originate from a particular point, and it does not point in a particular direction.
  • A PointLight radiates from a specified location in all directions and diminishes with distance. An example of a point light is a desk lamp with no lampshade.
  • A SpotLight is a type of point light that restricts the light to a cone shape. An example of a spot light is a flashlight.
  • A DirectionalLight shines in a particular direction but doesn't emanate from any particular location. All light rays of a directional light travel in parallel. The sun is technically a point light source, but sunlight can be more accurately imitated using a DirectionalLight.
The teapot in Figure B has ambient light (you can see that the back side is illuminated slightly), and directional light shining on the front. Both lights affect the final color of each triangle on the surface of the teapot. Figure B

Java 3D API

Material properties

Material properties describe how an object reflects light. If your object (Primitive or Shape3D) does not have a Material object in its Appearance object, it will not be illuminated even though you are specifying a light source. You must create a Material object, enable lighting in the Material object, and add the Material object to the Appearance object. The Material object is one of several attribute sets that are held in the Appearance object.

To better understand how material properties affect an object's appearance, think about a shiny ruby gemstone object and a red carpet. While they are both red, they will reflect light differently -- the ruby will have a bright highlight where the light bounces off it, and the carpet will appear to scatter the light. To specify this difference in appearance to Java 3D, you'd give the ruby a high shininess value in the Material object and the carpet a very low shininess value.

A surface normal is a vector that is perpendicular to the surface at the vertex, representing the orientation of the surface at that vertex. The surface normal affects how light is reflected from a surface when calculating lighting effects. The surface normals and position of the viewer determine where the shiny highlight (or specular reflection) is located on a sphere. The good news is that the Primitive classes will generate surface normals for you.

3D scenes

A Java 3D scene graph is a tree with two parts or branches: view and content. The view branch contains all of the gory details of the complex Java 3D viewing model, and it defines the viewpoint. The content branch describes what you see in your scene. It contains all your graphics objects (spheres, boxes, or more complicated geometry objects), as well as the transformations that move them around (i.e., lights, behaviors, group nodes, and fog). For most simple applications, you can use the universe utility classes; the most interesting of which is SimpleUniverse class.

The source code below generates a scene graph with the cylinders that make up the axis and three cones. Then it adds one cone with rotate before translate and another that has translate before rotate. Note: This is an applet code to be shown in the AppletViewer or Internet browser. You can easily convert this program into an independent application.
import java.applet.Applet;

import java.awt.BorderLayout;

import java.awt.event.*;

import java.awt.*;

import java.awt.GraphicsConfiguration;

import com.sun.j3d.utils.applet.MainFrame;

import com.sun.j3d.utils.geometry.*;

import com.sun.j3d.utils.universe.*;

import javax.media.j3d.*;

import javax.vecmath.*;

import com.sun.j3d.utils.behaviors.vp.*;

public class TransformOrder extends Applet {

    public static final int X =1;

    public static final int Y =2;

    public static final int Z =3;

    public static final int ROTATE_TOP    =4;

    public static final int TRANSLATE_TOP =5;

    public static final int NO_TRANSFORM  =6;

    private SimpleUniverse universe ;

    private BranchGroup scene;

    private Canvas3D canvas;

    private BoundingSphere bounds =

            new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 1000.0);

   

    private Appearance red = new Appearance();

    private Appearance yellow = new Appearance();

    private Appearance purple = new Appearance();

    Transform3D rotate = new Transform3D();

    Transform3D translate = new Transform3D();

   

    public void setupView() {

        /** Add some view related things to view branch side

        of scene graph */

        // add mouse interaction to the ViewingPlatform

        OrbitBehavior orbit = new OrbitBehavior(canvas,

                OrbitBehavior.REVERSE_ALL|OrbitBehavior.STOP_ZOOM);

        orbit.setSchedulingBounds(bounds);

       

        ViewingPlatform viewingPlatform = universe.getViewingPlatform();

        // This will move the ViewPlatform back a bit so the

        // objects in the scene can be viewed.

        viewingPlatform.setNominalViewingTransform();

        viewingPlatform.setViewPlatformBehavior(orbit);

        }

    //construct each branch of the graph, changing the order children added

    // since  Group node can only have one parent, have to construct

    // new translate and rotate group nodes for each branch.

    Group rotateOnTop(){

          Group root=new Group();

          TransformGroup objRotate = new TransformGroup(rotate);

          TransformGroup objTranslate = new TransformGroup(translate);

          Cone redCone= 

              new Cone(.3f, 0.7f, Primitive.GENERATE_NORMALS, red);

          root.addChild(objRotate);

          objRotate.addChild(objTranslate);

          objTranslate.addChild(redCone); //tack on red cone

          return root;

    }

    Group translateOnTop(){

          Group root=new Group();

          TransformGroup objRotate = new TransformGroup(rotate);

          TransformGroup objTranslate = new TransformGroup(translate);

          Cone yellowCone= 

              new Cone(.3f, 0.7f, Primitive.GENERATE_NORMALS, yellow);

          root.addChild(objTranslate);

          objTranslate.addChild(objRotate);

          objRotate.addChild(yellowCone); //tack on yellow cone

          return root;

   

    }

   

    Group noTransform(){

          Cone purpleCone= 

              new Cone(.3f, 0.7f, Primitive.GENERATE_NORMALS, purple);

          return purpleCone;

    }

    /** Represent an axis using cylinder Primitive. Cylinder is

         aligned with Y axis, so we have to rotate it when

         creating X and Z axis

    */

    public TransformGroup createAxis(int type) {

         //appearance and lightingProps are used in

         //lighting. Each axis a different color

         Appearance appearance = new Appearance();

         Material lightingProps = new Material();

        Transform3D t = new Transform3D();

         switch (type) {

            case Z:

              t.rotX(Math.toRadians(90.0));

               lightingProps.setAmbientColor(1.0f,0.0f,0.0f);

            break;

            case Y:

              // no rotation needed, cylinder aligned with Y already

               lightingProps.setAmbientColor(0.0f,1.0f,0.0f);

            break;

            case X:

              t.rotZ(Math.toRadians(90.0));

               lightingProps.setAmbientColor(0.0f,0.0f,1.0f);

            break;

            default:

            break;

         }

         appearance.setMaterial(lightingProps);

        TransformGroup objTrans = new TransformGroup(t);

         objTrans.addChild( new Cylinder(.03f,2.5f,Primitive.GENERATE_NORMALS,appearance));

         return objTrans;

    }

    /** Create X, Y , and Z axis, and 3 cones. Throws in

         some quick lighting to help viewing the scene

    */

    public BranchGroup createSceneGraph() {

         // Create the root of the branch graph

         BranchGroup objRoot = new BranchGroup();

         //45 degree rotation around the X axis

        rotate.rotX(Math.toRadians(45.0));

         //translation up the Y axis

         translate.setTranslation(new Vector3f(0.0f,2.0f,1.0f)); //SCD 0.0f));

        //Material objects are related to lighting, we'll cover

        //that later

        Material redProps = new Material();

        redProps.setAmbientColor(1.0f,0.0f,0.0f); //red cone

         red.setMaterial(redProps);

        Material yellowProps = new Material();

        yellowProps.setAmbientColor(1.0f,1.0f,0.0f); //yellow cone

         yellow.setMaterial(yellowProps);

        Material purpleProps = new Material();

        purpleProps.setAmbientColor(0.8f,0.0f,0.8f); //purple cone

         purple.setMaterial(purpleProps);

         // Create a x,y,z axis, and then 3 cone branches

         objRoot.addChild(createAxis(X));

         objRoot.addChild(createAxis(Y));

         objRoot.addChild(createAxis(Z));

         objRoot.addChild(noTransform());    //purple cone

         objRoot.addChild(rotateOnTop());      //red cone

         objRoot.addChild(translateOnTop());   //yellow cone

         //throw in some light so we aren't stumbling

         //around in the dark

         Color3f lightColor = new Color3f(.3f,.3f,.3f);

        AmbientLight ambientLight= new AmbientLight(lightColor);

        ambientLight.setInfluencingBounds(bounds);

        objRoot.addChild(ambientLight);

        DirectionalLight directionalLight = new DirectionalLight();

        directionalLight.setColor(lightColor);

        directionalLight.setInfluencingBounds(bounds);

        objRoot.addChild(directionalLight);

         return objRoot;

    }

    public TransformOrder() {

    }

    public void init() {

         BranchGroup scene = createSceneGraph();

         setLayout(new BorderLayout());

        GraphicsConfiguration config =

           SimpleUniverse.getPreferredConfiguration();

         canvas = new Canvas3D(config);

         add("Center", canvas);

         // Create a simple scene and attach it to the virtual universe

         universe = new SimpleUniverse(canvas);

         setupView();

         universe.addBranchGraph(scene);

    }

    public void destroy() {

         universe.removeAllLocales();

    }

    //

    // The following allows TransformOrder to be run as an application

    // as well as an applet

    //

    public static void main(String[] args) {

         new MainFrame(new TransformOrder(), 256, 256);

    }

}

Additional resources about Java 3D

Peter V. Mikhalenko is a Sun certified professional who works as a business and technical consultant for several top-tier investment banks.

---------------------------------------------------------------------------------------

Get Java tips in your inbox Delivered each Thursday, our free Java Developer newsletter provides insight and hands-on tips you need to unlock the full potential of this programming language. Automatically subscribe today!
2 comments
gcarter
gcarter

Can anybody recommend any excellent or good references (books, tutorials, papers, etc.)for someone who has some java experience, but no Java 3D experience? Thanks, gc

psychoreggae
psychoreggae

I've looked awhile for a good, in-depth book on J3D and have been frustrated. However, I find that the user forums at http://forums.java.net/jive/forum.jspa?forumID=70 can be pretty helpful though. Graphics programming using a scene graph as opposed to traditional GL/D3D styles of programming really does require a dramatically different way of thinking about your program. There are a lot of critical concepts/mechanisms like TransformGroups, Interpolators, ViewPlatforms, and updateCallback methods (to name but a few) that don't have an obvious analogue in traditional graphics programming approaches. There's a lot of depth to these concepts but in the sources I've come across so far they seem to receive only a functional, "how do I do something?" treatment as opposed to an in depth examination of their foundations. There are not that many books available on the topic of J3D and the few that I've checked out seem to just do GL-style approaches within J3D without really making the cognitive leap to the scene graph paradigm. Admittedly, I haven't done an exhaustive search of the available literature though, so I too would welcome suggestions for good Books and/or Tutorials. What I wish for is the equivalent of the OpenGL Red Book except for J3D. That said, I just ordered Computer Graphics Using Java 2D and 3D by Y. Daniel Liang, Hong Zhang and have my hopes up.

Editor's Picks