CS 424: Computer Graphics, Fall 2015
Lab 7: Textures

In this lab, you will work on a program that applies image textures to 3D objects. The image texture can come from a resource file in the program. To add some interest, it is also possible to draw an image by hand and use that image as a texture, and it is possible to take a copy of the OpenGL image and draw on top of that. The program window has two panels, one on the left for drawing and one on the right for displaying an OpenGL scene that shows one 3D object. For the cylinder shown on the right, I drew cartoon face, used it as a texture on a cube, copied the cube image to the paint panel, drew some red curves around the cube, and finally used that image as a texture on a cylinder.

For this lab, you will have to work in Java. Textures support in JOGL is quite different from the C API. Since you might want to use textures in your midterm project, you should learn the JOGL version first. (If you do decide to support textures in the project, I suggest that your API should have a method to create a texture from a BufferedImage, as is done in this lab. Leave the problem of getting the image into a BufferedImage to the program that uses your API.)

Before the lab, you should have read Section 4.3 in the textbook.

You will need copies of the seven files in the folder /classes/cs424/lab7-files. There are four Java files and three image files. The main program is Lab7.java, and that is the only file on which you will work. The other files are used by Lab7.java: PaintPanel.java defines the panel that is used for drawing (and most of the menus); Camera.java defines a camera class that is used in the program to let the user rotate the 3D object by dragging with the mouse; and TexturedShapes.java defines all but one of the 3D shapes that are used in the program. Copy the seven files into an Eclipse project that is set up to use JOGL.

This lab is due next Thursday, October 22. I will look for Lab7.java somewhere in your homework folder, in /classes/cs424/homework.

Create Textures

The OpenGL API does not include functions for loading an image texture from an image file. (You have to load the texture from raw pixel color data in memory.) However, JOGL does have methods for creating image textures from image files and from BufferedImages. The image textures are represented as objects of type Texture, one of the classes included in the JOGL API.

You will create the textures for this lab from BufferedImages. You will do that by writing the implementation for two methods, textureFromPainting() and textureFromResource(). These methods are called by the ActionListerner that handles events from the "Texture" menu.

For textureFromPainting(), the texture is created from a BufferedImage containing a copy of the drawing from the paint panel. You can get the BufferedImage by calling paintPanel.copyOSC(). For textureFromResource(), the BufferedImage must be loaded from a resource file. This is standard stuff:

URL textureURL;
textureURL = this.getClass().getClassLoader().getResource( resourceName );
BufferedImage img = ImageIO.read( textureURL );

The resouceName is the name of the image resource file (or a path to the resource, if it is in a non-default package). This code can throw checked exceptions, which must be handled. (Since a resource is included as part of the program, any exception would be a bug in the program.)

A Texture can be created from a BufferedImage, img, using the static method AWTTextureIO.newTexture(). Here is the code that you need; it also sets the texture wrap modes, since GL_REPEAT is not the default in JOGL:

Texture texture;
ImageUtil.flipImageVertically( img );
texture = AWTTextureIO.newTexture(displayGL.getGLProfile(), img, true);
texture.setTexParameteri(gl2, GL2.GL_TEXTURE_WRAP_S, GL2.GL_REPEAT);
texture.setTexParameteri(gl2, GL2.GL_TEXTURE_WRAP_T, GL2.GL_REPEAT);

But of course, there is a problem: The code needs access to a gl2 graphics context for setting the texture parameters. In fact, AWTTextureIO.newTexture also needs access to an OpenGL context internally, and it will throw an exception if none is available. The way that JOGL works is that for OpenGL methods to work, an OpenGL context must be "made current on the current thread." Before calling methods such as display() and init(), the JOGL system makes the appropriate OpenGL context current. However, the methods textureFromPaint and textureFromResource are called by the event-handling thread, not by the JOGL system, and the OpenGL context presumably won't be current when they are called. This means that you need to make the OpenGL context for displayGL current before you create the texture. Furthermore, you must then release the context before returning from the method.

The same problem applies to the method paintingFromOpenGL(), which is already complete. You should study how the problem is handled in that method and copy the code that you need.

Use Textures on Objects

OpenGL will attempt to apply a 2D image texture to a primitive if GL_TEXTURE_2D is enabled when the primitive is drawn. For drawing a primitive with no texture, GL_TEXTURE_2D must be disabled. 2D textures can be enabled and disabled by calling

gl2.glEnable( GL2.GL_TEXTURE_2D );

gl2.glDisable( GL2.GL_TEXTURE_2D );

If tex is a JOGL object of type Texture that represents a 2D texture, then calling tex.enable(gl2) is equivalent to calling gl2.glEnable(GL2.GL_TEXTURE_2D).

To specify which texture to use on the primitive, you have to bind the texture. For a JOGL Texture, tex, the texture can be bound by calling

tex.bind(gl2);

The program Lab7.java has a variable currentTexture of type Texture that holds the texture image that should be applied to the 3D object. The value of the variable is null if no texture is to be applied.

Add a few lines of code to the display() method to enable and bind the current texture, or to disable texturing if the current texture is null. Once you have done that (and completed the previous part of the lab), you should be able to see textures on all of the objects except for the Triangular Prism.

Texture Coordinates for the Prism

The last object in the "3D Object" menu is a "Triangular Prism." While the other shapes are drawn by methods from TexturedObjects.java, the prism is drawn by the method triangularPrism() in the main file, Lab7.java. In that method, each side is drawn as a separate OpenGL primitive, with an appropriate normal vector. However, no texture coordinates are provided for the vertices of the primitive. (You might look at the prism with several different textures applied—say, Earth and Brick—before defining texture coordinates, and see if you can figure out where the color comes from.)

The coordinates for the vertices of the prism are shown on the left, below. (The value of t is Math.sqrt(3)/4, which gives an equilateral triangle for the base.) You should provide texture coordinates for each vertex in the primitives. The texture should wrap once around the sides of the prism. That is, you should use 1/3 of the texture for each side, and the pieces should meet up nicely at the edges between the sides. For the top and bottom of the prism, you can cut out any triangular area from the texture. The prism with the Earth texture is shown on the right:

It will require some care to get the texture coordinates correct. Plan before you begin! It might be helpful to write out the texture coordinates on a drawing of the prism.

Texture Transformation

Finally, you should implement a texture transformation in the display() method, before the textured object is drawn. The texture transform in this program is used to implement the "texture repeat" commands in the "Texture" menu. Setting a horizontal texture repeat to N means that the texture should be repeated N times horizontally in the space where only one copy of the texture would normally be. This can be implemented by a texture transformation that scales the horizontal texture coordinates by a factor of N. Vertical texture repeat works in the same way. Texture transformations can be used to better adapt an image to a surface when the surface has texture coordinates that are not appropriate for the texture image. For example, the "Brick" texture fits the torus better with horizontal repeat set to 4 and vertical repeat set to 2:

The repeat factors in the program are stored in global integer variables textureRepeatHorizontal and textureRepeatVertical. These variables are already set by the commands in the "Texture" menu. You just need to apply them in the display() method as the horizontal and vertical scale factors in a texture transformation.

The texture transformation in OpenGL 1.1, just like the projection and modelview transformations, is stored as a matrix. To work on that matrix, you must set the matrix mode to GL2.GL_TEXTURE. Remember to set the matrix mode back to GL2.GL_MODELVIEW after setting up the texture transform.