Sep 18, 2008 2:00 AM PT

Open source Java projects: Java Binding for OpenGL (JOGL)

A low-level Java API for 3D graphics

If you're planning to develop a game engine or other Java software that needs 3D graphics, consider using Java Binding for OpenGL (JOGL). This low-level Java API acts as a bridge to the underlying platform's OpenGL technology. In this installment of the Open source Java projects series, Jeff Friesen introduces JOGL. Learn how to use JOGL to bring OpenGL's high performance and powerful features (such as shaders) to your Java-based games.

The open source Java Binding for OpenGL (JOGL) project introduces hardware-supported 3D graphics to Java programs running on Solaris, Linux, Windows, and Mac OS X platforms. JOGL provides access to the OpenGL 2.0 APIs, and to most OpenGL vendor extensions. Also, JOGL supports Java 1.4 and later versions.

Here are some more facts about JOGL:

  • JOGL provides AWT and Swing components for integrating 3D graphics into GUIs.
  • JOGL supports the OpenGL Architecture Review Board (ARB)'s GLSL and Nvidia's Cg shader languages.
  • JOGL only supports truecolor (15 bits per pixel and higher) rendering.
  • JOGL uses New I/O (NIO) internally in its implementation.
  • JOGL is written almost entirely in Java. Most of its native code is autogenerated during the build process by a tool called GlueGen.
  • JOGL supports some of the most popular features introduced by Magician, GL4Java, and other Java bindings for OpenGL. For example, JOGL supports Magician's "composable pipeline" paradigm, which can provide faster debugging for JOGL applications.

JOGL past, present, and future

JOGL originated as a project named Jungle, which was created by 3D graphics experts Ken Russell (of Sun Microsystems) and Chris Kline (of Irrational Games). JOGL is currently maintained on the java.net-hosted JOGL project site, under the leadership of Russell. JOGL is evolving into the official reference implementation for JSR 231: Java Binding for the OpenGL API.

This article explores JOGL by first showing you how to obtain the current release build, and how to compile and run JOGL-based applications. After discussing JOGL's demos, the article teaches you how to create your own JOGL programs, animate their scenes, and take pictures of these scenes. Finally, you examine JOGL's texture support.

Lack of space prevents me from providing a comprehensive overview of JOGL and its foundations in OpenGL. To learn more about JOGL, I recommend that you read the JOGL user guide (located in the release build archive). To learn about OpenGL, I recommend that you read the OpenGL programming guide -- version 1.1 is freely available on the Web.

Get started with JOGL

The JOGL project site's main page presents a link to the current release build of the JOGL software. Follow this link to download a release build for your platform, along with documentation, demos, source code, and signed JAR files for use with Java Web Start. For example, I downloaded the version 1.1.1 release build for my Windows platform.

Open source licenses

Each of the open source Java projects covered in this series is subject to a license, which you should understand before integrating the project with your own projects. JOGL is subject to the Berkeley Software Distribution (BSD) License.

Specifically, I downloaded and unzipped the jogl-1.1.1-windows-i586.zip release build archive. The resulting jogl-1.1.1-windows-i586 home directory contains some text files, a user guide HTML file, and (most importantly) a lib subdirectory. This subdirectory contains the JOGL implementation in terms of JAR files and native Windows libraries (DLLs):

  • gluegen-rt.dll
  • gluegen-rt.jar
  • jogl.dll
  • jogl.jar
  • jogl_awt.dll
  • jogl_cg.dll

When compiling JOGL source code, you must make jogl.jar available to the compiler. When running JOGL applications, you must make jogl.jar and gluegen-rt.jar available to the application launcher. Furthermore, you need to tell the launcher where to find the native libraries. Accomplish these tasks via the -cp and -Djava.library.path options, respectively:

javac -cp "c:\jogl\jogl.jar" x.java

java -cp "c:\jogl\jogl.jar;c:\jogl\gluegen-rt.jar;." -Djava.library.path="c:\jogl" x

DirectDraw and Windows Vista

