Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

JOGL

Download as pdf or txt
Download as pdf or txt
You are on page 1of 12

JOGL (Java OpenGL) Tutorial

@for Developers
@author Kai Ruhl
@since 2009-04

Introduction
JOGL is the OpenGL binding for Java (JSR-231), mapping functions as closely as possible from C. OpenGL is
a standardized API used to draw 3D graphics to the screen (similar to DirectX / Direct3D). The primary
difference to 2D is that you need to think about z coordinates and the position of the viewer ("camera",
including viewing angle and such).
I am writing this JOGL tutorial because I did not find another good one. The JOGL homepage is poor in
information, the few tutorials are mostly outdated (not using newer functions, e.g. for textures or fonts), and the
famous NeHe OpenGL tutorials are for pure OpenGL under C (Java port available though). The best API doc I
found is the Python OpenGL API (better than the JOGL javadoc).
My plan for this tutorial (which is based on my experiences while writing Breed Orbiter) is:
1. Open a blank OpenGL window in Java.
2. Set the camera position and direction, looking at a triangle.
3. Create a planet sphere.
4. Add lighting from the sun.
5. Add an earth texture to the planet.
6. Add an orbit with a primitive satellite.
If you like, follow me on this way...

How does JOGL/OpenGL work?


First, a few words on how OpenGL works: Procedural. You start a thread that repeatedly calls a display()
method at your fps (frames per second) rate; in this method, you clear the screen and rebuild the entire scene.
You do this by issuing primitive commands: Begin vertex, this is the 1st point, the 2nd point, the 3rd, end
vertex. And so on.
Of course, it gets more complicated for textures, lighting and the like, but the bottom line is that there is one GL
object which has like 1000 glWhatever() methods that you will call over and over again. Yes it is primitive but
near to the hardware, and this means fast and flexible.
To integrate with Java, there have to be some concessions: Namely, the AWT component (a GLCanvas
extending Component) is integrated into the AWT event queue, and you need to implement a listener
(GLEventListener) for lifecycle callbacks (not so procedural here, aint it).
As a little prepping up for the 3D world, the figures below show coordinate system and camera.

3D coordinate system Camera looking at 3D scene


A positive z coordinate is coming out of the screen, a negative into the screen. The camera position is
determined by one point to be in, another point to look at, and a vector defining where your head is pointing
into the sky (well, and a viewing angle, but more on that later). With that, lets start doing a bit 3D!

Step 1: A blank OpenGL window in Java


Before we start programming, you have to setup a bit. Other sites have described that, so I will be brief:
• Put jogl.jar and gluegen.jar in your classpath
• add the 3 library files (.so, .dll) into the library path by supplying -Djava.library.path=lib/jogl/your_path
to your java start command.
And then we make a start class: Extend GLCanvas and implement GLEventListener; and in the main method,
put the canvas into a JFrame. You will notice a constructor and 4 event methods:
public class MyJoglCanvas extends GLCanvas implements GLEventListener {
public MyJoglCanvas(int width, int height, GLCapabilities capabilities) {}
public void init(GLAutoDrawable drawable) {}
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {}
public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {}
public void display(GLAutoDrawable drawable) {}
}
One by one, they are good for the following things:
• The constructor specifies the initial size of the window as well as the minimum capabilities
requirements to OpenGL.
• The init() method is called by the AWT event queue as soon as the window is ready; we can do some
global settings here.
• Whenever the canvas is resized, reshape() is called (this is important e.g. for the camera viewing angle
whenever the width/height ratio changes).
• We completely ignore the displayChanged() method. This would be called in a multiple screen scenario.
• And finally display(): This is called for every re-rendering of the scene. It is here that we will spent most
of our time.
For your convenience, the important classes so far are summarised in the following figure:

JOGL starter classes (many methods omitted)

And now, in detail. Lets start with the constructor.


