原文:http://wiki.lwjgl.org/wiki/The_Quad_textured
Introduction 介紹
本教程將講怎樣對模型貼紋理,其他方形的部分就和之前教程中講的差不多。後面可以看到完整的源代碼。(廢話特多,全略)
Resources 資源
本教程將用第三方類庫讀取圖片,圖片讀取器使用TWL的PNG解碼器工具,你可以在這裏獲取:[1]。它就是一個JAR文件,把它加入工程庫裏。
材質我們將使用一套UV網格,什麼是UV稍後會討論,我們將使用兩個紋理,都是PNG圖片,圖片來源是:[2],你可以從這裏獲取圖片並用你的圖片編輯器把它們轉成PNG格式。
Texture Units 紋理單元
首先要知道OpenGL使用的是一個紋理單元列表,就跟VBO的屬性列表一樣,也有一套紋理單元供使用。紋理單元的總數可能是不一定是多少,但是最小值是2。我們可以調用GL_TEXTUREX獲取這些紋理單元,X就是紋理的單元序號。系統一定會支持的紋理單元是GL_TEXTURE0和GL_TEXTURE1。
由於一個模型可能會用到許多不同的紋理(漫射、高光、法線、烤陰影……),我們必須告訴OpenGL哪個紋理放在哪裏。本教程將只使用GL_TEXTURE0,因爲我們的shader只處理漫反射紋理,漫反射紋理就是物體的基本顏色。目前爲止,我們已經分配顏色到頂點還利用交叉數據在不同頂點間創建有色方形,通過使用紋理,我們可以映射一個圖片到方形上,還能很輕鬆地操控每一個象素的顏色。
(Texture) Coordinates (紋理)座標
通常我們渲染的東西是3D的,所以頂點座標有三個組件(X,Y,Z),儘管方形沒有深度,頂點Z值也得設一個0。紋理座標只需要2個組件(S,T),3D建模者應該知道這些座標的名字“UV”,以下是圖例:
XYZ組件跟ST組件沒什麼關係,因爲這個轉換過程一般叫做展開,美工可以針對每個頂點定義其ST位置,完全不需要考慮XYZ。圖例裏的數字就是我們在範例裏用的數據,我們已經更新了自定義頂點類Vertex,這樣就可以分配ST值給它了。像下面這樣定義頂點:
// 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 使用屬性列表
仍然使用交叉數據保存,帶上額外的ST數據。
// 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 紋理shader
在OpenGL 3.x以上版本里,必須依賴shader去完成所有的工作。因此必須更新shader以使它能讀到ST數據,映射紋理到模型的工作將在片段shader裏完成。因爲到時需要每個象素去定義,所以是片段shader的工作。
Vertex Shader 頂點shader
但是一切都從頂點shader開始,所以還是要增加一個in變量,然後將它傳遞到片段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 片段shader
現在片段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 = texture(texture_diffuse, pass_TextureCoord);
}
注意片段shader從紋理取樣,紋理也可以被uniform變量texture_diffuse訪問(注意到它是sampler2D類型,稍後會再講),它從材質取樣因爲我們的屏幕象素通常和材質象素並非是一象素一象素對應的。如果有縮放,我們可能會需要更多或更少的象素。假如我們想取3個象素,但是實際上只有2個,採樣器將會根據周圍的象素創建一箇中間象素出來。
Binding the shader variables 綁定shader變量
我們現在都有三種數據在每個頂點上,XYZ座標、顏色、T座標。我們必須綁定頂點shader的紋理座標變量到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");
記着要用同樣的變量名。
Loading textures 讀取紋理
所有東西就緒,只需讀取紋理了。導入圖片,用第三方庫TWL PNG解碼器,具體怎樣做不重要,重要的是OpenGL需要的是你象素色組件一樣的字節緩衝區,用PNG解碼器就可以獲取到:
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);
}
代碼運行後,我們得到一個緩衝區,裏面填滿RGBA格式的象素數據,我們需要告訴OpenGL緩衝區裏填的是哪種類型的數據,感謝PNG解碼器幫我們做了大部分的工作。RGBA是非常通用的格式,另一個通用格式是BRG(A),此格式是DirectX用的。
下一步我們將在內存裏創建我們的紋理對象,textureUnit變量就是教程一開始說的紋理單元,我們當然還必須綁定紋理對象使變化生效,第一個變化就是把字節緩衝區推送至內存。
// 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);
記的我們的shader變量是sampler2D類型,2D是指紋理的類型(也有3D紋理這種東西)。當上傳數據到緩衝區時,我們必須用合適的紋理類型:
// 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);
關於不同各類的wrap,這裏有一個不錯的講解:[3]
接下來,把紋理上傳到OpenGL(即內存中)。本例中,我們用兩個紋理,用鍵盤在二者之間切換,用整數數組保存ID:
texIds[0] = this.loadPNGTexture("assets/stGrid1.png", GL13.GL_TEXTURE0);
texIds[1] = this.loadPNGTexture("assets/stGrid2.png", GL13.GL_TEXTURE0);
注意,我們把它們都上傳到0單元裏了,如果我們有許多采樣器變量在shader裏的話,我們可以通過使用不同的紋理單元將紋理分配到不同的變量中去。我們通過按鍵盤上的兩個鍵來選擇使用不同的紋理。
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;
}
}
當然,還得綁定紋理到渲染循環中。
GL20.glUseProgram(pId);
// Bind the texture
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texIds[textureSelector]);
程序關閉時,要釋放。
// Delete the texture
GL11.glDeleteTextures(texIds[0]);
GL11.glDeleteTextures(texIds[1]);
The result 結果
這就是程序的樣子,可以按1和2鍵在兩個紋理中切換。
Complete source code 完整源代碼
Vertex shader 頂點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 片段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 = texture(texture_diffuse, pass_TextureCoord);
}
TexturedVertex class
public 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);
// 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.glLinkProgram(pId);
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);
}
}
}