According to the Java SE 6 Release Notes, you don't need to specify -Dsun.java2d.noddraw=true on a Vista platform because "the use of DirectDraw for hardware acceleration is currently disabled by default because of incompatibilities with Vista's Desktop Window Manager."

These command lines reveal that jogl.jar is located in the \jogl directory on my C: drive. (I chose to copy lib's files to this location for convenience.) Furthermore, the second command line identifies this directory as the location of JOGL's native libraries. Finally, the command lines specify x as a placeholder for the Java file that contains the application's entry point.

If you're working on a Windows platform and discover program crashes, flickering, and other problems, chances are that OpenGL and the Java2D DirectDraw rendering pipeline are conflicting in some way. To correct this problem, specify the -Dsun.java2d.noddraw=true option in the java command line. This option disables the DirectDraw pipeline.

If you cannot use DirectDraw with OpenGL, you should enable the Java2D OpenGL rendering pipeline so that you still get hardware acceleration while working with JOGL's GLJPanel class (I discuss this class later). You can enable the OpenGL pipeline by specifying the -Dsun.java2d.opengl=true option in the aforementioned java command line.

Verify that the Java2D OpenGL rendering pipeline is active

According to Java 5's New Features in Java2D technology documentation, certain platform-specific minimum requirements must be met before the Java2D OpenGL rendering pipeline will be activated. To determine whether these requirements have been met for your platform, and that this pipeline is active (via a message that's output to the console), specify -Dsun.java2d.noddraw=True instead of -Dsun.java2d.noddraw=true. To find out what rendering operations have been accelerated, specify -Dsun.java2d.trace=log.

A gallery of demos

A variety of demonstration programs, located in the jogl-demos.zip archive, have been created to demonstrate JOGL. Furthermore, jogl-demos-src.zip contains their source code, along with documentation on running these demos. You can download both archives from the same location as the JOGL release build archive.

The jogl-demos.zip archive contains jogl-demos.jar, jogl-demos-util.jar, and jogl-demos-data.jar. You'll need to add jogl-demos.jar and possibly add the other two JAR files to your classpath before running a demo. For example, the command line below (divided across two lines for readability) requires only the first JAR file to run the gears demo on a Windows platform:

java -cp "c:\jogl\jogl.jar;c:\jogl\gluegen-rt.jar;c:\jogldemos\jogl-demos.jar" 
     -Djava.library.path="c:\jogl" demos.gears.Gears

This command line assumes that c:\jogl contains JOGL's jogl.jar and gluegen-rt.jar JAR files, and native libraries. Furthermore, it assumes that the three JAR files for the demos are located in c:\jogldemos. Once you've invoked the command, you should observe the 3D scene that's shown in Figure 1.

Figure 1. This classic OpenGL demo, converted to JOGL, features three turning interconnected gears

Additionally, the gears demo outputs some information about the platform's OpenGL implementation to the command window. For example, it outputs the following (reformatted) information about my platform's implementation:

INIT GL IS: com.sun.opengl.impl.GLImpl
Chosen GLCapabilities: GLCapabilities [DoubleBuffered: true, Stereo: false, HardwareAccelerated: true, 
                       DepthBits: 24, StencilBits: 0, Red: 8, Green: 8, Blue: 8, Alpha: 0, Red Accum: 16, 
                       Green Accum: 16, Blue Accum: 16, Alpha Accum: 16, Multisample: false ]
GL_VENDOR: NVIDIA Corporation
GL_RENDERER: GeForce 6100/PCI/SSE2/3DNOW!
GL_VERSION: 2.1.2

Most demos have certain hardware and software requirements. For example, the procedural texture physics demo requires pbuffer (pixel buffer) support, along with the ARB_vertex_program and ARB_fragment_program OpenGL extensions. Assuming that your platform meets these requirements, the following command line (divided across three lines for readability) runs this demo:

java -cp "c:\jogl\jogl.jar;c:\jogl\gluegen-rt.jar;c:\jogldemos\jogl-demos.jar;
          c:\jogldemos\jogl-demos-util.jar;c:\jogldemos\jogl-demos-data.jar" 
     -Djava.library.path="c:\jogl" demos.proceduralTexturePhysics.ProceduralTexturePhysics