public MyJoglCanvas(int width, int height, GLCapabilities capabilities) {
super(capabilities);
setSize(width, height);
}
Seems easy enough, but where to the capabilities come from? You need to specify them in your main method.
Fortunately, this is quite easy:
GLCapabilities capabilities = new GLCapabilities();
capabilities.setRedBits(8);
capabilities.setBlueBits(8);
capabilities.setGreenBits(8);
capabilities.setAlphaBits(8);
The capabilities simply define what you want from the underlying OpenGL implementation. If the machine
cannot do this, an exception will be thrown and you know what will work and what wont. Now going to the
init() method:
public void init(GLAutoDrawable drawable) {
GL gl = drawable.getGL();
drawable.setGL(new DebugGL(gl));

// Global settings.
gl.glEnable(GL.GL_DEPTH_TEST);
gl.glDepthFunc(GL.GL_LEQUAL);
gl.glShadeModel(GL.GL_SMOOTH);
gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
gl.glClearColor(0f, 0f, 0f, 1f);

// Start animator (which should be a field).


animator = new FPSAnimator(this, 60);
animator.start();
}
A little more lines here. At first, you can notice the GL object: This is the one that we will issue most
commands on. As a nice feature of JOGL, we can decorate it with a DebugGL, which will supply us with
stacktraces on exceptions (as opposed to simply crashing as it does in C).
Next, some global settings (here: my standard settings). The first two enable z-buffers (always needed for 3D
drawing - you could also use OpenGL to do 2D only) with last-one-wins settings (draw a rect, and a line on it,
the line will still be visible). A shade model of GL_SMOOTH looks nice, as does the GL_NICEST correction
hint. The clear color is the CLS (ClrScr) color: Black with no translucency (alpha is 1), specified as RGBA in
floats.
Finally, the fps thread I spoke about earlier: The animator thread (which should be a field in our class) expects a
GLCanvas and the frame rate setting (here: 60fps). As we are in the init() method, we can start it without
fearing something is not ready in AWT. And now, someone could resize the window:
public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
GL gl = drawable.getGL();
gl.glViewport(0, 0, width, height);
}
Dont ask me what exactly a view port is; it represents the part of the screen that you can see, and we'll leave it
at that (for more, wikipedia is your friend). Ignoring the displayChanged() method, we come to the last and
most important one:
public void display(GLAutoDrawable drawable) {
GL gl = drawable.getGL();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
}
And now, you can start the whole thing and we can see... a black screen (this is what glClear() does at every
tick). We have no objects yet and no camera direction, but this is something we can change now.
For a source code view of what we have covered so far, consider looking at the MyJoglCanvasStep1.java file.
Step 2: Camera position and first Triangle
Kinda complicated just for a black screen so far, huh? I certainly think so (however I am willing to accept that
this is the price of being near to the hardware). Now I want to see something! For that, we need to define our
camera position in the 3D space and then actually draw something.
Consider the display() method: We will spend all our time here. Before any drawing occurs, the camera position
should be set. This is because OpenGL is just a simple pipeline: It renders something and promptly forgets
about it, so if we change the camera position in between, it will be confused.
For a start, we set the camera position at (0, 0, 100), which is 100 units just in front of the screen (where you are
probably sitting). For this, we need a GLU object (an OpenGL helper library with standard geometric
functions), and a setCamera() method within display():
public void init(GLAutoDrawable drawable) {
glu = new GLU();
}

