A detailed look at implementing graphically demanding games in OpenGL with Java, using the JOGL library

Wednesday, February 11, 2009

Quick hint - Texture matrix

Just a quick note stemming from a week long bug hunt. The GL_TEXTURE matrix is per texture unit, so if you are using multiple texture units, be sure to get the right one active before pushing, popping or loading a new matrix! e.g.


gl.glMatrixMode(GL.GL_TEXTURE);
gl.glActiveTexture(GL.GL_TEXTURE0);
gl.glPushMatrix();
gl.glLoadIdentity();

...

gl.glPopMatrix();


Misunderstanding this feature can also easily lead to stack under flow if you change the active texture in between push and pop pairs.
Of course in retrospect, it's obvious that each texture unit needs its own matrix, but it's just one of those little details that's not well described in the man pages.

Text rendering

I can't think of many (or any!) games that don't have any need for text. From displaying your vital stats to pointing the player in the right direction to providing subtitles for those with hearing difficulties, it's hard to escape at least 5000 years of written tradition.

Rendering text in OpenGL poses some issues. There's no built in text rendering facility, though there are several utility libraries that give you varying degrees of control. But to go old-school and implement your own basic text functions will give you the ultimate control and help you earn serious respect for font designers.

There are two approaches to rendering text - vector and bitmap. Modern computing has sidelined the bitmap font in favor of structured vector fonts, but bitmaps have several advantages in OpenGL - they can be conveniently represented in a texture, and they're fast. And, you can easily customise them, adding colour, embossing, or even pixel shader effects such as normal bump mapping that literally make your text stand up off the page.

I've chosen a small-caps font. This means I have to draw fewer glyphs and kern fewer character pairs (more later on kerning). Here's my font texture, fresh out of photoshop:
Notice the classy embossed faux-gold effect ;o)

The characters are in an 8 by 8 grid, giving me 64 characters to play with. Now its a simple matter to load this texture, and render texture mapped quads for each character in a string:

private void drawChar(GL gl, int index, float x, float y, float size) {
float tx, ty;
tx = (index % 8) / 8.0f;
ty = (index / 8) / 8.0f;
gl.glBegin(GL.GL_QUADS);
{
gl.glTexCoord2f(tx, ty);
gl.glVertex2f(x, y + 0.8f * size);

gl.glTexCoord2f(tx, ty + 1.0f / 8.0f);
gl.glVertex2f(x, y - 0.2f * size);

gl.glTexCoord2f(tx + 1.0f / 8.0f, ty + 1.0f / 8.0f);
gl.glVertex2f(x + size, y - 0.2f * size);

gl.glTexCoord2f(tx + 1.0f / 8.0f, ty);
gl.glVertex2f(x + size, y + 0.8f * size);
}
gl.glEnd();
}

But hang on a second, we can do better than that. Used naively, we'd get ugly monospaced text, where consecutive i's look stranded and the W's are almost overlapping. We need kerning.

To get the kerning right is pretty hard. It's a subjective matter that requires an artful eye, but I thought I'd give it a go anyway. I wrote a quick app that displays grids of letter pairs, and allows the user to adjust the kerning by left and right clicking:


So - nearly 2000 kerning pairs later, we have a variable width font that can display whatever strings, in whatever size is needed.

The GLText class can be found here in the JavaPop project (though it is pretty standalone if you want to re-use it; you'll just have to pick somewhere in your project to keep the kerning2.dat file) and the kerning tool is in the JavaPopTools project. Let me know if you have any similar bitmap fonts that need kerning and maybe I'll see what I can do.

Saturday, January 24, 2009

JavaPop update

It's been quiet here but I've been busy working away on JavaPop. Several new features have come together recently, so I've released a new .jar so you can try earthquakes, lightning, volcanoes and so on for yourself. Give it a whirl and tell me which area I should work on next.

While there are buttons for all the effects from Populous II: Trials of the Olympian Gods, only the raise/lower land, move papal magnet, swamp, earthquake, batholith, volcano and basalt do anything at the moment. And there are no victory conditions, although there is a very dumb AI to play against. He will try to expand his settlement, but will not attack you yet.

Saturday, January 3, 2009

Models

It can get pretty difficult to generate all your geometry with calls to glVertex(). Wouldn't it be nice to use a 3d modeling app to design and texture your models, and then be able to render those models from Java?

Well - OK then, you've twisted my arm. Example source is at Tutorial 5. The fundamentals here are creating a file that contains four things - vertex positions, normals, texture coordinates, and triangle indices - and then importing it in Java. I used Blender, but you should be able to use 3D Studio, Lightwave or your favourite modeler. I'm not going to go into detail about using the modeler but I will point you to these fine Blender tutorials:
  1. Introduction to Character Animation
  2. UV Mapping Basics
Once you have your texture mapped, normal applied model, we need to save it in a format that contains the above mentioned four sets of data in a reasonable format. Even though we're using OpenGL, the DirectX .x format is a reasonable choice. It contains all of the above in a nice plain text format that's easy to read in. A hint though: in Blender, go back to Object mode and select the object you wish to export before using the .x exporter. Using the exporter in Edit mode ignores the normals and provides different texture coords, at least for me.

If you want to skip the model creation bit, you can use the very simple model I created. Just don't laugh too much.

To load the model, I've rolled the input and rendering into a class called XModel. It *very* lazily parses the .x file, and I can't guarantee that it will work for anything but the simplest models produced by the blender exporter. The key points to watch are the parse*() methods, which each take a portion of the file and fill an ArrayList with data. Then these are cut down to size by deduplicate(), which merges identical vertices, and finally passed to fillbuffer():
private void fillBuffer() {
b = BufferUtil.newFloatBuffer(triangleCount * 3 * vertexstride);
int index;
Vector8 v;
for (int i = 0; i < indices.size(); i++) {
index = indices.get(i);
v = vertices.get(index);
b.put(v.x);
b.put(v.y);
b.put(v.z);
b.put(v.nx);
b.put(v.ny);
b.put(v.nz);
b.put(v.tx);
b.put(v.ty);
}
}


This just gives us a plain old FloatBuffer that we can pass to glDrawArrays. After loading and enabling the texture (using TextureIO which I suppose is sort of cheating ;o) we get:



I said don't laugh! By the way, I'm open to assistance with the graphics and models for JavaPop. Any aspiring artists out there who want to draw 5 or so frames of walking and a dozen or so houses please leave a comment!