The next NeHe Lesson, 6, is about textures. However it makes a massive jump here, since it uses a texture from a graphics file, so it conflates reading a file with using a texture. Consequently I'm shifting to the Red Book examples, starting with textures. As the Red Book does, we start here with a programmatically generated texture.
void makeCheckImage() {
for (int i = 0; i < checkImageHeight; i++) {
for (int j = 0; j < checkImageWidth; j++) {
checkImage.put((byte) (i*4));
checkImage.put((byte) (j*4));
checkImage.put((byte) 0);
checkImage.put((byte) 255);
}
}
checkImage.rewind();
}
checkImage here is a ByteBuffer. We use this, where the Red Book uses an array of bytes, because later we use a function glTexImage2D to connect the data to the texture, and this expects a ByteBuffer. checkImageheight and width are just ints (=64). A texture is made of texels, as the displayed screen is made of pixels. Each texel is made of 4 bytes, which are R G and B colors values, plus an A for transparency. So the nested loop is writing texels into the ByteBuffer, left to right along a row, bottom row to top.
Here is the init. You can have 1D textures, but this is 2D. What is going to happen is that we will draw a quad polygon, and put this texture on it. The polygon and the texture might have different sizes and shapes. Consequently one pixel might cover several texels, so it might average them, or use the nearest, and so on. Similarly if one texel covers several pixels:
public void init(GLAutoDrawable drawable) {
GL gl = drawable.getGL();
gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
gl.glShadeModel(GL.GL_FLAT);
gl.glEnable(GL.GL_DEPTH_TEST);
makeCheckImage();
// The following controls the alignment of the start of pixel rows in memory
// 1 means align on bytes.
gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1);
final int[] tmp = new int[1];
// glGenTextures gives you a set of 'names' for textures
// the names are identifying integers
gl.glGenTextures(1, tmp, 0);
texName = tmp[0];
// make the named texture to be the type we want
gl.glBindTexture(GL.GL_TEXTURE_2D, texName);
// say how it repeats if needed
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
// and how texels and pixels map
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER,
GL.GL_NEAREST);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
GL.GL_NEAREST);
// connect to data
gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, checkImageWidth,
checkImageHeight, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE,
checkImage);
}
Here is display. We just draw a quad - but for each vertex, we need to also specify which part of the texture maps to it. texture co-ordinates go from 0,0 to 1,1. So
gl.glTexCoord2f(0.0f, 0.0f);
picks out the bottom left of the texture, and
gl.glVertex3f(-1.0f, -1.0f, -3.0f);
maps it to the bottom left vertex of the quad:
public void display(GLAutoDrawable drawable) {
// like int DrawGLScene(GLvoid)
final GL gl = drawable.getGL();
gl.glLoadIdentity();
gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
gl.glEnable(GL.GL_TEXTURE_2D);
// we want to place the texture on the quad like a decal:
gl.glTexEnvf(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_DECAL);
gl.glBindTexture(GL.GL_TEXTURE_2D, texName);
gl.glBegin(GL.GL_QUADS);
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, -3.0f);
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex3f(-1.0f, 1.0f, -3.0f);
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex3f(1.0f, 1.0f, -3.0f);
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, -3.0f);
gl.glEnd();
gl.glFlush();
gl.glDisable(GL.GL_TEXTURE_2D);
}
The result is:

If we change the alpha value of the texels like this:
void makeCheckImage() {
for (int i = 0; i < checkImageHeight; i++) {
for (int j = 0; j < checkImageWidth; j++) {
checkImage.put((byte) (i*4));
checkImage.put((byte) (j*4));
checkImage.put((byte) 0);
checkImage.put((byte) (i*4));
}
}
checkImage.rewind();
}
then the bottom of the quad (which is white by default) shows through:

Suppose we try to map parts of the texture over (1,1). What happens? For example
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, -3.0f);
gl.glTexCoord2f(0.0f, 2.0f);
gl.glVertex3f(-1.0f, 1.0f, -3.0f);
gl.glTexCoord2f(2.0f, 2.0f);
gl.glVertex3f(1.0f, 1.0f, -3.0f);
gl.glTexCoord2f(2.0f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, -3.0f);
The result is

This is because of
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
Where S is the x axis and T the y axis.
If we change this to
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
we get

which shows what GL_CLAMP does.
Suppose we say:
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex3f(-1.0f, -1.0f, -3.0f);
gl.glTexCoord2f(0.0f, 0.1f);
gl.glVertex3f(-1.0f, 1.0f, -3.0f);
gl.glTexCoord2f(0.1f, 0.1f);
gl.glVertex3f(1.0f, 1.0f, -3.0f);
gl.glTexCoord2f(0.1f, 0.0f);
gl.glVertex3f(1.0f, -1.0f, -3.0f);
Now we are only using the texture from 0,0 to 0.1,0.1. The whole texture has 64X64 texels, but we will only use an area of just over 6 by 6. The result is:

Each texel is covering a lot of pixels (we've altered the texture to make the texels a bit brighter). The result is controlled by
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
which means the pixel gets the nearest texel. If we change this to
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
then each pixel is the average of the 4 nearest texels, which looks like

The left and bottom pixels are using the right and top texels, because we are using the wrap option.