This command line includes jogl-demos-util.jar and jogl-demos-data.jar in the classpath because they're required by the demo. After invoking the command, you should observe the 3D scene that's shown in Figure 2 -- try clicking the scene (to give it focus) and repeatedly pressing the period key to increase the frequency of the water droplets.

Figure 2. This physics-based water simulation is run in its entirety on the graphics card via vertex programs, fragment programs, and pbuffers.

By the way, the java.net-hosted JOGL Demos project site lets you test drive the demos located in jogl-demos.jar and additional site-specific demos via Java Web Start.

Create your own JOGL demos

JOGL provides an API for creating programs that interact with OpenGL. Although this API consists of nearly 70 classes and interfaces (organized into seven packages), you don't need to master the entire API before you can start working with JOGL. For proof, this section focuses on only a small part of the API while demonstrating how to create a pair of JOGL programs via event-based rendering.

Event-based rendering

One approach to creating a JOGL program involves event-based rendering. With this approach, a program creates a GUI component that presents rendered content, creates a listener that renders this content, and associates the listener with the component. From time to time, the component sends an event to the listener, which invokes various OpenGL rendering operations via equivalent JOGL methods.

Active rendering

In his book, Pro Java 6 3D Game Development, Dr. Andrew Davison examines two approaches to creating a JOGL program: callbacks (which I refer to as event-based rendering) and active rendering (which is more appropriate for game development). For space reasons, I only focus on event-based rendering in this article. If you'd like to learn about active rendering, you can download a PDF draft of this book's fifteenth chapter.

The component's class implements the javax.media.opengl.GLAutoDrawable interface. This interface, which inherits from the javax.media.opengl.GLDrawable interface, essentially turns the component into a rendering surface (a drawable) on which graphics are rendered. Behind the scenes, the drawable creates a rendering context that connects the rendering pipeline to the drawable's window.

What's a viewport and a view volume?

A viewport is the region of the drawable's surface in which graphics are rendered. A view volume specifies the 3D volume in space that (after being projected) fits within the viewport.

The listener's class implements the javax.media.opengl.GLEventListener interface. Once the listener has been registered with the drawable via GLAutoDrawable's public void addGLEventListener(GLEventListener listener) method, the drawable sends events to the listener by invoking GLEventListener's four methods:

  • public void init(GLAutoDrawable drawable) is called after the rendering context initializes. The listener responds by creating display lists, setting the background color, and performing other initialization tasks. This method may be called more than once if the GLAutoDrawable's context is destroyed and recreated -- removing the drawable from and later adding it to the GUI, for example.
  • public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) is called immediately after init() and each time the drawable is subsequently resized. The listener typically responds by updating the viewport and view volume. Before this method is called, the drawable sets the viewport to the same values as the last four arguments passed to this method.
  • public void display(GLAutoDrawable drawable) is called after reshape(), to render a scene to OpenGL's back buffer. After all listeners have been notified of a display event, the drawable automatically swaps its front and back buffers, unless you disable this behavior via drawable.setAutoSwapBufferMode (false);.
  • public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) is intended to be called when a mode change (such as reducing the number of screen device colors) or a device change (such as dragging a window containing the drawable to another monitor) occurs. JOGL's reference implementation doesn't implement this method.

JOGL's javax.media.opengl.GLCanvas and javax.media.opengl.GLJPanel classes implement GLAutoDrawable, making them eligible drawables. The former class subclasses the AWT's Canvas class (which makes it a heavyweight drawable), whereas the latter class subclasses Swing's JPanel class (which makes it a lightweight drawable).

Working with GLCanvas

GLCanvas takes advantage of hardware acceleration to perform OpenGL operations rapidly. You can employ this drawable in AWT and Swing GUIs, but must exercise care when using GLCanvas with Swing. For example, when adding a GLCanvas to a JFrame that also contains lightweight components, you must add this drawable to a JPanel, and then add the panel to the frame.

Listing 1 presents the source code to a JOGLDemo1 application that shows you how to architect a Swing-based JOGL program with GLCanvas and GLEventListener.