public void display(GLAutoDrawable drawable) {


setCamera(gl, glu, 100);
}
And of course, the magic of setting the camera.
private void setCamera(GL gl, GLU glu, float distance) {
// Change to projection matrix.
gl.glMatrixMode(GL.GL_PROJECTION);
gl.glLoadIdentity();

// Perspective.
float widthHeightRatio = (float) getWidth() / (float) getHeight();
glu.gluPerspective(45, widthHeightRatio, 1, 1000);
glu.gluLookAt(0, 0, distance, 0, 0, 0, 0, 1, 0);

// Change back to model view matrix.


gl.glMatrixMode(GL.GL_MODELVIEW);
gl.glLoadIdentity();
}
This looks complicated, and inside, it surely is -- but this is no concern to us. The first thing to know is that
OpenGL calculates coordinates using matrixes. For the camera, we will switch on the PROJECTION matrix,
and for everything else the MODELVIEW matrix. The glLoadIdentity() method resets all previous matrix
settings one may have made.
As for the perspective itself, that is something that GLU takes care of. We specify a viewing angle (e.g. 45)
with respect to the current width/height ratio of the window (required by OpenGL to calculate the actual
viewing angle on screen), and a minimum (1) and a maximum (1000) distance where we will see things
(infinity would be impossible to compute by your poor GPU).
And then, the gluLookAt() method, which takes three 3D coordinates:
• (0, 0, distance): Where we are standing ("eye"): Here, we modify only the z-coordinate.
• (0, 0, 0): Where we are looking at ("at"): Directly at the center of the coordinate system.
• (0, 1, 0): Where our head points into the sky ("up"): Directly up, along the Y coordinate (remember that
Y is counted as if on a graph, not like in an image manipulation program).
And now we are out of the screen, looking in. And for what to see? Let us go back into the display() method
and add some code at the very end:
public void display(GLAutoDrawable drawable) {
// Write triangle.
gl.glColor3f(0.9f, 0.5f, 0.2f);
gl.glBegin(GL.GL_TRIANGLE_FAN);
gl.glVertex3f(-20, -20, 0);
gl.glVertex3f(+20, -20, 0);
gl.glVertex3f(0, 20, 0);
gl.glEnd();
}
Here is it that the standard JOGL / OpenGL drawing procedure begins. You specify a color (RGB), you say
what you want (GL_TRIANGLE_FAN), and then begin churning out coordinates. When you are finished, you
glEnd() what you started with glBegin(). And voila, our first visual result:

Our first triangle


You will notice that although the 3D coordinates are evenly spaced, the triangle on screen is not. This is an
effect of the viewing angle: If you stand in front of a tree, you wont see its "real" height either.
And in keeping step, you might now consider looking at the MyJoglCanvasStep2.java source file.

Step 3: Planet Sphere


Having a triangle is nice and all (and this is actually the basis of almost all games out there), but how about
some more sophisticated shapes? The GLU helper library can help us again, this time with a sphere. Again, go
to the display() method and replace the triangle section at the end:
public void display(GLAutoDrawable drawable) {
// Set camera.
setCamera(gl, glu, 30);

// Draw sphere (possible styles: FILL, LINE, POINT).


gl.glColor3f(0.3f, 0.5f, 1f);
GLUquadric earth = glu.gluNewQuadric();
glu.gluQuadricDrawStyle(earth, GLU.GLU_FILL);
glu.gluQuadricNormals(earth, GLU.GLU_FLAT);
glu.gluQuadricOrientation(earth, GLU.GLU_OUTSIDE);
final float radius = 6.378f;
final int slices = 16;
final int stacks = 16;
glu.gluSphere(earth, radius, slices, stacks);
glu.gluDeleteQuadric(earth);
}
We set the camera to distance 30 to better coincide with the 6.378 km earth radius, then set a nice blue color
(RGB = 0.3, 0.5, 1.0) and command GLU to paint a sphere. Now, GLU uses a concept called "quadrics", which
is really just an identifier for a lot of behind-the-scenes work: A sphere is actually drawn using a lot of quads
("rectangle floating in space").
Everytime we want to use a GLU geometry, we have to create one with gluNewQuadric() and, at the end, delete
it with gluDeleteQuadric(). Everything in between depends on the geometry.
Above, you first see that we specify the draw style. FILL means to fill the quads that make up the sphere's shell;
it is not the volume of the sphere. Try LINE and POINT to see what they mean.
Next, we specify the normals. Normals are vectors that are perpendicular to a quad, specifying where "up" is
when standing on the rectangle. This is important to OpenGL when calculating light effects (see below). Next,
the orientation of the quads is either INSIDE or OUTSIDE; you would use the former if you wanted to paint
some kind of cave to stand in, using a sphere.
And then, the sphere itself: We call gluSphere() with the quadric identifier, radius, and slices and stacks (think
"longitude and latitude detail along z-axis"). The result looks like the following:

