PC's JOGL Blog

Learning the Java JOGL OpenGL Bindings

Wednesday, January 24, 2007

Blending with glPolygonOffset

glPolygonOffset() is used to offset the polygon being rendered, and thus the pixels in the polygon, from the others currently in the displaybuffer. This does not actually effect the values written to the ZBuffer and is useful when blending two textures or polygons together.

The following images give an example of the problem and the effect of using glPolygonOffset. There are two polygons are rendered, the first being red, with the second being yellow blended over it in the same position in 3D space. The aim of the blending is to make a clean transition between red and yellow, or between 2 textures, in an animation. The second yellow polygon is a little bigger that the first red polygon be show the effect more clearly. In the first image, the "popping" of the red polygon through the yellow polygon occurs because the pixels values of the yellow polygon do not exactly match the Z buffer values of the first.

By enabling polygon offset the second polygon depth tests are performed using the following calculation...

"The value of the offset is factor * DZ + r * units, where DZ is a measurement of the change in depth relative to the screen area of the polygon, and r is the smallest value that is guaranteed to
produce a resolvable offset for a given implementation. The offset is added before the depth test is performed and before the value is written into the depth buffer *" OpenGL Reference Documentation

The result is that the second yellow polygon gives the desired result and blends nicely over the red polygon. This effect is very useful when blended between textures, for example, when changing a models level of detail. It is important when using this method that the polygons are drawn in a particular order. The offset yellow polygon must be draw original red polygon otherwise the effect does not work.

// Render first polygon
gl.glEnable(GL.GL_POLYGON_OFFSET_FILL) ;
gl.glPolygonOffset(-1.0f, 1.0f) ;
// Render second polygon
gl.glDisable(GL.GL_POLYGON_OFFSET_FILL) ;

Note that offsets can be used for the lines and the points in a polygon as well by enabling GL.GL_POLYGON_OFFSET_LINE and/or GL.GL_POLGON_OFFSET_POINT.

Monday, July 17, 2006

JNLP Download Error

I came in to work this morning and decided to test my JNLP program. I was using Firefox to launch the application from my computer, which is behind a firewall and a proxy server, and web start was giving me "Download Error" message.
An error occurred while launching/
running the application.

Title: Simple Blending Example
Vendor: Peter Finch
Category: Download Error

Unable to load resource: http://www.homepla.net/webstart
/simpleblending/simpleblending.jnlp

I found this was due to my proxy settings in IE, which was not setup to go through the proxy server (testing reasons), so web start could not get through the firewall to the Internet and load the program. It appears that web start uses the IE internet connection settings to get to the Internet by default.

You can change this using "javaws". Start "javaws" and go to the "Edit", "Preferences" menu to get the "Java Control Panel" dialog. From there go to the "General" tab and select "Network Settings". You'll probably find "Use Browser Settings" checked by default, so just set it to "use proxy server" and enter the appropriate details. It will now be independent of your browser settings.

Saturday, July 15, 2006

JOGL Java Webstart (JNPL)

Turning a JOGL application into a Java webstart application was relatively easy and just involves building a JAR file and creating a JNLP (Java Net Launch Protocol) file. After doing this once I bet you'll probably end up always using webstart to distribute you applications. It is just so easy to work with and installing applications on any browser with Java installed is a snap.

However, I did have a problem when trying to start the application from the browser for the first time. Webstart kept giving me an error message “Java Web Start – Unexpected Error - Unable to launch simpleblending”. When I asked for details, I got the following information (not the most helpful message).

An error occurred while launching/running 
the application.

Title: simpleblending
Vendor: Peter Finch
Category: Unexpected Error

Unexpected exception: java.lang.Exception

I tracked it down to the location I was getting the JOGL extension from. I originally used the prescribed location of https://jogl.dev.java.net/webstart/jogl-1-1.jnlp, however, when I changed this to http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jnlp the problem disappeared and the application started. Perhaps this is a library version problem or something. I understand it is possible to have your own copies of the libraries, which is probably a good idea so that you always know what version or JOGL your application running against. I'll work that out later...