Listing 1. JOGLDemo1.java

// JOGLDemo1.java

// This demo renders a smoothly shaded triangle and a flat shaded square. The
// triangle is rendered in front of the square in the default front view --
// looking toward the negative Z axis. In the back view (looking toward the
// positive Z axis), as specified by a command-line argument, the square is
// rendered in front of the triangle.

import java.awt.*;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;

import javax.swing.*;

public class JOGLDemo1 extends JFrame

...

Download the complete Listing 1.

Listing 1 specifies classes JOGLDemo1 and SceneRenderer. The former class provides the application's entry point, which lets you decide whether to view the rendered scene -- a smoothly shaded triangle and a flat shaded square -- from the front (the default) or the back (if back is specified as the single command-line argument). The latter class renders the scene.

After determining the scene's viewing direction, the main() method's initial thread hands off GUI construction to the event-dispatching thread. This thread creates a SceneRenderer component instance, informing the instance of the viewing direction. The thread next sizes this component and adds it to the JFrame.

SceneRenderer subclasses GLCanvas and implements GLEventListener. As a result, this component is both a drawable and a listener for events arising from the underlying JOGL/OpenGL implementation. The first event results in the init() method being invoked, which takes care of initialization.

After printing a console message to show you that it's always called first, init() invokes its drawable argument's public GL getGL() method to access the rendering pipeline. The returned javax.media.opengl.GL instance is used to enable depth buffer testing, to ensure that the square is not rendered in front of the triangle when the scene is viewed from the front.

Be careful with getGL()

Don't invoke the getGL() method outside of a listener method because getGL() might return null in this situation. Also, instead of caching the returned GL instance, always re-obtain this instance via a fresh call to getGL() at the start of the listener method. According to JOGL's user guide: "multithreading issues inherent to the AWT toolkit make it difficult to reason about which threads certain operations are occurring on, and if the GL object is stored in a field it is unfortunately too easy to accidentally make OpenGL calls from a thread that does not have a current context. This will usually cause the application to crash."

The next event method to be invoked is reshape(). (In addition to being invoked immediately after init(), reshape() is invoked whenever the drawable is resized.) Although I could use this method to create a viewport, I prefer the default viewport (covering the whole surface) that JOGL creates prior to invoking this method.

More importantly, reshape() is the place to initialize the projection matrix, which determines how the scene is projected onto the screen: orthographic or perspective. If I was projecting an architectural drawing, I'd choose an orthographic projection. However, because I want to take advantage of perspective, I've decided to go with a perspective projection, by invoking the following GL method:

public void glFrustum(double left, double right, double bottom, double top, 
                      double near_val, double far_val)
  • left and right contain the coordinates for the left and right vertical clipping planes
  • bottom and top contain the coordinates for the bottom and top horizontal clipping planes
  • zNear and zFar contain the positive distances from the viewpoint (the location where the viewer is observing the scene) to the near and far clipping planes

The display() event method is always called after reshape(). After telling OpenGL to clear the drawable's surface to the background color, and to clear the depth buffer in preparation for another round of depth testing, this method initializes the viewpoint, viewing direction, and what direction is up (positive Y or negative Y axis, for example) via the following method:

public void gluLookAt(double eyeX, double eyeY, double eyeZ,
                      double centerX, double centerY, double centerZ,
                      double upX, double upY, double upZ)
  • eyeX, eyeY, and eyeZ establish the viewpoint
  • centerX, centerY, and centerZ establish the viewing direction via a reference point
  • upX, upY, and upZ establish the up vector

This method, which you can think of as setting up an imaginary camera, is one of many useful OpenGL Utilities (GLU) Library methods that exist in the javax.media.opengl.glu.GLU class. Before its invocation, a GLU instance must be created by invoking its public GLU() constructor -- you can call this constructor from outside GLEventListener methods.

I've chosen a viewpoint that positions the camera one unit in front of either the triangle (front view) or the square (back view). At this distance, the triangle completely fills the viewport (front view) and the square occupies 1/16th of the viewport (back view) -- I still want to see part of the triangle when viewing from the back, which is why the square is so much smaller.

