The Java 3D Appearance
class enables you to set rendering attributes for the geometric primitives in your scene—at startup, during scenegraph creation, or dynamically at runtime. After reading this chapter, you should be able to dynamically modify the appearance of the geometry within your scenegraph.
Java 3D contains a host of classes to specify the rendering attributes for geometric primitives, such as color, texture, back-face removal, and so on. Java 3D defines “has-a” relationships between the classes that control overall rendering appearance. For example, an Appearance
has a Material
class, a PolygonAttributes
class, a RenderingAttributes
class, and so on. This is one of the best-designed areas of Java 3D; with a little experience you can quickly learn to navigate the various classes that, when combined, specify object appearance. Compared with learning the plethora of separate OpenGL methods to specify appearance, the OO nature of the Java 3D design pays dividends.
An instance of an Appearance
object is associated with each Shape3D
geometric object in the scenegraph. The Appearance
object defines rendering state information that must be applied to the rendering pipeline before the raw geometry within the Shape3D
is rendered.
It is useful to think of the Java 3D renderer traversing through the defined scenegraph; when it encounters TransformGroups
, it applies matrix transformations; when it encounters a Shape3D Node
, it applies the Shape3D
’s Appearance
state—all before executing the Shape3D
object to generate native graphics API calls.
What follows is an overview of the NodeComponent
-derived classes that define a Java 3D rendering state for a Shape3D
object. Emphasis is placed on areas of potential confusion or typical problems. Consult the detailed Sun API reference for a method-by-method summary of the classes. I have prefaced each section with a table listing the capability bits that control access to the specific feature being discussed. The tables include references to OpenGL functions where appropriate and useful.
One of the lengthier examples of the book accompanies this chapter: AppearanceTest (illustrated in figure 9.1) allows you to dynamically modify most of the Appearance
attributes on a simple scene. I encourage you to examine the code for the example and run it as you work through this chapter—most of the sections will be significantly clarified by this interactive example.
Appearance
settings to be modified at runtime
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.Appearance
The Appearance
class contains member variables that together define an appearance state for a Shape3D
object. A Shape3D
object basically contains Geometry
and Appearance
. The Appearance
class itself does not define any of the properties that control Shape3D
appearance, but instead packages Appearance
subcomponents, such as PolygonAttributes
, RenderingAttributes
, and so on.
The Appearance
class controls access to its member variables through the familiar, but rigorous, process of setting capability bits. That is, the application developer must specify which Appearance
member variables will be modified after the Appearance
is attached to a live scenegraph. A live scenegraph is one that has either been compiled or has been rendered.
The capability bits that control access to the member variables of the Appearance
class are:
COLORING_ATTRIBUTES
LINE_ATTRIBUTES
MATERIAL
POINT_ATTRIBUTES
POLYGON_ATTRIBUTES
RENDERING_ATTRIBUTES
TEXGEN
TEXTURE_ATTRIBUTES
TEXTURE
TRANSPARENCY
Preface the listed items with ALLOW_
and add _READ
or _WRITE
for read or write access respectively. For example, to allow read/write access to the Material
and PolygonAttributes
member variables, you would put the following in your code:
Appearance app = new Appearance();
app.setCapability( ALLOW_MATERIAL_READ );
app.setCapability( ALLOW_MATERIAL_WRITE );
app.setCapability( ALLOW_POLYGON_ATTRIBUTES_READ );
app.setCapability( ALLOW_ POLYGON_ATTRIBUTES_WRITE );
NOTE |
Four separate calls must be made to the setCapability method—it is not possible to OR together the various capabilities. ORing together capability bits will lead to very unpredictable behavior. |
Public accessor methods to access the member variable are defined as:
app.setMaterial( new Material() );
app.setPolygonAttributes( new PolygonAttributes() );
PolygonAttributes polyAttribs = app.getPolygonAttributes();
Material material = app.getMaterial();
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.ColoringAttributes
COLOR |
SHADE_MODE |
OpenGL Reference: glColor |
The ColoringAttributes
within a Shape3D
’s Appearance
are used to control the color of a Shape3D
if a Material
has not also been assigned to the Appearance
. If a Material
has been assigned, the ColoringAttributes
are ignored and the more complex color and lighting information within the Material
class are used instead. See table 9.1.
The colors of any vertices within the Shape3D
that have per-vertex colors applied are unchanged by the ColoringAttributes
.
The next example, from AppearanceTest.java, creates a standard Box
and then replaces the LEFT
face with a new face created with new geometry and per-vertex colors. When a ColoringAttributes
object is assigned to the Appearance
for the Box
it will set the color for the five original faces but will not effect the LEFT
face which has per-vertex colors assigned, as illustrated in figure 9.2.
Box
primitive with an applied ColoringAttribute
. The LEFT
face has been removed and replaced with a QuadArray
that includes per-vertex colors that are unaffected by the ColoringAttributes
int nScale = 50;
Box box = new Box( nScale,nScale,nScale,
Primitive.GENERATE_NORMALS |
Primitive.GENERATE_TEXTURE_COORDS,
m_Appearance);
Shape3D frontFace = box.getShape( Box.LEFT );
//create a new LEFT face so we can assign per-vertex colors
GeometryArray geometry =
new QuadArray( 4, GeometryArray.COORDINATES |
GeometryArray.NORMALS |
GeometryArray.COLOR_4 |
GeometryArray.TEXTURE_COORDINATE_2 );
nScale = 40;
final float[] verts =
{
// new LEFT face
-1.0f * nScale, -1.0f * nScale, 1.0f * nScale,
-1.0f * nScale, 1.0f * nScale, 1.0f * nScale,
-1.0f * nScale, 1.0f * nScale, -1.0f * nScale,
-1.0f * nScale, -1.0f * nScale, -1.0f * nScale
};
final float[] colors =
{
// left face
1,0,0,0,
0,1,0,0.2f,
0,0,1,0.8f,
0,0,0,1,
};
float[] tcoords =
{
// texture coordinates for LEFT face
1, 0,
1, 1,
0, 1,
0, 0
};
Vector3f normalVector = new Vector3f(-1.0f, 0.0f, 0.0f);
geometry.setColors( 0, colors, 0, 4 );
for( int n = 0; n < 4; n++ )
geometry.setNormal( n, normalVector );
geometry.setTextureCoordinates( 0, tcoords, 0, 4 );
geometry.setCoordinates( 0, verts );
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.LineAttributes
ANTIALIASING |
PATTERN |
WIDTH |
OpenGL Reference: glLineStipple, glLineWidth |
The LineAttributes
class controls the style of lines used to draw the edges of surfaces. See table 9.2. The available styles are:
To see the effect of the LineAttributes
class, the Appearance
must be set to render in LINE
(wire frame) mode:
Appearance app = new Appearance();
PolygonAttributes polyAttribs = new PolygonAttributes( PolygonAttributes.POLYGON_LINE, PolygonAttributes.CULL_NONE, 0 );
app.setPolygonAttributes(polyAttribs );
See section 9.7.1 for more detail on PolygonAttributes
. Figures 9.3–9.6 show examples rendered using various LineAttribute
styles.
LINE
mode with a null LineAttributes
LineAttributes
of width 10 without antialiasing
LineAttributes
of width 10 with antialiasing
LineAttributes
of width 2 with a Dash Dot pattern
The lines rendered in LINE
mode are effected by color, lighting, and texture applied to surfaces.
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.Material
COMPONENT |
OpenGL Reference: glColorMaterial, glMaterial |
The Material
class specifies surface rendering characteristics (table 9.3) using the following parameters:
Shape3D
.
The effects of the various colors specified within the Material
are described in detail in the context of lighting in chapter 10. Figure 9.7 shows a cube rendered with an applied Material
, and per-vertex colors on four vertices of the cube.
Appearance
with an applied Material
with Ambient, Diffuse, Emissive, and Specular colors. Lighting calculations determine the shade of each surface
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.PointAttributes
ANTIALIASING |
SIZE |
OpenGL Reference: glPointSize, GL_POINT_SMOOTH |
The PointAttributes
class specifies rendering information for rendered points (table 9.4). PointAttributes
encapsulates two appearance properties:
If antialiasing is enabled, points are rendered as circular; if antialiasing is disabled, points are rendered as square (size pixels by size pixels) as shown in figures 9.8 and 9.9.
PointAttributes
NOTE |
The time taken to render a point (at least without hardware acceleration) is proportional to the size of the point in pixels. |
Note that a single point, when antialiased, approximates a sphere, in that it is never possible to view the point “edge-on” as a disc. When points are rendered without antialiasing, the point approximates a cube with each front face always perpendicular to the viewer. Figures 9.10 and 9.11 illustrate.
The figures are taken from the PointTest.java example. The example defines the points to be rendered, as well as Appearances
, as follows:
//create a BranchGroup containing an nNumPoints x nNumPoints
//array of points
//the size of the points is set to nPointSize and antialiasing
//is set to bAliased
private BranchGroup createPoints( final int nPointSize, final int
nNumPoints, boolean bAliased )
{
BranchGroup bg = new BranchGroup();
//create a Text3D label describing the points
String szText = new String();
szText += ( nNumPoints + "X, Size:" + nPointSize + ", aliased: "
+ bAliased );
Font3D f3d = new Font3D( new Font( "SansSerif", Font.PLAIN, 1), new
FontExtrusion() );
Text3D label3D = new Text3D( f3d, szText, new Point3f(-5,0,0) );
Shape3D sh = new Shape3D( label3D );
bg.addChild( sh );
//create the PointArray used to hold the geometry for the points
PointArray pointArray = new PointArray( nNumPoints * nNumPoints,
GeometryArray.COORDINATES | GeometryArray.COLOR_3 );
//populate the PointArray that we will be rendering
int nPoint = 0;
final double factor = 1.0 / nNumPoints;
for( int n = 0; n < nNumPoints; n++ )
{
for( int i = 0; i < nNumPoints; i++ )
{
Point3f point = new Point3f( n - nNumPoints/2,
i - nNumPoints/2, 0.0f );
pointArray.setCoordinate( nPoint, point );
pointArray.setColor( nPoint++,
new Color3f( 0.5f, (float) (n * factor),
(float) (i * factor) ) );
}
}
//create the Appearance for the points
Appearance pointApp = new Appearance();
//enlarge the points and set antialiasing
pointApp.setPointAttributes( new PointAttributes
( nPointSize, bAliased ) );
//create a Shape3D for the PointArray and assign the appearance
Shape3D pointShape = new Shape3D( pointArray, pointApp );
//add the Shape3D to the BranchGroup and return
bg.addChild( pointShape );
return bg;
}
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.PolygonAttributes
CULL_FACE |
MODE |
NORMAL_FLIP |
OFFSET |
OpenGL Reference: glCullFace, glFrontFace, glPolygonMode |
The PolygonAttributes
class encapsulates properties for how polygons are rendered (table 9.5). Polygon rendering is controlled by the following properties:
Appearance
away from the eye (viewer) by the specified amount. By specifying an offset on one Shape3D
it can be moved in front of or behind the other Shape3D
. Because of differences in OpenGL/DirectX hardware implementation, however, it is very difficult to achieve consistent results using this method.
For example, to allow write access to the CULL_FACE
property, use the following:
PolygonAttributes polyAttribs = new PolygonAttributes();
polyAttribs.setCapability(PolygonAttributes.ALLOW_CULL_FACE_WRITE );
CULL_BACK |
CULL_FRONT |
CULL_NONE |
Cull face parameters (table 9.6) can be implemented as follows:
polyAttribs.setCullFace( PolygonAttributes.CULL_BACK );
POLYGON_FILL |
POLYGON_LINE |
POLYGON_POINT |
Polygon fill-mode parameters are shown in table 9.7. For example, to set line-only (wire frame) mode, use the following:
polyAttribs.setPolygonMode( PolygonAttributes.POLYGON_LINE );
Figures 9.12 through 9.15 illustrate how normal vector flipping can influence both the lighting calculations, (compare the shading of the horizontal faces in figure 9.12 with figure 9.13), and surface culling (figure 9.14, 9.15).
CULL_NONE
, NORMAL_FLIP
= false
CULL_NONE
, NORMAL_FLIP
= true
CULL_FRONT
, NORMAL_FLIP
= false
CULL_FRONT
, NORMAL_FLIP
= true
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.RenderingAttributes
ALPHA_TEST_FUNCTION |
ALLOW_ALPHA_TEST_VALUE |
ALLOW_DEPTH_ENABLE |
OpenGL Reference: glAlphaFunc |
The RenderingAttributes
class allows pixels within the final rendered scene to be included or excluded based on the pixel Alpha (transparency) value (table 9.8).
Assuming an Alpha test value of A
, the test conditions listed in table 9.9 and shown in figure 9.16 are available.
RenderingAttributes
to control the portions of a QuadArray
that are rendered. Vertex 0 has transparency 0.0, vertex 1 has transparency 0.2, vertex 2 has transparency 0.8, and vertex 3 has transparency 1.0. In all cases the Alpha
test value was set to 0.5
Alpha test function | Meaning |
---|---|
ALWAYS | Always render pixel regardless of A |
EQUAL | Render pixel if pixel transparency = A |
GREATER | Render pixel if pixel transparency > A |
GREATER_OR_EQUAL | Render pixel if pixel transparency >= A |
LESS | Render pixel if pixel transparency < A |
LESS_OR_EQUAL | Render pixel if pixel transparency <= A |
NEVER | Never render pixel regardless of A |
NOT_EQUAL | Render pixel if pixel transparency != A |
The interpolation of transparency values across primitives (quads or triangles) can sometimes be surprising. Figure 9.17 illustrates what happens to a QuadArray
when it is rotated. In this case vertexes 0 and 1 have a transparency of 0.0, and vertexes 1 and 2 have a transparency of 1.0.
Alpha
test value is 0.5 and Alpha
test function is LESS
. Note that, as the QuadArray
rotates, the transparency interpolation changes as a function of the rotation and an apparent spike appears in the middle of the quad
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.TexCoordGeneration
ENABLE |
FORMAT |
MODE |
PLANE |
OpenGL Reference: glTexGen |
Java 3D’s texture capabilities (table 9.10) are discussed in detail in chapter 14.
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.TextureAttributes
BLEND_COLOR |
MODE |
TRANSFORM |
OpenGL Reference: glTexParameter |
Java 3D’s texture capabilities (table 9.11) are discussed in detail in chapter 14.
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.Texture
BOUNDARY_COLOR |
ENABLE |
FILTER |
IMAGE |
MIPMAP_MODE |
OpenGL Reference: glTexImage2D. |
Java 3D’s texture capabilities (table 9.12) are discussed in detail in chapter 14.
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.TransparencyAttributes
MODE |
VALUE |
OpenGL Reference: glBlendFunc |
The TransparencyAttributes
class allows the transparency of objects to be specified. Transparency is set using an Alpha value (table 9.13). Alpha values range from 0.0, representing complete opacity, to 1.0, representing complete transparency. Like color, transparency can either be set on a per-vertex basis or, by using the TransparencyAttributes
class, for an entire Shape3D
. Per-vertex transparency (set using COLOR_4
per-vertex colors) takes precedence over the transparency value specified in the Shape3D
’s TransparencyAttributes
. The examples to follow will help to illustrate this.
Transparency should be used with care, because, as the OpenGL reference indicates, it is a blending operation between pixels that have their source geometry located at different positions within the scene.
Using transparency slows rendering considerably because the renderer must track the transparency of every pixel in the frame. Without transparency the color of a pixel in the frame is dictated by the color of the geometry closest to the viewer. All other geometry that would be mapped into the frame pixel location can be discarded since it is occluded by the geometry closest to the viewer. If some of the elements in the scenegraph are transparent, however, the renderer must perform a sequence of complex blending operations between the colors generated for a single pixel in the frame.
Sphere
primitives with varying transparency, from left to right 100%, 70%, 50%, 30%, and 10%. All of the Spheres
have the same color (black)
Here is what the Microsoft/Silicon Graphics OpenGL reference says about using transparency: “Transparency is best implemented using glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
with primitives sorted from farthest to nearest. Note that this transparency calculation does not require the presence of alpha bitplanes in the frame buffer. You can also use glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
for rendering antialiased points and lines in arbitrary order.” The key phrase in the description is, “with primitives sorted from farthest to nearest.” This is where the problems with using transparency in Java 3D occur. Because transparency is a Shape3D
object attribute, Java 3D users naturally assume that they merely have to set the object or vertex transparency and Java 3D will take care of everything else. Sadly, this is not the case, as Java 3D provides very little support for sorting the primitives “from farthest to nearest.” This should be changing however, as Java 3D 1.3 will implement depth-sorted transparency—as demonstrated as a Sun presentation at the JavaOne 2001 conference.
Traditional 3D rendering techniques rely on the Z-buffer to ensure objects nearer to the viewer are rendered on top of objects further away.
The Z-buffer is essentially an array of depth values, one element in the array for each pixel in the frame. As the scene is rendered, the Z-buffer is updated with the distance of the source location for each pixel from the viewer. The Color buffer tracks the color of the closest source location for each pixel in the frame, as shown in the following sequences:
In this way, when all the geometry in the scene has been prerendered, the Z-buffer contains the depth values for each pixel to the nearest geometry and the Color buffer contains the color for each pixel. When transparency is used, however, the color of a pixel cannot be solely determined from the color of the closest geometry; instead, a complex blend of the colors of all geometry gets mapped to the pixel location. In general, the blending will only be calculated correctly if the geometry is rendered from back to front, which is of course view-dependent.
The problem therefore becomes one of sorting geometry from front to back before rendering takes place. For general scenes this is extremely difficult, especially for scenes that are composed of overlapping or interpenetrating objects. Such objects will have to be decomposed into nonpenetrating subsections before rendering.
Without sorting, you will only get reasonable results if most of your objects are opaque and you have only a few, nonoverlapping, transparent surfaces. The Java 3D rendering order is as follows:
OrderedGroups
)
Within the opaque and transparent groups no sorting is performed. Therefore, transparent objects will overlap opaque or ordered objects in front of them, whereas, because transparent objects are not depth-sorted, there are no guarantees that transparent objects will be rendered correctly.
Figures 9.19 through 9.29 illustrate some of the potential rendering problems. The scene for the following figures was composed of a single Box
primitive with one face (LEFT
) removed. The LEFT
face was replaced with a new, slightly smaller face that included per-vertex colors (COLOR_4). The code used to replace the LEFT
face follows:
//create a Box with Normal vectors and texture coordinates
Box box = new Box(nScale,nScale,nScale,
Primitive.GENERATE_NORMALS |
Primitive.GENERATE_TEXTURE_COORDS, m_Appearance );
Shape3D frontFace = box.getShape( Box.LEFT );
//create a new left face so we can assign per-vertex colors
GeometryArray geometry = new QuadArray( 4, GeometryArray.COORDINATES |
GeometryArray.NORMALS |
GeometryArray.COLOR_4 |
GeometryArray.TEXTURE_COORDINATE_2 );
nScale = 40;
//define the geometry for the left face
final float[] verts =
{
-1.0f * nScale, -1.0f * nScale, 1.0f * nScale,
-1.0f * nScale, 1.0f * nScale, 1.0f * nScale,
-1.0f * nScale, 1.0f * nScale, -1.0f * nScale,
-1.0f * nScale, -1.0f * nScale, -1.0f * nScale
};
//define the colors for the left face. Note we are using RGBA
//colors and include per-vertex transparency
final float[] colors =
{
1,0,0,0,
0,1,0,0.2f,
0,0,1,0.8f,
0,0,0,1,
};
//define the texture coordinates for the left face
float[] tcoords =
{
1, 0,
1, 1,
0, 1,
0, 0
};
//define the normal vector for the new left face
Vector3f normalVector = new Vector3f(-1.0f, 0.0f, 0.0f);
//assign the colors to the QuadArray
geometry.setColors( 0, colors, 0, 4 );
//assign the normal vector for each vertex in the QuadArray
for( int n = 0; n < 4; n++ )
geometry.setNormal( n, normalVector );
//assign the texture coordinates for each vertex in the QuadArray
geometry.setTextureCoordinates( 0, tcoords, 0, 4 );
//finally, assign the vertices themselves into the QuadArray
geometry.setCoordinates( 0, verts );
NOTE |
All rendering was performed with PolygonAttributes.CULL_NONE; so all the faces of the Box were rendered. |
Consider carefully what you are trying to achieve if you choose to use transparency as an Appearance
attribute in your scene. It is very difficult to know exactly what will be rendered by Java 3D in either NICEST or SCREEN_DOOR mode, and without some form of application-specific sorting algorithm, which must be carried out potentially on every frame, problems will probably occur.
The issues with transparency really do not reside at the Java3D level, but Java 3D has not done a good job of insulating the Java 3D developer from the underlying issues in the OpenGL/DirectX implementation. I am hopeful that Sun can address many of these issues in Java 3D 1.3.
QuadArray
with per-vertex colors is unaffected
QuadArray
with per-vertex colors
Box
(100% opaque) and the QuadArray
(per-vertex transparency). Even though the QuadArray
should be visible at the front of the Box
, it is being rendered behind the Box
. This would be a case that could be solved using a simple sorting algorithm because the centroid of the Box
is behind the centroid of the QuadArray
and hence it should be rendered before the QuadArray
Box
is semitransparent, the problem with rendering order is not apparent because you can see the QuadArray
through the Box
Box
and rotated it so that the QuadArray
is now at the rear of the Box
. No problems apparent here
Box
and rotated it so that the QuadArray
is now at the rear of the Box
. Using SCREEN_DOOR transparency, the QuadArray
has now disappeared
Java 3D provides you with a rich set of capabilities for controlling the appearance of your geometry. By combining aspects of the Appearance
class, such as materials, transparency, textures, or rendering attributes, you can achieve visually effective scenes with very little programming.
The high-level object-oriented API allows you to easily navigate through the dozens of appearance capabilities and is a real productivity improvement over a lower-level API such as OpenGL. In some areas (for example, transparency), however, it can be important to understand what is going on under the covers, as Java 3D does a poor job at insulating you from the peculiarities of the underlying implementation or providing adequate documentation. If you require additional information I strongly recommend a good 3D graphics text as well as an OpenGL/DirectX reference.