So you’re back, and you’ve been wondering why did Greg leave me high and dry when the time came to actually put textures on the screen. Well something unfortunate happened that has been draining my time… I picked up Bioware’s Knight’s of the Old Republic and everytime I sat down to work on this tutorial, the call of the Jedi order got in the way But fortunately I’ve reached a logical stopping point and have determined to finish this tutorial and get you folks going so the next game that I?m obsessed with is written in Java!
The first thing we want to do is come up with a class to encapsulate the texture itself. The reason I created this class is two-fold. First, I wanted to have something that I could quickly grab from a cache if I ever needed to refer to a texture or its information. The other reason I created this class is because there was something similar in LWJGL and I wanted to preserve that functionality so that I didn’t have to change too much of my code. The texture class is mostly boilerplate, storing the following properties:
private String name;
protected int width, height;
protected int textureID;
protected BufferedImage bufferedImage;
protected int target;
protected int srcPixelFormat;
protected int dstPixelFormat;
protected int minFilter;
protected int magFilter;
protected boolean wrap;
protected boolean mipmapped;
One thing of importance for you as an OpenGL developer is the textureID. This is the textureID that you will bind to when you want a certain texture to be active. Also of importance is that the texture has a name. The reason I have a name here won?t be immediately obvious to you, but if you?ve ever dealt with a commercial engine or a real game you will quickly come to respect the value of being able to access and manipulate a texture by its name. Your artists will also like the fact that they can name textures (and when we get to the thing on loaders ? materials) and be able to access them in your tools.
There is only one method in this class that doesn?t deal with the actual texture data, and that is the bind method. This method is what you would call when you want to have that texture active (note I?m only talking about texturing in multipass, I will cover multitexture in the next tutorial).
protected void bind( GL gl )
{
gl.glBindTexture( target, textureID );
}
Cool, so now you have something that effectively wraps a texture. Now we will add some architecture to better handle textures as a whole. The first thing you will want to do is create your textures. You could set up you access methods such that Textures can be created directly, but from an architectural standpoint, that’s not all that great. I have created a class TextureFactory which has several methods for actually creating these Texture objects.
TextureFactory is a singleton and as such there is only ever one instance of the TextureFactory. If you’re not familiar with the Singleton Design Pattern you may want to pick up a book on Design Patterns because it is a very useful and effective pattern and some of my later tutorials will use it and other patterns heavily to reduce the amount of code being written and also to better organize that code in maintainable units.
Anyways, TextureFactory has one method for loading an Image from a file:
protected BufferedImage loadImage( String resourceName ) throws IOException
{
logger.debug("Loading resource [" + resourceName + "]");
BufferedImage bufferedImage = ImageIO.read( new File( resourceName ));
// Flip Image
//
AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
tx.translate(0, -bufferedImage.getHeight(null));
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
bufferedImage = op.filter(bufferedImage, null);
return bufferedImage;
}
You can see here that we are simply using ImageIO to load the texture data in as a BufferedImage. ImageIO is a fairly efficient way to load texture data from an InputStream. The next section of code you see you may not be familiar with. The section that starts with AffineTransform is flipping the image so that the image data is using the same coordinate system as OpenGL.
The next method of merit is the convertImageData method. This method will convert the BufferedImage into a ByteBuffer that we can use. It is important to note that an OpenGL texture is just a collection of bytes just like it is in C/C++. What we need to do is get this ByteBuffer organized in the right format so that when we render the texture we don’t get some gibberish. In a later version of the code you will see some of this section of code change, but I wanted to keep this simple so you could understand what?s going on so that you can be empowered enough to experiment and do cool things on your own.