The display() method goes on to render a triangle followed by a square into OpenGL's back buffer. Because the square is rendered last, the depth buffer ensures that the square (which is located behind the triangle when viewed from the front) will not be rendered in front of the triangle (when viewed from the same direction). After display() exits, JOGL swaps the back and front buffers, and the scene appears.

Assuming the original setup and a Windows platform, compile JOGLDemo1.java via the following command line:

javac -cp "c:\jogl\jogl.jar" JOGLDemo1.java

Run this application via the command line below:

java -cp "c:\jogl\jogl.jar;c:\jogl\gluegen-rt.jar;." -Djava.library.path="c:\jogl" JOGLDemo1

Figure 3 reveals the resulting scene.

Figure 3. The default smooth shading model ensures that colors are interpolated between the three vertices.

Let's observe the scene from the back. This time, include the back argument, as follows:

java -cp "c:\jogl\jogl.jar;c:\jogl\gluegen-rt.jar;." -Djava.library.path="c:\jogl" JOGLDemo1 back

Figure 4 reveals the resulting scene.

Figure 4. The square is no longer eclipsed by the triangle.

Working with GLJPanel

Unlike GLCanvas, GLJPanel plays nicely with Swing GUIs. For example, you can subclass GLJPanel and have the subclass's paintComponent() method render Java2D content (such as a gradient or an image) as a background to an OpenGL scene. (You can also render Java2D content over the scene, possibly to achieve a head-up display.)

Before you can render Java2D graphics behind an OpenGL scene, you need to assign a non-zero alpha depth to the drawable, by creating a javax.media.opengl.GLCapabilities object, by invoking this object's public void setAlphaBits(int alphaBits) method (typically eight alpha bits), and by passing this object to a GLJPanel superclass constructor that takes a GLCapabilities argument.

In addition to assigning a non-zero alpha depth to the drawable, you need to make the drawable non-opaque so that Java2D-rendered content behind the scene will show through. Accomplish this task by invoking GLJPanel's public void setOpaque(boolean opaque) method with a false argument. The following code fragment demonstrates both tasks:

GLCapabilities caps = new GLCapabilities ();
caps.setAlphaBits (8);
SceneRenderer sr = new SceneRenderer (caps);

// ...

class SceneRenderer extends GLJPanel ...
{
   SceneRenderer (GLCapabilities caps)
   {
      super (caps);
      setOpaque (false);
   }
}

Listing 2 presents the source code to a JOGLDemo2 application that shows you how to architect a JOGL program based on GLJPanel and GLEventListener.

Listing 2. JOGLDemo2.java

// JOGLDemo2.java

// This demo renders a torus over a Java2D-rendered image of the sky.

import java.awt.*;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;

import javax.swing.*;

import com.sun.opengl.util.*;

public class JOGLDemo2 extends JFrame

...

Download the complete Listing 2.

JOGLDemo2.java presents a few items that need to be clarified. First, the display() method employs GLJPanel's public boolean shouldPreserveColorBufferIfTranslucent() method to determine if it should clear the color buffer. If this method returns true, as when the OpenGL rendering pipeline is enabled via -Dsun.java2d.opengl=true, the color buffer shouldn't be cleared.

JOGLDemo2 takes advantage of JOGL's com.sun.opengl.util.GLUT class to access the OpenGL Utility Toolkit (GLUT). Before accessing the GLUT, this class is instantiated by invoking its public GLUT() constructor -- as with GLU, GLUT's constructor can be called from outside GLEventListener methods.

Although GLUT lets you render a variety of geometric objects, display() is only concerned with rendering a torus (a doughnut shape). It accomplishes this task by invoking GLUT's public void glutSolidTorus(double innerRadius, double outerRadius, int nsides, int nrings) method:

  • innerRadius specifies the radius of the cylindrical tube that surrounds the "doughnut hole"
  • outerRadius specifies the radius from the center of the "doughnut hole" to the tube's outer wall
  • nsides specifies the shape of the tube wall; the greater the value, the rounder the tube wall
  • nrings specifies the shape of the tube; the greater the value, the rounder the tube

