Computer graphics/2013-2014/Laboratory 4
Quick links: front; laboratories agenda, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, JOGL template.
(J)OGL BASICS (PART 3)
editNOTE: Grab the application template from the following link: Computer graphics -- 2013-2014 -- info.uvt.ro/JOGL-Template
Textures
editA texture is a rectangular array of data containing information about color, luminance, color and alpha. Each element in this array is called a texel.
Textures can be 1D, 2D or 3D (used to simulate certain characteristics in the material they depict)
Texture mapping allows one to glue together an image of an object on a polygon. For example images of walls, vegetation, landscapes, etc. NOTE: besides being applied to polygons textures could be also applied to other primitives such as points, lines, polygons, bitmaps and images. It implies mapping texture coordinates to geometric coordinates.
NOTE: you can download textures from the following link: [1].
Links:
Creating and displaying 2D textures
editThe steps involved in generating a 2D texture are as follows:
- Generate a name for our texture using glGenTextures function (method)
- Bind (select) our texture in a target. The target is in our case GL_TEXTURE_2D but can also take other values such as GL_TEXTURE_3D or GL_TEXTURE_1D. The binding is accomplished by using the glBindTexture function (method)
- Specify the pixel storage mode by using the glPixelStorei function (method). This affects how the pixels stored in memory are read
- Define the filters used when the texture is scaled (resized) during the drawing by using the glTexParameteri function (method)
- Construct the texture with the representation of the picture file by using either glTexImage2D or gluBuild2DMipmaps functions (methods) depending on whether we want mipmaps (multiple levels of detail in our texture) or not
- Enable GL_TEXTURE_2D by using the glEnable function (method)
- Render the texture by assigning vertex coordinates to texture coordinates. The texture coordinates are usually given inside the [0, 1] interval. One can specify a texture coordinate by using the glTexCoord2* family functions (methods)
NOTE: an alternate method for generating textures by using the Texture and TextureIO classes is presented at the following link: Computer graphics -- 2007-2008 -- info.uvt.ro/Laboratory 5.
Important: texture size must be a power of 2 (32x32, 64x64, 512x128, etc) for the texture loader to work properly. Failing to provide a power of 2 texture size will result in an error.
The following code fragments exemplifies the previous steps (IMPORTANT - Please be sure to download and copy the classes found inside the following arhive: http://www.info.uvt.ro/~mfrincu/textureManager.zip):
// Import the GLU class so that we can use them.
import javax.media.opengl.glu.GLU;
public class MainFrame [...] {
[...]
// Number of textures we want to create
private final int NO_TEXTURES = 1;
private int texture[] = new int[NO_TEXTURES];
TextureReader.Texture[] tex = new TextureReader.Texture[NO_TEXTURES];
// GLU object used for mipmapping.
private GLU glu;
public void init(GLAutoDrawable canvas)
{
[...]
// Create a new GLU object.
glu = GLU.createGLU();
// Generate a name (id) for the texture.
// This is called once in init no matter how many textures we want to generate in the texture vector
gl.glGenTextures(NO_TEXTURES, texture, 0);
// Define the filters used when the texture is scaled.
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
// Do not forget to enable texturing.
gl.glEnable(GL.GL_TEXTURE_2D);
// The following lines are for creating ONE texture
// If you want TWO textures modify NO_TEXTURES=2 and copy-paste again the next lines of code
// up until (and including) this.makeRGBTexture(...)
// Modify texture[0] and tex[0] to texture[1] and tex[1] in the new code and that's it
// Bind (select) the texture.
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0]);
// Read the texture from the image.
try {
tex[0] = TextureReader.readTexture("path/to/your/image/here");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
// Construct the texture and use mipmapping in the process.
this.makeRGBTexture(gl, glu, tex[0], GL.GL_TEXTURE_2D, true);
[...]
}
public void display(GLAutoDrawable canvas)
{
[...]
// Bind (select) the texture
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0]);
// Draw a square and apply a texture on it.
gl.glBegin(GL2.GL_QUADS);
// Lower left corner.
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex2f(0.1f, 0.1f);
// Lower right corner.
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex2f(0.9f, 0.1f);
// Upper right corner.
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex2f(0.9f, 0.9f);
// Upper left corner.
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex2f(0.1f, 0.9f);
gl.glEnd();
[...]
}
private void makeRGBTexture(GL gl, GLU glu, TextureReader.Texture img, int target, boolean mipmapped) {
if (mipmapped) {
glu.gluBuild2DMipmaps(target, GL.GL_RGB8, img.getWidth(), img.getHeight(), GL.GL_RGB, GL.GL_UNSIGNED_BYTE, img.getPixels());
} else {
gl.glTexImage2D(target, 0, GL.GL_RGB, img.getWidth(), img.getHeight(), 0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, img.getPixels());
}
}
[...]
}
NOTE: While the previous code reads textures from image files, the textures can also be generated by specifying the pixel matrix. This (uses glTexImage2D) and this (uses glDrawPixels) exemplifies how you can achieve this.
Textures can also be repeated or clamped:
- repeated - copies of the texture map tile the surface
- clamped - a single copy of the image appears on a large surface
The previous techniques are useful when one decides to give texture coordinates outside the [0, 1] interval.
[...]
public void init(GLAutoDrawable canvas)
{
[...]
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_CLAMP);
[...]
}
[...]
Links:
Replacing parts of a texture with another
edit(J)OGL allows one to replace parts of a texture with another. This technique is also used for improving performance as replacing a texture is much faster than rebuilding it each time.
The following code fragment shows how one can replace a texture with another. Be sure to generate 2 textures inside init, and change NO_TEXTURES = 1 with NO_TEXTURES = 2 inside your code before testing it.
[...]
private final int NO_TEXTURES = 2;
public void init(GLAutoDrawable canvas)
{
[...]
// Read the texture from the image.
try {
tex[0] = TextureReader.readTexture("path/to/your/image/here");
// This line reads another image that will be used to replace a part of the previous
tex[1] = TextureReader.readTexture("path/to/your/second/image/here");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
[...]
}
public void display(GLAutoDrawable canvas)
{
[...]
// Replace all of our texture with another one.
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0]); // the pixel data for this texture is given by tex[0] in our example.
gl.glTexSubImage2D(GL.GL_TEXTURE_2D, 0, 0, 0, tex[1].getWidth(), tex[1].getHeight(), GL.GL_RGB, GL.GL_UNSIGNED_BYTE, tex[1].getPixels());
// Draw a square and apply a texture on it.
[...]
}
[...]
NOTE arguments 3,4,5 and 6 from the glTexSubImage2D represent the position in pixels (arguments 3 and 4) from where a portion of given width (argument 5) and height (argument 6) will be replaced by the pixel data given as last argument. The width and height need to be smaller than the initial image size (tex[0].getWidth(), tex[0].getHeight() - in our example). The coordinate center (0,0) is situated in the lower left corner of the image.
Links:
Anisotropic Fitering
editMipmapping often produces blurred images for distant textures viewed at oblique angles. This is due to the fact that texture mapping mode assumes the texture space is a square (isotropic) while in reality it is rather long and narrow (anisotropic). To overcome this problem one could apply anisotropic filtering as detailed in the following code fragment:
[...]
public void init(GLAutoDrawable canvas)
{
[...]
if( gl.isExtensionAvailable("GL_EXT_texture_filter_anisotropic") )
{
float max[] = new float[1];
gl.glGetFloatv( GL.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, max, 0 );
gl.glTexParameterf( GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAX_ANISOTROPY_EXT, max[0] );
}
[...]
}
[...]
Texture transparency and blending
editBlending means combining two or more textures by mixing their components.
Transparency means making some pixels transparent. The transparency factor can range from opaque to fully transparent.
Steps for blending
edit- Enable blending by using the glEnable(GL.GL_BLEND) function (method)
- Set the blend function by using the glBlendFunc(mode, mode) function (method)
- Draw the object(s)
- disabling blending by using the gl.glDisable(GL.GL_BLEND) function (method)
Note: (for 3D graphics) there are cases when you need to disable the depth test while blending, by using the glEnable(GL_DEPTH_TEST) and glDisable(GL_DEPTH_TEST) functions (methods)
Steps for transparency (alpha testing)
edit- Enable alpha testing by using the glEnable(GL_ALPHA_TEST) function (method)
- Set alpha testing by using the glAlphaFunc(GL_GREATER, 0.7f) function (method) which tells OGL to discard the pixels with an alpha value greater then 0.7 (for example)
[...]
public void display(GLAutoDrawable canvas)
{
[...]
// Disable blending for this texture.
gl.glDisable(GL.GL_BLEND);
// Bind (select) the texture
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0]);
// Draw a square and apply a texture on it.
gl.glBegin(GL2.GL_QUADS);
// Lower left corner.
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex2f(0.1f, 0.1f);
// Lower right corner.
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex2f(0.9f, 0.1f);
// Upper right corner.
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex2f(0.9f, 0.9f);
// Upper left corner.
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex2f(0.1f, 0.9f);
gl.glEnd();
// Enable blending for this texture.
gl.glEnable(GL.GL_BLEND);
// Set the blend function.
gl.glBlendFunc(GL.GL_SRC_COLOR, GL.GL_DST_ALPHA);
// Bind (select) the texture
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[1]);
// Draw a square and apply a texture on it.
gl.glBegin(GL2.GL_QUADS);
// Lower left corner.
gl.glTexCoord2f(0.0f, 0.0f);
gl.glVertex2f(0.1f, 0.1f);
// Lower right corner.
gl.glTexCoord2f(1.0f, 0.0f);
gl.glVertex2f(0.9f, 0.1f);
// Upper right corner.
gl.glTexCoord2f(1.0f, 1.0f);
gl.glVertex2f(0.9f, 0.9f);
// Upper left corner.
gl.glTexCoord2f(0.0f, 1.0f);
gl.glVertex2f(0.1f, 0.9f);
gl.glEnd();
[...]
}
[...]
IMPORTANT: In order to enable blending we need two textures. As a result the code fragment in the init method for creating a texture needs to be duplicated:
[...]
// Number of textures we want to create
private final int NO_TEXTURES = 2;
[...]
public void init(GLAutoDrawable canvas)
{
[...]
// Create a new GLU object.
glu = GLU.createGLU();
// Generate a name (id) for the texture.
// This is called once in init no matter how many textures we want to generate in the texture vector
gl.glGenTextures(NO_TEXTURES, texture, 0);
// Bind (select) the FIRST texture.
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0]);
// Read the texture from the image.
try {
tex[0] = TextureReader.readTexture("path/to/your/image/here");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
// Define the filters used when the texture is scaled.
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
// Construct the texture and use mipmapping in the process.
this.makeRGBTexture(gl, glu, tex[0], GL.GL_TEXTURE_2D, true);
// Bind (select) the SECOND texture.
gl.glBindTexture(GL.GL_TEXTURE_2D, texture[1]);
// Read the texture from the image.
try {
tex[1] = TextureReader.readTexture("path/to/your/second/image/here");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
// Define the filters used when the texture is scaled.
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);
// Construct the texture and use mipmapping in the process.
this.makeRGBTexture(gl, glu, tex[1], GL.GL_TEXTURE_2D, true);
// Do not forget to enable texturing.
gl.glEnable(GL.GL_TEXTURE_2D);
[...]
}
NOTE: The previous method is inefficient as it requires one to copy several lines of code multiple times. A better approach would be to create a class for handling texture creation and manipulation (binding, enabling, disabling).
Links:
API
editExercises
edit- Create a separate class called TextureHandler for generating a texture and test it by loading and displaying two textures. The class should have the following methods:
- a method called bind() for binding (selecting) the texture. Wrapper for gl.glBindTexture(GL.GL_TEXTURE_2D, texture[0]);.
- a method called enable() for enabling the texture. Wrapper for gl.glEnable(GL.GL_TEXTURE_2D);.
- a method called disable() for disabling the texture. Wrapper for gl.glDisable(GL.GL_TEXTURE_2D);.
- a method called getTexture() for retrieving the TextureReader.Texture object.
- a constructor called TextureHandler(GL gl, GLU glu, String filename, boolean mipmapped) for generating the texture. Much of the code in the init method presented at the beginning of this lab goes in there. The constructor should receive as argument a reference to the GL and GLU objects, the path to the image and a boolean argument which specifies whether mipmapping should be used or not.
- a private method makeRGBTexture(...) as the one presented in this laboratory.
- Create a chessboard by replacing parts of a large white texture with smaller black textures.
- Create two squares on top of each other, apply two textures to them and set up blending. Use 5 different blending options and compare them. Make the squares move on the X axis and let them bounce of the wall (screen margin)
NOTE: Use the TextureHandler that you implemented for the first assignment in the rest of the exercises!