Our first sphere


The sphere looks kind of flat, but that is ok: After all, we specified only a blue color, and no lights or shadows,
so where should the "3D effect" come from? Since this is not nice, we are going to change this in the next
section.
For this section though, you could have a look at the MyJoglCanvasStep3.java source file.

Step 4: Lighting
Lighting is where the whole color model is exchanged: That's right, throw glColor3f() away. Instead, we
specify how a rectangle surface reacts to light in its RGB spectrum (like in the real world), and we add light
sources with their own RGB spectrum (also like in the real world).
Before we begin, let me brief you on the kind of lights. We will be using AMBIENT and SPECULAR light, and
our surface "material" will react on this light. Ambient light is just everywhere, with no particular source (no
"3D effect" with this). We will typically use it at strength 0.2 (of 0..1) so that we can see places dimly where no
light spot is shining. Specular light is the one coming from a spot, and reflecting from surfaces; this gives the
typical "3D effect". I normally use it at strength 0.8 across the spectrum.
In standard OpenGL, you can have eight light sources, but we will be only using one. You have to specify them
before drawing (remember that OpenGL is just a stupid pipeline...), otherwise light will not be used correctly.
Once again, we go into the display() method and set everything:
public void display(GLAutoDrawable drawable) {
// Prepare light parameters.
float SHINE_ALL_DIRECTIONS = 1;
float[] lightPos = {-30, 0, 0, SHINE_ALL_DIRECTIONS};
float[] lightColorAmbient = {0.2f, 0.2f, 0.2f, 1f};
float[] lightColorSpecular = {0.8f, 0.8f, 0.8f, 1f};

// Set light parameters.


gl.glLightfv(GL.GL_LIGHT1, GL.GL_POSITION, lightPos, 0);
gl.glLightfv(GL.GL_LIGHT1, GL.GL_AMBIENT, lightColorAmbient, 0);
gl.glLightfv(GL.GL_LIGHT1, GL.GL_SPECULAR, lightColorSpecular, 0);

// Enable lighting in GL.


gl.glEnable(GL.GL_LIGHT1);
gl.glEnable(GL.GL_LIGHTING);

// Set material properties.


float[] rgba = {0.3f, 0.5f, 1f};
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, rgba, 0);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, rgba, 0);
gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, 0.5f);

// Draw sphere (possible styles: FILL, LINE, POINT).


GLUquadric earth = glu.gluNewQuadric();
...
}
You see that a lot is going on. First, we define the light parameters: The position of our only light source: A 3D
coordinate (-30, 0, 0) slightly to the left, with a fourth element set to 1. This is necessary to shine in all
directions, but if you want to know why, ask another tutorial. Next, you state the RGBA value of AMBIENT
and SPECULAR light (here: white light, evenly distributed). The alpha value is always 1, meaning full opacity
("no glass effect at all").
In the next three lines, we bind these parameters to a specific light, namely LIGHT1, by specifying the
parameter to set, the appropriate array, and an index where to start in this array (this could be different from 0 if
you use one giant array for performance reasons). Finally, we enable LIGHT1 and, most importantly,
LIGHTING at all! From now an, everything will be lighted.
Please note that if you do not change the lighting between fps frames, you could put everything so far into the
init() method instead of display(). This would bring a bit of performance.
Now that we have finished writing the light sources, it is time to specify how the material reacts to the light
across the RGB spectrum: This is what used to be "the color". We take the same blue color as before and add an
alpha of 1 (full opacity); then specify it both as reaction to AMBIENT and to SPECULAR light (this is not
necessarily so! Try experimenting if you like).
The last thing before we start drawing our sphere is the SHININESS, which specifies how strong the reflection
on the surface is (0..100). We set it to 0.5 here, so it has not much effect. And then, we draw the same sphere as
before -- the result:

A lighted sphere
Much better, I would say! The light source from the left illuminates the left side of the sphere with specular 0.8
light, while the darker right side is purely made visible by the ambient 0.2 light.
As always, please follow the code by inspecting the MyJoglCanvasStep4.java source file.
Step 5: Earth Texture
We can do even better: A texture is what brings a form truly alive, by adding a pattern that our human eyes like
so much. In case of planet textures, you can get a good one from Planet Pixel Emporium; the 1000x500 texture
is freeware. So now, how do we apply it to our sphere?
Two steps need to be taken: First, we need to load the texture from a PNG file and assemble it into a form
JOGL can understand. Second, just before drawing the sphere, we have to tell JOGL that the next thing coming
should use the texture ("bind the texture to the GL context").
The first step used to be difficult with OpenGL (see excerpt); in JOGL, we have helper classes such as the
TextureRenderer that do most of the things for us. In the init() method,we load the texture:
public void init(GLAutoDrawable drawable) {
// Load earth texture.
try {
InputStream stream = getClass().getResourceAsStream("earth.png");
TextureData data = TextureIO.newTextureData(stream, false, "png");
earthTexture = TextureIO.newTexture(data);
}
catch (IOException exc) {
exc.printStackTrace();
System.exit(1);
}
}
For this purpose, we retrieve an input stream (standard Java IO: image must be in same package as class) and
command TextureIO to read the texture data; apart from the stream, we specify false to supress generation of
mipmaps ("up/downscaled versions of the texture for different viewing distances"), and supply the file ending.
Then, we generate an OpenGL earthTexture from the data, again using TextureIO.
In the display() method, we put this earthTexture into use, by binding the texture to the GL context before
proceeding with drawing the sphere.
public void display(GLAutoDrawable drawable) {
// Set material properties.
float[] rgba = {1f, 1f, 1f};
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, rgba, 0);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, rgba, 0);
gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, 0.5f);

// Apply texture.
earthTexture.enable();
earthTexture.bind();

// Draw sphere.
GLUquadric earth = glu.gluNewQuadric();
glu.gluQuadricTexture(earth, true);
}
Compared to the previous version, you can see three changes: First, we change the material color to white (1f,
1f, 1f), because now the texture shall define the color of each spot on the surface. Then, we enable() textures
globally (switch this off again if you want an untextured surface come next), and then bind() the specific texture
for whatever comes next.
And next comes the GLU sphere! Here, we need to make only one change: Announce that we indeed want to
use the current texture for the next form. This is kind of redundant, and GLU specific: If you draw triangles (or
quads), you wont have to do this.
The rest plays out as before, resulting in the following:
A textured sphere
And this is the earth! Seen from the south pole, and that is because GLU always paints at 0, 0, 0 and along the
z-axis; and we are looking along the z-axis into the monitor. No worry, we can change the zero position
("translation"), and also the alignment of the body ("rotation") -- stuff for the next chapter!
For a review of what we have done so far, have a look at the MyJoglCanvasStep5.java source file.

Step 6: Satellite on Orbit