Below is the JNPL file I used and here is a link to the webstart application. Just create a JAR file as you normally would. I like to use Ant scripts to automate the building and deployment for this sort of thing, as it is really easy and built into Eclipse. Note that the first time you load this it will go off and download the JOGL jar and native libraries for your platform. This could take a while (it's about 1.5MB's of data), but the great thing is that once it is downloaded, if any of you other applications use the same location they will reuse the already downloaded version and start up a lot quicking. JNPL even handles version changes (it's so cool!)

<?xml version="1.0" encoding="utf-8"?>
<jnlp spec="1.0+" codebase="http://www.homepla.net/webstart/simpleblending" href="simpleblending.jnlp">
<information>
<title>Simple Blending</title>
<vendor>Peter Finch</vendor>
<homepage href="http://www.homepla.net/webstart/simpleblending/" />
<description>JOGL Webstart Example 1</description>
<offline-allowed/>
</information>
<resources>
<j2se version="1.4+" />
<jar href="jplanet.jar" />
<property name="sun.java2d.noddraw" value="true"/>
<extension name="jogl" href="http://download.java.net/media/jogl/builds/archive/jsr-231-webstart-current/jogl.jnlp" />
</resources>
<application-desc main-class="jplanet.JPlanetWindow" />
</jnlp>

Thursday, July 13, 2006

JOGL Draw Arrays

Calling the OpenGL drawing primitives in Java, like glVertex() and glNormal(), quickly becomes a bottleneck for rendering scenes. The function call overhead, although small, when multiplied a million times for each polygon is a problem very quickly, however, this can be overcome using glDrawArrays (commonly known as vertex arrays). Vertices, normals, colours and texture coordinates can be loaded into an IntBuffer, FloatBuffer or a DoubleBuffer (depending on what you want to use) and then sent to the rendering engine using a single glDrawArrays() function call.

import java.nio.DoubleBuffer ;
..
DoubleBuffer verticesBuffer = BufferUtil.newDoubleBuffer(3 * 3) ;
..
verticesBuffer.put(c0.x) ;
verticesBuffer.put(c0.y) ;
verticesBuffer.put(c0.z) ;
verticesBuffer.put(c1.x) ;
verticesBuffer.put(c1.y) ;
verticesBuffer.put(c1.z) ;
verticesBuffer.put(c2.x) ;
verticesBuffer.put(c2.y) ;
verticesBuffer.put(c2.z) ;
..
gl.glEnableClientState (GL.GL_VERTEX_ARRAY);
verticesBuffer.rewind();
gl.glVertexPointer(3, GL.GL_DOUBLE, 0, verticesBuffer);
gl.glDrawArrays(GL.GL_TRIANGLES, 0, 3);

Sadly, there is still a function call overhead loading the values into the DoubleBuffer, however, this is less that the overhead of calling glVertex() multiple times. Make sure you rewind the location in the buffer using verticesBuffer.rewind() or verticesBuffer.position(0) before calling glVertexPointer() otherwise you will load the current location in the buffer into OpenGL instead of the starting location and you’ll end up only seeing a new vertices each call.

I found that loading the values into a local array and the calling the put(double[], int, int) function to load multiple values at once was much slower that calling multiple put(double) functions, even if the array was pre-allocated and reused. I could not find any way to load values into the buffers without doing it thought a function call… so if you know how to do please let me know.



// Really slow for some reason.
double array[] = new double[9] ;
array[0] = c0.x ;
array[1] = c0.y ;
..
verticesBuffer.put(array, 0, 9) ;

Rendering tip… remember to set gl.glEnable(GL.GL_NORMALIZE) when using matrixes to translating and scaling groups of vertices, otherwise the vertices normals passed to OpenGL get transformed as well, but they don’t get re-normalized, so you end up of unexpected lighting results.

Here is some good example code but make sure you add that rewind calling glColorPointer().

Thursday, July 06, 2006

Using the mouse

When using a Java Frame to hold the JOGL GLCanvas, interaction with the frame, using the mouse, can be done in the usual way by using a Listener. The listener event handler class (the class which is to receive the events), must implement the MouseListener, MouseMotionListener interfaces. One of the nice things about Eclipse is that the moment you add these interfaces to the class it will ask you if you want to add the corresponding functions.
public class joglpbuffer implements 
GLEventListener,
MouseListener,
MouseMotionListener {

public void init(GLAutoDrawable drawable) {
drawable.addMouseListener(this);
drawable.addMouseMotionListener(this);
}
}

The event functions themselves are self explanatory. They are all passed a MouseEvent argument which can be used to determine the location of the mouse or the button that was just pressed.
  public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {}
public void mousePressed(MouseEvent e) {
prevMouseX = e.getX();
prevMouseY = e.getY();
if ((e.getModifiers() & e.BUTTON3_MASK) != 0) {
mouseRButtonDown = true;
}
}
public void mouseReleased(MouseEvent e) {
if ((e.getModifiers() & e.BUTTON3_MASK) != 0) {
mouseRButtonDown = false;
}
}
public void mouseDragged(MouseEvent e) {
int x = e.getX();
int y = e.getY();
}