The Quad textured
From LWJGL
Contents |
Introduction
This tutorial will cover how to apply textures to a model, as with the other “quad” tutorials we’ll build on the former knowledge about VBO’s and Shaders. We’ll talk about what to do to make sure textures are rendered on the screen. If you are a unsure how to create an interleaved VBO or how to setup a shader read the previous Quad tutorials first. As always you can find the complete source code at the end of the tutorial. Let’s begin.
Resources
For this tutorial we’ll use an extra library to load our images, and we’ll also use a set of images of course. The image loader we use is TWL’s PNG decoder tool which you can find here: [1]. It’s a JAR file that you must add to your project’s libraries.
Finally the textures we’ll use are a set of “UV grids”. We’ll talk about UV’s a bit later. We’re going to use 2 textures, these are PNG images but the original came from this site: [2]. Pick 2 of them and convert them into PNG’s with your image editor of choice.
Texture Units
The first thing we must know is that OpenGL uses a list of texture units. Just like with attribute lists of a VBO there are only a set amount of texture units available. The number of texture units varies, the minimum that must be available is 2. We can get these texture units by calling “GL_TEXTUREX” where X is the unit of the texture. The 2 units that must be supported are GL_TEXTURE0 and GL_TEXTURE1.
Because a model can use many different textures (diffuse, specular, normal, baked shadow, …) we must tell OpenGL what texture to place where. In this tutorial we’ll only use GL_TEXTURE0, because our shader only looks at the diffuse texture. A diffuse texture is just the base color of the object. Up until now we’ve assigned colors to our vertices and OpenGL interpolated between the vertices creating our colored Quad. By using textures we can map an image on to this quad and have easy control over each pixels color.
(Texture) Coordinates
Most of the time we want to create a 3D render of something so our vertices have 3 components (X, Y, Z). While our quad has no depth, the vertices still have a Z value (0). Texture coordinates however only have 2 components (S, T). 3D modelers know another name for these coordinates namely “UV’. Here’s a graphical presentation of the coordinates that OpenGL uses:
The XYZ components have nothing to do with the ST components, as the process of going from XYZ to ST is more often called unwrapping where an artist can define each individual vertex it’s ST position independent of the XYZ position. The graphical example are the exact numbers we are using in this tutorial. We’ve updated the custom Vertex so we can assign ST values to it. We define our vertices like this:
// We'll define our quad using 4 vertices of the custom 'TexturedVertex' class TexturedVertex v0 = new TexturedVertex(); v0.setXYZ(-0.5f, 0.5f, 0); v0.setRGB(1, 0, 0); v0.setST(0, 0); TexturedVertex v1 = new TexturedVertex(); v1.setXYZ(-0.5f, -0.5f, 0); v1.setRGB(0, 1, 0); v1.setST(0, 1); TexturedVertex v2 = new TexturedVertex(); v2.setXYZ(0.5f, -0.5f, 0); v2.setRGB(0, 0, 1); v2.setST(1, 1); TexturedVertex v3 = new TexturedVertex(); v3.setXYZ(0.5f, 0.5f, 0); v3.setRGB(1, 1, 1); v3.setST(1, 0);
Using the attribute lists
If you are unfamiliar with the workings of our custom vertex class see the code at the end or read the tutorial about “interleaving”. We are still interleaving our vertex data, so let’s do that now with the extra ST data:
// Create a new Vertex Buffer Object in memory and select it (bind) vboId = GL15.glGenBuffers(); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW); // Put the position coordinates in attribute list 0 GL20.glVertexAttribPointer(0, TexturedVertex.positionElementCount, GL11.GL_FLOAT, false, TexturedVertex.stride, TexturedVertex.positionByteOffset); // Put the color components in attribute list 1 GL20.glVertexAttribPointer(1, TexturedVertex.colorElementCount, GL11.GL_FLOAT, false, TexturedVertex.stride, TexturedVertex.colorByteOffset); // Put the texture coordinates in attribute list 2 GL20.glVertexAttribPointer(2, TexturedVertex.textureElementCount, GL11.GL_FLOAT, false, TexturedVertex.stride, TexturedVertex.textureByteOffset); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
Texture Shaders
In OpenGL 3.x and up we have to rely on shaders to do all of the graphical work. Therefore we must update our shaders so it can read and process the ST data. Mapping textures to the model is done in the “fragment” shaders. Because we are defining individual pixels and that’s the job of the fragment shader.
Vertex Shader
But it all starts with the vertex shader, so we have to define a new “in” variable in our vertex shader and simply pass the values to our fragment shader:
#version 150 core
in vec4 in_Position;
in vec4 in_Color;
in vec2 in_TextureCoord;
out vec4 pass_Color;
out vec2 pass_TextureCoord;
void main(void) {
gl_Position = in_Position;
pass_Color = in_Color;
pass_TextureCoord = in_TextureCoord;
}
Fragment Shader
And now our fragment shader can use the texture coordinates to sample our texture and apply color to our pixels:
#version 150 core
uniform sampler2D texture_diffuse;
in vec4 pass_Color;
in vec2 pass_TextureCoord;
out vec4 out_Color;
void main(void) {
out_Color = pass_Color;
// Override out_Color with our texture pixel
out_Color = texture2D(texture_diffuse, pass_TextureCoord);
}
Notice that the fragment shader “samples” our texture, which it can access by our uniform variable “texture_diffuse” (of the type sampler2D, remember this for later). It must sample the texture because our screen pixels will usually not be a 1 to 1 copy of the texture pixels. If we zoom in our zoom out we’ll need more or less pixels. If we need 3 pixels where there originally only were 2, the sampler will create a middle pixel by for example averaging the neighboring pixels.
Binding the shader variables
In any case, we now have 3 kinds of data for each vertex: an XYZ coordinate, a color and an ST coordinate. We must bind our texture coordinate variable of the vertex shader to an attribute list of our VBO:
// Position information will be attribute 0 GL20.glBindAttribLocation(pId, 0, "in_Position"); // Color information will be attribute 1 GL20.glBindAttribLocation(pId, 1, "in_Color"); // Textute information will be attribute 2 GL20.glBindAttribLocation(pId, 2, "in_TextureCoord");
Remember to use the exact same variable names when binding attributes to an attribute list.
Loading textures
We’ve got everything hooked up, all we need is to load the actual texture. To import the images we’re going to use an external library called the TWL PNG decoder. How this is done is of no importance, what is important is that OpenGL requires a buffer of bytes that resemble the pixel color components. Which is what we get from the PNG decoder:
ByteBuffer buf = null;
int tWidth = 0;
int tHeight = 0;
try {
// Open the PNG file as an InputStream
InputStream in = new FileInputStream(filename);
// Link the PNG decoder to this stream
PNGDecoder decoder = new PNGDecoder(in);
// Get the width and height of the texture
tWidth = decoder.getWidth();
tHeight = decoder.getHeight();
// Decode the PNG file in a ByteBuffer
buf = ByteBuffer.allocateDirect(
4 * decoder.getWidth() * decoder.getHeight());
decoder.decode(buf, decoder.getWidth() * 4, Format.RGBA);
buf.flip();
in.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
After we run this code, we get a buffer filled with pixel data in the RGBA format. We must also tell OpenGL what format the buffer is filled with, thanks so the PNG decoder we can simply pick one that fits our situation. RGBA is a very common format, another common format is BRG(A) which DirectX uses.
Next we’ll create our texture object in memory, the “textureUnit” variable is the Texture Unit as explained in the beginning of this tutorial. We must of course bind this texture object to apply change, the first change is to push the byte buffer to memory:
// Create a new texture object in memory and bind it int texId = GL11.glGenTextures(); GL13.glActiveTexture(textureUnit); GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId); // All RGB bytes are aligned to each other and each component is 1 byte GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
Remember that our shader variable is a sampler2D, the 2D part tells OpenGL what type of texture it is (a 3D type also exists for example). When uploading the buffer we must use the appropriate texture type:
// Upload the texture data and generate mip maps (for scaling) GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, tWidth, tHeight, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf); GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
A good explanation of how the different kinds of “wraps” work can be found here [3].
Done, we’ve now uploaded our texture to OpenGL (memory). In this example we’ll use 2 textures and switch between them by using the keyboard, we’ll use an integer array to hold the ID’s:
texIds[0] = this.loadPNGTexture("assets/stGrid1.png", GL13.GL_TEXTURE0);
texIds[1] = this.loadPNGTexture("assets/stGrid2.png", GL13.GL_TEXTURE0);
Notice that we both upload them to texture unit 0. If we have multiple smapler variables in our shader we can assign textures to the different variables by using a different texture unit. We select what texture we’re going to display by pressing 2 keys on the keyboard:
while(Keyboard.next()) {
// Only listen to events where the key was pressed (down event)
if (!Keyboard.getEventKeyState()) continue;
// Switch textures depending on the key released
switch (Keyboard.getEventKey()) {
case Keyboard.KEY_1:
textureSelector = 0;
break;
case Keyboard.KEY_2:
textureSelector = 1;
break;
}
}
Of course we’ll have to bind the texture in our rendering loop:
GL20.glUseProgram(pId); // Bind the texture GL13.glActiveTexture(GL13.GL_TEXTURE0); GL11.glBindTexture(GL11.GL_TEXTURE_2D, texIds[textureSelector]);
And destroy them when the application closes:
// Delete the texture GL11.glDeleteTextures(texIds[0]); GL11.glDeleteTextures(texIds[1]);
The result
Here’s how the program looks, we can switch between the 2 textures by pressing the 1 and 2 keys at the top of our keyboard:
Complete source code
Vertex shader
#version 150 core
in vec4 in_Position;
in vec4 in_Color;
in vec2 in_TextureCoord;
out vec4 pass_Color;
out vec2 pass_TextureCoord;
void main(void) {
gl_Position = in_Position;
pass_Color = in_Color;
pass_TextureCoord = in_TextureCoord;
}
Fragment shader
#version 150 core
uniform sampler2D texture_diffuse;
in vec4 pass_Color;
in vec2 pass_TextureCoord;
out vec4 out_Color;
void main(void) {
out_Color = pass_Color;
// Override out_Color with our texture pixel
out_Color = texture2D(texture_diffuse, pass_TextureCoord);
}
TexturedVertex class
ublic class TexturedVertex {
// Vertex data
private float[] xyzw = new float[] {0f, 0f, 0f, 1f};
private float[] rgba = new float[] {1f, 1f, 1f, 1f};
private float[] st = new float[] {0f, 0f};
// The amount of bytes an element has
public static final int elementBytes = 4;
// Elements per parameter
public static final int positionElementCount = 4;
public static final int colorElementCount = 4;
public static final int textureElementCount = 2;
// Bytes per parameter
public static final int positionBytesCount = positionElementCount * elementBytes;
public static final int colorByteCount = colorElementCount * elementBytes;
public static final int textureByteCount = textureElementCount * elementBytes;
// Byte offsets per parameter
public static final int positionByteOffset = 0;
public static final int colorByteOffset = positionByteOffset + positionBytesCount;
public static final int textureByteOffset = colorByteOffset + colorByteCount;
// The amount of elements that a vertex has
public static final int elementCount = positionElementCount +
colorElementCount + textureElementCount;
// The size of a vertex in bytes, like in C/C++: sizeof(Vertex)
public static final int stride = positionBytesCount + colorByteCount +
textureByteCount;
// Setters
public void setXYZ(float x, float y, float z) {
this.setXYZW(x, y, z, 1f);
}
public void setRGB(float r, float g, float b) {
this.setRGBA(r, g, b, 1f);
}
public void setST(float s, float t) {
this.st = new float[] {s, t};
}
public void setXYZW(float x, float y, float z, float w) {
this.xyzw = new float[] {x, y, z, w};
}
public void setRGBA(float r, float g, float b, float a) {
this.rgba = new float[] {r, g, b, 1f};
}
// Getters
public float[] getElements() {
float[] out = new float[TexturedVertex.elementCount];
int i = 0;
// Insert XYZW elements
out[i++] = this.xyzw[0];
out[i++] = this.xyzw[1];
out[i++] = this.xyzw[2];
out[i++] = this.xyzw[3];
// Insert RGBA elements
out[i++] = this.rgba[0];
out[i++] = this.rgba[1];
out[i++] = this.rgba[2];
out[i++] = this.rgba[3];
// Insert ST elements
out[i++] = this.st[0];
out[i++] = this.st[1];
return out;
}
public float[] getXYZW() {
return new float[] {this.xyzw[0], this.xyzw[1], this.xyzw[2], this.xyzw[3]};
}
public float[] getRGBA() {
return new float[] {this.rgba[0], this.rgba[1], this.rgba[2], this.rgba[3]};
}
public float[] getST() {
return new float[] {this.st[0], this.st[1]};
}
}
Application
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.util.glu.GLU;
import de.matthiasmann.twl.utils.PNGDecoder;
import de.matthiasmann.twl.utils.PNGDecoder.Format;
public class TheQuadExampleTextured {
// Entry point for the application
public static void main(String[] args) {
new TheQuadExampleTextured();
}
// Setup variables
private final String WINDOW_TITLE = "The Quad: Textured";
private final int WIDTH = 320;
private final int HEIGHT = 320;
// Quad variables
private int vaoId = 0;
private int vboId = 0;
private int vboiId = 0;
private int indicesCount = 0;
// Shader variables
private int vsId = 0;
private int fsId = 0;
private int pId = 0;
// Texture variables
private int[] texIds = new int[] {0, 0};
private int textureSelector = 0;
public TheQuadExampleTextured() {
// Initialize OpenGL (Display)
this.setupOpenGL();
this.setupQuad();
this.setupShaders();
this.setupTextures();
while (!Display.isCloseRequested()) {
// Do a single loop (logic/render)
this.loopCycle();
// Force a maximum FPS of about 60
Display.sync(60);
// Let the CPU synchronize with the GPU if GPU is tagging behind
Display.update();
}
// Destroy OpenGL (Display)
this.destroyOpenGL();
}
private void setupTextures() {
texIds[0] = this.loadPNGTexture("assets/stGrid1.png", GL13.GL_TEXTURE0);
texIds[1] = this.loadPNGTexture("assets/stGrid2.png", GL13.GL_TEXTURE0);
this.exitOnGLError("setupTexture");
}
private void setupOpenGL() {
// Setup an OpenGL context with API version 3.2
try {
PixelFormat pixelFormat = new PixelFormat();
ContextAttribs contextAtrributes = new ContextAttribs(3, 2)
.withForwardCompatible(true)
.withProfileCore(true);
Display.setDisplayMode(new DisplayMode(WIDTH, HEIGHT));
Display.setTitle(WINDOW_TITLE);
Display.create(pixelFormat, contextAtrributes);
GL11.glViewport(0, 0, WIDTH, HEIGHT);
} catch (LWJGLException e) {
e.printStackTrace();
System.exit(-1);
}
// Setup an XNA like background color
GL11.glClearColor(0.4f, 0.6f, 0.9f, 0f);
// Map the internal OpenGL coordinate system to the entire screen
GL11.glViewport(0, 0, WIDTH, HEIGHT);
this.exitOnGLError("setupOpenGL");
}
private void setupQuad() {
// We'll define our quad using 4 vertices of the custom 'TexturedVertex' class
TexturedVertex v0 = new TexturedVertex();
v0.setXYZ(-0.5f, 0.5f, 0); v0.setRGB(1, 0, 0); v0.setST(0, 0);
TexturedVertex v1 = new TexturedVertex();
v1.setXYZ(-0.5f, -0.5f, 0); v1.setRGB(0, 1, 0); v1.setST(0, 1);
TexturedVertex v2 = new TexturedVertex();
v2.setXYZ(0.5f, -0.5f, 0); v2.setRGB(0, 0, 1); v2.setST(1, 1);
TexturedVertex v3 = new TexturedVertex();
v3.setXYZ(0.5f, 0.5f, 0); v3.setRGB(1, 1, 1); v3.setST(1, 0);
TexturedVertex[] vertices = new TexturedVertex[] {v0, v1, v2, v3};
// Put each 'Vertex' in one FloatBuffer
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length *
TexturedVertex.elementCount);
for (int i = 0; i < vertices.length; i++) {
// Add position, color and texture floats to the buffer
verticesBuffer.put(vertices[i].getElements());
}
verticesBuffer.flip();
// OpenGL expects to draw vertices in counter clockwise order by default
byte[] indices = {
0, 1, 2,
2, 3, 0
};
indicesCount = indices.length;
ByteBuffer indicesBuffer = BufferUtils.createByteBuffer(indicesCount);
indicesBuffer.put(indices);
indicesBuffer.flip();
// Create a new Vertex Array Object in memory and select it (bind)
vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);
// Create a new Vertex Buffer Object in memory and select it (bind)
vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
// Put the position coordinates in attribute list 0
GL20.glVertexAttribPointer(0, TexturedVertex.positionElementCount, GL11.GL_FLOAT,
false, TexturedVertex.stride, TexturedVertex.positionByteOffset);
// Put the color components in attribute list 1
GL20.glVertexAttribPointer(1, TexturedVertex.colorElementCount, GL11.GL_FLOAT,
false, TexturedVertex.stride, TexturedVertex.colorByteOffset);
// Put the texture coordinates in attribute list 2
GL20.glVertexAttribPointer(2, TexturedVertex.textureElementCount, GL11.GL_FLOAT,
false, TexturedVertex.stride, TexturedVertex.textureByteOffset);
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
// Deselect (bind to 0) the VAO
GL30.glBindVertexArray(0);
// Create a new VBO for the indices and select it (bind) - INDICES
vboiId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
this.exitOnGLError("setupQuad");
}
private void setupShaders() {
// Load the vertex shader
vsId = this.loadShader("src/thequad/vertex_textured.glsl", GL20.GL_VERTEX_SHADER);
// Load the fragment shader
fsId = this.loadShader("src/thequad/fragment_textured.glsl", GL20.GL_FRAGMENT_SHADER);
// Create a new shader program that links both shaders
pId = GL20.glCreateProgram();
GL20.glAttachShader(pId, vsId);
GL20.glAttachShader(pId, fsId);
GL20.glLinkProgram(pId);
// Position information will be attribute 0
GL20.glBindAttribLocation(pId, 0, "in_Position");
// Color information will be attribute 1
GL20.glBindAttribLocation(pId, 1, "in_Color");
// Textute information will be attribute 2
GL20.glBindAttribLocation(pId, 2, "in_TextureCoord");
GL20.glValidateProgram(pId);
this.exitOnGLError("setupShaders");
}
private void loopCycle() {
// Logic
while(Keyboard.next()) {
// Only listen to events where the key was pressed (down event)
if (!Keyboard.getEventKeyState()) continue;
// Switch textures depending on the key released
switch (Keyboard.getEventKey()) {
case Keyboard.KEY_1:
textureSelector = 0;
break;
case Keyboard.KEY_2:
textureSelector = 1;
break;
}
}
// Render
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
GL20.glUseProgram(pId);
// Bind the texture
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texIds[textureSelector]);
// Bind to the VAO that has all the information about the vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);
GL20.glEnableVertexAttribArray(1);
GL20.glEnableVertexAttribArray(2);
// Bind to the index VBO that has all the information about the order of the vertices
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
// Draw the vertices
GL11.glDrawElements(GL11.GL_TRIANGLES, indicesCount, GL11.GL_UNSIGNED_BYTE, 0);
// Put everything back to default (deselect)
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
GL20.glDisableVertexAttribArray(2);
GL30.glBindVertexArray(0);
GL20.glUseProgram(0);
this.exitOnGLError("loopCycle");
}
private void destroyOpenGL() {
// Delete the texture
GL11.glDeleteTextures(texIds[0]);
GL11.glDeleteTextures(texIds[1]);
// Delete the shaders
GL20.glUseProgram(0);
GL20.glDetachShader(pId, vsId);
GL20.glDetachShader(pId, fsId);
GL20.glDeleteShader(vsId);
GL20.glDeleteShader(fsId);
GL20.glDeleteProgram(pId);
// Select the VAO
GL30.glBindVertexArray(vaoId);
// Disable the VBO index from the VAO attributes list
GL20.glDisableVertexAttribArray(0);
GL20.glDisableVertexAttribArray(1);
// Delete the vertex VBO
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboId);
// Delete the index VBO
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboiId);
// Delete the VAO
GL30.glBindVertexArray(0);
GL30.glDeleteVertexArrays(vaoId);
this.exitOnGLError("destroyOpenGL");
Display.destroy();
}
private int loadShader(String filename, int type) {
StringBuilder shaderSource = new StringBuilder();
int shaderID = 0;
try {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line;
while ((line = reader.readLine()) != null) {
shaderSource.append(line).append("\n");
}
reader.close();
} catch (IOException e) {
System.err.println("Could not read file.");
e.printStackTrace();
System.exit(-1);
}
shaderID = GL20.glCreateShader(type);
GL20.glShaderSource(shaderID, shaderSource);
GL20.glCompileShader(shaderID);
if (GL20.glGetShader(shaderID, GL20.GL_COMPILE_STATUS) == GL11.GL_FALSE) {
System.err.println("Could not compile shader.");
System.exit(-1);
}
this.exitOnGLError("loadShader");
return shaderID;
}
private int loadPNGTexture(String filename, int textureUnit) {
ByteBuffer buf = null;
int tWidth = 0;
int tHeight = 0;
try {
// Open the PNG file as an InputStream
InputStream in = new FileInputStream(filename);
// Link the PNG decoder to this stream
PNGDecoder decoder = new PNGDecoder(in);
// Get the width and height of the texture
tWidth = decoder.getWidth();
tHeight = decoder.getHeight();
// Decode the PNG file in a ByteBuffer
buf = ByteBuffer.allocateDirect(
4 * decoder.getWidth() * decoder.getHeight());
decoder.decode(buf, decoder.getWidth() * 4, Format.RGBA);
buf.flip();
in.close();
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
// Create a new texture object in memory and bind it
int texId = GL11.glGenTextures();
GL13.glActiveTexture(textureUnit);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
// All RGB bytes are aligned to each other and each component is 1 byte
GL11.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
// Upload the texture data and generate mip maps (for scaling)
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGB, tWidth, tHeight, 0,
GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, buf);
GL30.glGenerateMipmap(GL11.GL_TEXTURE_2D);
// Setup the ST coordinate system
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_REPEAT);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_REPEAT);
// Setup what to do when the texture has to be scaled
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER,
GL11.GL_NEAREST);
GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER,
GL11.GL_LINEAR_MIPMAP_LINEAR);
this.exitOnGLError("loadPNGTexture");
return texId;
}
private void exitOnGLError(String errorMessage) {
int errorValue = GL11.glGetError();
if (errorValue != GL11.GL_NO_ERROR) {
String errorString = GLU.gluErrorString(errorValue);
System.err.println("ERROR - " + errorMessage + ": " + errorString);
if (Display.isCreated()) Display.destroy();
System.exit(-1);
}
}
}