So now we have the basics of triangles, bodies, light and material, and texture. In our toolkit, there is only one
basic thing missing: How to arrange the bodies we draw in the 3D space. You only need to memorize two
methods names here: glTranslate() and glRotate().
What I want to do with you is to paint a satellite on a circular orbit around earth. This satellite will be a cube
with two solar panels. Clearly, it is unfeasible to manually calculate all those coordinates for each point within
the orbit circle. Therefore, we will simply shift what is considered to be the zero point (0, 0, 0).
Before any drawing begins, we load a secondary texture for the solar panel in the init() method. This also
demonstrates nicely how to use multiple textures. I used GIMP and a scanned foto to produce a solar panel
texture with font on it.
public void init(GLAutoDrawable drawable) {
// Load the solar panel texture.
try {
InputStream stream = getClass().getResourceAsStream("solar_panel.png");
TextureData data = TextureIO.newTextureData(stream, false, "png");
solarPanelTexture = TextureIO.newTexture(data);
}
catch (IOException exc) {
exc.printStackTrace();
System.exit(2);
}
}
Having that, once again we go into the display() method, this time computing the orbit position and drawing a
satellite (silver cylinder plus textured panel). For a start, we will cover the cylinder at the right position.
public void display(GLAutoDrawable drawable) {
// Save old state.
gl.glPushMatrix();

// Compute satellite position.


satelliteAngle = (satelliteAngle + 1f) % 360f;
final float distance = 10.000f;
final float x = (float) Math.sin(Math.toRadians(satelliteAngle)) * distance;
final float y = (float) Math.cos(Math.toRadians(satelliteAngle)) * distance;
final float z = 0;
gl.glTranslatef(x, y, z);
gl.glRotatef(satelliteAngle, 0, 0, -1);
gl.glRotatef(45f, 0, 1, 0);

// Set silver color, and disable texturing.


gl.glDisable(GL.GL_TEXTURE_2D);
float[] ambiColor = {0.3f, 0.3f, 0.3f, 1f};
float[] specColor = {0.8f, 0.8f, 0.8f, 1f};
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, ambiColor, 0);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, specColor, 0);
gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, 90f);

// Draw satellite body.


final float cylinderRadius = 1f;
final float cylinderHeight = 2f;
GLUquadric body = glu.gluNewQuadric();
glu.gluQuadricTexture(body, false);
glu.gluQuadricDrawStyle(body, GLU.GLU_FILL);
glu.gluQuadricNormals(body, GLU.GLU_FLAT);
glu.gluQuadricOrientation(body, GLU.GLU_OUTSIDE);
gl.glTranslatef(0, 0, -cylinderHeight / 2);
glu.gluDisk(body, 0, cylinderRadius, cylinderSlices, 2);
glu.gluCylinder(body, cylinderRadius, cylinderRadius, cylinderHeight, slices, stacks);
gl.glTranslatef(0, 0, cylinderHeight);
glu.gluDisk(body, 0, cylinderRadius, cylinderSlices, 2);
glu.gluDeleteQuadric(body);
gl.glTranslatef(0, 0, -cylinderHeight / 2);
}
The first thing you see is glPushMatrix(). Since we will be tinkering with the default position, a backup is
useful; after we are done drawing, we will restore it via glPopMatrix(). Next, we compute the satellite position:
A circle around the z axis, computed with sin() und cos() from the standard Java libraries. To track the current
position, we will simply store a satelliteAngle (0..359) which advances 1 degree with each display() call.
And then, the interesting thing happens: Using glTranslatef(), we shift the default position to where the cylinder
should be; so for the next GL or GLU commands, their "local" coordinate (0, 0, 0) is at our "global" (x, y, z).
You also see another position manipulation: With glRotatef(), the local coordinate system can be rotated: First
around the z axis according to the satellite angle, then 45 degrees around the y axis (so we have a nice isometric
view on the cylinder and the panels).
Setting the material properties for the cylinder begins with a glDisable(GL_TEXTURE_2D). This is because
the earth texture we used earlier is still in effect: If we would draw the cylinder now, it would be earth textured.
The rest of the material properties is simple: Gray color in ambient and specular spectrum, and a high shininess
rating (90) to let the silver reflect light properly.
The cylinder itself should look somewhat familiar: A new quadric is created, and here it is used 3 times: 2 disks
("top and bottom of the pipe") and a GLU cylinder (which is a pipe, not a can). Since GLU always works from
its local (0, 0, 0), we have to glTranslatef() along the z axis while drawing disk, cylinder, disk. Afterwards, the
last translation restores the previous state.
Since this is the last chapter, I will now load even more stuff on your brain: We will do the solar panel texturing
the "classic way". If you ever want to texture quads or triangles by yourself (not using GLU), you will need
this. To make things simpler, we will draw only one large solar panel, which goes straight through the cylinder.
public void display(GLAutoDrawable drawable) {
// Set white color, and enable texturing.
gl.glEnable(GL.GL_TEXTURE_2D);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, rgba, 0);
gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, rgba, 0);
gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, 0f);
// Draw solar panels.
gl.glScalef(6f, 0.7f, 0.1f);
solarPanelTexture.bind();
gl.glBegin(GL.GL_QUADS);
final float[] frontUL = {-1.0f, -1.0f, 1.0f};
final float[] frontUR = {1.0f, -1.0f, 1.0f};
final float[] frontLR = {1.0f, 1.0f, 1.0f};
final float[] frontLL = {-1.0f, 1.0f, 1.0f};
final float[] backUL = {-1.0f, -1.0f, -1.0f};
final float[] backLL = {-1.0f, 1.0f, -1.0f};
final float[] backLR = {1.0f, 1.0f, -1.0f};
final float[] backUR = {1.0f, -1.0f, -1.0f};
// Front Face.
gl.glNormal3f(0.0f, 0.0f, 1.0f);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3fv(frontUR, 0);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3fv(frontUL, 0);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3fv(frontLL, 0);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3fv(frontLR, 0);
// Back Face.
gl.glNormal3f(0.0f, 0.0f, -1.0f);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3fv(backUL, 0);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3fv(backUR, 0);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3fv(backLR, 0);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3fv(backLL, 0);
gl.glEnd();