This method renders a solid torus that's centered at the origin of the modeling coordinate system, and whose Z axis is aligned with the camera's Z axis. (Alternatively, you can use GLUT's public void glutWireTorus(double innerRadius, double outerRadius, int nsides, int nrings) method to render a wireframe torus with the same origin and alignment.)

Finally, the reshape() method works with the GLU class's public void gluPerspective(double fovy, double aspect, double zNear, double zFar) method, which offers an easier-to-use alternative to glFrustum() that's based on a field-of-view angle and an aspect ratio (which should be set to the viewport's aspect ratio to prevent distortion):

  • fovy specifies the field-of-view angle (in degrees) in the Y direction
  • aspect specifies the aspect ratio (the ratio of width to height) of the field of view in the X direction
  • zNear specifies the positive distance from the viewpoint to the near clipping plane
  • zFar specifies the positive distance from the viewpoint to the far clipping plane

Assuming Windows and the same setup as before, compile JOGLDemo2.java via the following command line:

javac -cp "c:\jogl\jogl.jar" JOGLDemo2.java

Run this application via the command line below:

java -cp "c:\jogl\jogl.jar;c:\jogl\gluegen-rt.jar;." -Djava.library.path="c:\jogl" JOGLDemo2

Figure 5 reveals the resulting scene.

Figure 5. Homer Simpson would enjoy this UFO!

Animate a scene

If you need to animate a scene (such as JOGLDemo1's triangle and square), you could create a thread that repeatedly invokes GLAutoDrawable's public void display() method. This method, which you can safely call on any thread, invokes the display() event method of every registered GLEventListener.

However, JOGL provides an easier way to perform animation, by providing the com.sun.opengl.util.Animator class and its com.sun.opengl.util.FPSAnimator subclass. Each class creates a background thread -- via a timer, in the case of FPSAnimator -- that repeatedly invokes the display() methods of all registered drawables.

Animator's public Animator() constructor lets you create an instance with no registered drawables. Alternatively, you can use the public Animator(GLAutoDrawable drawable) constructor to create an instance with the specified drawable. Regardless of constructor, additional drawables can be registered via Animator's public void add(GLAutoDrawable drawable) method.

After registering one or more drawables, start the animation by invoking Animator's public void start() method. To terminate the animation, invoke the public void stop() method. These methods (and their FPSAnimator counterparts) throw the unchecked javax.media.opengl.GLException when you try to start a started or stop a stopped animation.

Animator repeatedly invokes a drawable's display() method and then briefly pauses -- via Thread.yield() -- to avoid swamping the CPU. However, you can avoid this pause and run the animation as fast as possible by invoking Animator's public final void setRunAsFastAsPossible(boolean runFast) method, passing true to runFast.

Listing 3 presents the source code to a JOGLDemo3 application that demonstrates the Animator class, by using this class to rotate JOGLDemo1's scene around the Y axis.

Listing 3. JOGLDemo3.java

// JOGLDemo3.java

// This demo renders a smoothly shaded triangle and a flat shaded square. The
// scene is animated with the triangle and square rotating around the Y axis.
// Because polygon antialiasing is used to minimize jagged edges, depth buffer 
// testing must not be enabled -- it's performed manually by determining which
// geometric object to draw first based on the angle of rotation.

import java.awt.*;
import java.awt.event.*;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;

import javax.swing.*;

import com.sun.opengl.util.*;

public class JOGLDemo3 extends JFrame

...

Download the complete Listing 3.

You might be wondering why the registered window listener's windowClosing() method delegates its animation-stop and application-exit tasks to a background thread. In answer, Animator's Javadoc states that its stop() method might block until completion, which would cause the window to remain displayed until stop() completes if this method was invoked on the event-dispatching thread.

You might also be wondering why JOGLDemo3 assigns an alpha channel to its drawable. The reason has to do with SceneRenderer's use of polygon antialiasing to smooth the edges of the triangular polygons that comprise the triangle and square shapes. Polygon antialiasing cannot be carried out without an alpha channel.

Finally, you might want to know why I perform depth buffer testing manually, rather than take advantage of OpenGL's depth buffer testing, which I used previously in JOGLDemo1 and JOGLDemo2. It turns out that polygon antialiasing doesn't work properly with depth buffer testing. In addition to introducing artifacts, objects aren't properly sorted when both polygon antialiasing and depth buffer testing are enabled.

After compiling its source code, run JOGLDemo3. Figure 6 reveals one frame of the animation.

Figure 6. The triangle and square rotate around a common center.

Running an animation at an arbitrary rate is problematic because the animation runs too fast on some machines, making it hard to watch. Also, if the animation is interrupted by a garbage collection or other lengthy task, the animation may fall behind. For example, if each animation frame is responsible for updating a clock's second hand, a delay will result in the clock losing time because the delay is never corrected.

If your animation must run at a uniform rate, and must also not be affected by delays (games usually have these requirements), you'll want to work with FPSAnimator. This class lets you run an animation at a specified frame rate (also known as frames per second). Also, it lets you take advantage of fixed-rate scheduling to execute an animation the correct number of times over a lengthy period, regardless of delays.

FPSAnimator provides four constructors. The public FPSAnimator(GLAutoDrawable drawable, int fps, boolean scheduleAtFixedRate) constructor is the most flexible, letting you register a drawable, choose a desired frame rate via fps, and select fixed-rate scheduling by passing true to scheduleAtFixedRate.

As an exercise, modify Listing 3 to use FPSAnimator instead of Animator. Introduce an FPS integer constant that specifies the desired frame rate, and replace the Animator animator; declaration with FPSAnimator animator;. Also replace animator = new Animator (sr); with animator = new FPSAnimator (sr, FPS, true);.

Take pictures of a scene

JOGL's com.sun.opengl.util.Screenshot class lets you programmatically capture an OpenGL scene to a file or a buffered image. This class provides three methods for reading the scene into a buffered image, and six methods for writing the scene to a file. Three of the file-writing methods save the scene to a Targa file, and (according to their Javadoc) are the fastest methods for capturing the scene.

About Targa

Truevision Advanced Raster Graphics Adapter (TARGA), also known as Truevision Graphics Adapter (TGA), defines a file format used extensively by the animation and computer game industry for storing screen captures. Wikipedia's Truevision TGA entry has much to say about this format.

For example, Screenshot's public static void writeToTargaFile(File file, int x, int y, int width, int height, boolean alpha) method takes a screenshot of the scene in OpenGL's back buffer (unless you specify a different buffer via glReadBuffer()) and saves the screenshot in a Targa file. Pass the following arguments to this method:

  • file identifies the TARGA file that will be created to contain the screenshot
  • x identifies the starting X coordinate of the screenshot (measured from the lower-left corner of the scene)
  • y identifies the starting Y coordinate of the screenshot (measured from the lower-left corner of the scene)
  • width identifies the width of the screenshot
  • height identifies the height of the screenshot
  • alpha identifies whether (true) or not (false) the drawable's alpha channel should be saved -- the GL_EXT_abgr extension must be present when saving the alpha channel

The writeToTargaFile() method throws a GLException if the rendering context is not current (it's current when you call this method from a GLEventListener method, for example), or if some other OpenGL error occurs. This method throws an IOException if an I/O error occurs while writing to the file.

I've created a JOGLDemo4 application that demonstrates writeToTargaFile(). This application is nearly identical to JOGLDemo2, except for extra code that takes a screenshot of the most recently rendered scene and saves the result to a file named capture.tga in response to a mouse click event.

Because most of JOGLDemo4.java's code is identical to JOGLDemo2.java, I'll only focus on key differences, rather than present the complete source code -- check out this article's code archive for all of the source code. The first difference is the declaration of a mouseWasClicked field in the SceneRenderer class:

private volatile boolean mouseWasClicked;

This boolean field is marked volatile because it's accessed on the event-dispatching thread, and possibly on another thread that invokes GLEventListener's display() method. If not marked volatile, changes to mouseWasClicked's value on one thread might not be observed by the other thread. The following code fragment sets this field to true:

addMouseListener (new MouseAdapter ()
                  {
                      public void mouseClicked (MouseEvent me)
                      {
                         mouseWasClicked = true;
                         repaint ();
                      }
                  });

This code fragment installs a mouse listener via SceneRenderer's constructor. This listener responds to mouse click events by invoking repaint() to repaint the component via paintComponent(). In turn, paintComponent() causes the display() event method to be invoked. After rendering the back buffer's scene, this method executes the following code:

if (mouseWasClicked)
{
    try
    {
        int [] viewport = { 0, 0, 0, 0 };
        gl.glGetIntegerv (GL.GL_VIEWPORT, viewport, 0);
        Screenshot.writeToTargaFile (CAPTURE_FILE, viewport [0],
                                     viewport [1], viewport [2],
                                     viewport [3], false);
    }
    catch (IOException ioe)
    {
        System.out.println (ioe.getMessage ());
    }

    mouseWasClicked = false;
}

I want to capture the back buffer's contents located within the viewport, which is why this code fragment first obtains the viewport's location and dimensions. It then saves these contents to the Targa file identified via the CAPTURE_FILE constant. I disregard the alpha channel because saving it results in a false color (but pretty) torus.

When you run JOGLDemo4, make sure to enable the OpenGL pipeline -- specify -Dsun.java2d.opengl=true or -Dsun.java2d.opengl=True on Windows platforms. Otherwise, the background sky image won't be captured. I believe this has to do with JOGL/OpenGL not rendering content to and reading content from Swing's back buffer when the OpenGL pipeline is disabled.

Work with textures

Computer games achieve realism by mapping images onto polygonal shapes -- mapping a missile's image onto a polygon-based missile object can result in a realistic-looking missile, for example. The image data that's mapped onto a shape is known as a texture. Although you can work with textures via various methods in the GL class, JOGL provides several classes, starting with those below, that simplify this task:

  • com.sun.opengl.util.texture.Texture
  • com.sun.opengl.util.texture.TextureCoords
  • com.sun.opengl.util.texture.TextureIO

The Texture class represents an OpenGL texture object, the TextureCoords class specifies the texture coordinates for a rectangular area of the texture, and the TextureIO class lets you read textures from disk or other sources (and also write memory-based textures back to disk). Listing 4's JOGLDemo5 application source code demonstrates all three classes.

Listing 4. JOGLDemo5.java

// JOGLDemo5.java

// This demo renders a textured cube, with each side displaying a different
// texture.

import java.awt.*;
import java.awt.event.*;

import java.io.*;

import java.util.*;

import javax.media.opengl.*;
import javax.media.opengl.glu.*;

import javax.swing.*;

import com.sun.opengl.util.*;
import com.sun.opengl.util.texture.*;

public class JOGLDemo5 extends JFrame

...

Download the complete Listing 4.

JOGLDemo5.java describes an application that animates a cube, rotating it in randomly-chosen directions around the X, Y, and Z axes. The three aforementioned texture classes are used to load six textures based on nebula images and render these textures onto the cube's six surfaces. Figure 7 reveals one frame of the animation.

Figure 7. Six JPEG-based nebula images form the basis of this rotating cube's textures.

You could reduce the jagged edges by using polygon antialiasing. However, you would have to turn off depth buffer testing and manually sort the six polygons from front to back, which could be somewhat tricky. Alternatively, you could antialias the entire scene by taking advantage of the accumulation buffer. However, this form of antialiasing can leave the scene somewhat blurry, as Figure 8 reveals.

Figure 8. Full-scene antialiasing typically results in a somewhat blurry image.

In conclusion

JOGL offers a powerful and easy-to-use API for accessing OpenGL to render 3D (and 2D) graphics. Lack of space prevented me from digging deeper into JOGL; there's much more for you to explore. You might want to begin by investigating JOGL's partial support for Non-uniform rational B-splines (NURBs), and its com.sun.opengl.util.JOGLAppletLauncher class for deploying JOGL-based applets.

Jeff Friesen is a freelance software developer and educator who specializes in Java technology. Check out his javajeff.mb.ca website to discover all of his published Java articles and more.

Learn more about this topic

More