// Restore old state.


gl.glPopMatrix();
}
First, we enable textures again with glEnable(GL.GL_TEXTURE_2D). Note that we could also have used
satelliteTexture.enable(), as we did before with the earthTexture. Material properties are set to white, with no
shininess.
The plan for the next section is to draw front and back face of a cube, both textured. Since we want a flat, long
block rather than a die, we have to distort the dimensions: And we do it with glScalef(). In the float array
section, you see the definition of the cube's corners: Upper left, upper right, lower right and lower left for both
front and back face.
We draw GL_QUADS (rectangles). Note that you have to specify glBegin() and glEnd() only once: Every four
vertices, OpenGL assumes automatically that a new quad begins. But we do not only specify the vertices
("corners"): For each corner of the rectangle, we map a corner of the texture, whereby (0, 0) is the upper left,
and (1, 1) the lower right corner of the texture.
Additionally, we specify the normal vector ("arrow pointing into the sky") for each rectangle, using
glNormal3f(). This is required for the lighting, so OpenGL knows where and how to reflect light. This is also
the reason why the corners and their texture corner mappings may seem counterintuitive sometimes: Depending
on the normal vector, we have to switch left/right on the texture.
And that is it! This last step was kind of long, but we are rewarded by this sight:
A satellite in orbit
You will note that half of the time, the solar panel is dark: This is when the sunlight strikes the back of the
panel, which is reflected not into your eye, but into the screen, away from you.
And as the final source code of this tutorial, please have a look at the MyJoglCanvasStep6.java source file.

Conclusion
In this tutorial, we have walked through quite some JOGL functionality. We covered basic structures like
triangles, quads and spheres in three dimensional space; directional lighting and material properties; and
textures, which we all like so much. Finally, we worked some movement into our scene. Personally, I have
worked further on the orbiter, which currently looks like this:

Orbiter (breed) 0.2


And now, there is very much more to discover for you! (e.g. diffuse lighting, translucency, or the great JOGL
FontRenderer). I hope you have been able to learn something from this tutorial; for improvements, I
appreciate all comments (preferably via mail to kain at the above domain). Thanks for leaving a part
of your attention span here, and have a good time!

You might also like