原文:http://wiki.lwjgl.org/wiki/The_Quad_with_DrawElements
Introduction 介紹
之前教程裏用glDrawArrays畫方形,缺點是你必須指定所有三角形的每一個頂點。一個方形其實只有四個頂點,但是分成兩個三角形就變成了六個!用glDrawElements的話,就可以在定義頂點時把重複的點去掉了。取而代之的是,我們必須通過指定序號告訴OpenGL每個三角形用的是哪三個頂點。接下來教程開始。
Do you remember the following… 知識回顧
之前的DrawArrays教程將作爲本教程的基礎,也就是讀本篇前你得先了解什麼是AVO和VBO並且瞭解怎樣設置它們。你還需要知道VAO有許多屬性列表(默認是16個),我們可以將VBO連在上面。VBO裏含有數據,還需要綁定和解綁對象(比如VBO就是一個對象,頂點緩衝對象)。
Defining our vertices 定義頂點
定義僅含有方形四頂點的頂點數組。順序無所謂,因爲之後要具體在另一個對象裏指定順序。
// Vertices, the order is not important.
float[] vertices = {
-0.5f, 0.5f, 0f, // Left top ID: 0
-0.5f, -0.5f, 0f, // Left bottom ID: 1
0.5f, -0.5f, 0f, // Right bottom ID: 2
0.5f, 0.5f, 0f // Right left ID: 3
};
// Sending data to OpenGL requires the usage of (flipped) byte buffers
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
verticesBuffer.flip();
如你所見,只定義了四個頂點,就是方形的四個頂點。數組數目由0開始到3結束,每個頂點有三個值,代表位置。現在在我們的腦海裏可以給它們定一個序號(見代碼註釋),如果直接將四點給OpenGL,它將不知所措。所以我們還需要給OpenGL一個指引,讓它用四點畫兩個三角形,因此我們還需要建另一個VBO。
Adding indices 增加序號
方形依舊是由兩個三角形組成的,在新的VBO裏,我們用序號來定義之前頂點的順序。如下:
// OpenGL expects to draw vertices in counter clockwise order by default
byte[] indices = {
// Left bottom triangle
0, 1, 2,
// Right top triangle
2, 3, 0
};
indicesCount = indices.length;
ByteBuffer indicesBuffer = BufferUtils.createByteBuffer(indicesCount);
indicesBuffer.put(indices);
indicesBuffer.flip();
如你所見,我們用序號來定義了兩個三角形。關鍵在於,我們雖然定義了四個頂點,但是其中兩個頂點用了兩次。這樣內存佔用減小了,從6降到了4,但是又增加了一個序號VBO,看起來好像得不償失,但是實際遊戲的模型可不僅僅是一個方形,頂點數量巨大,就值了。
後建的這個VBO類型略有不同,並非是一個普通的數組緩衝(GL_ARRAY_BUFFER),而是元素數組(GL_ELEMENT_ARRAY_BUFFER)。爲了它,還需要再申請內存ID,綁定,填充數據到緩衝(別忘了最後解綁)。
// Create a new VBO for the indices and select it (bind)
vboiId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
// Deselect (bind to 0) the VBO
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
vboiId是一個全局整數變量。還要注意我們保存了序號的數目(就和之前glDrawArrays教程裏保存頂點數目一樣)
Rendering with “glDrawElements” 用glDrawElements渲染
用glDrawElements渲染跟之前的glDrawArrays差不多,渲染前需要綁定序號VBO。
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
// Bind to the VAO that has all the information about the vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);
// 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);
GL30.glBindVertexArray(0);
Cleaning up our memory 清除內存
做內存管理的時候別忘了也刪除序號VBO。
// Delete the index VBO
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboiId);
The rest of our cleaning code looks exactly the same, here’s the complete cleanup code:
其他的清除代碼也一樣,以下是完整的清除代碼:
// Disable the VBO index from the VAO attributes list
GL20.glDisableVertexAttribArray(0);
// 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);
Display.destroy();
The result 結果
以下是畫出的方形(和之前一樣):
Complete source code 完整代碼
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import org.lwjgl.BufferUtils;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.PixelFormat;
public class TheQuadExampleDrawElements {
// Entry point for the application
public static void main(String[] args) {
new TheQuadExampleDrawElements();
}
// Setup variables
private final String WINDOW_TITLE = "The Quad: glDrawElements";
private final int WIDTH = 320;
private final int HEIGHT = 240;
// Quad variables
private int vaoId = 0;
private int vboId = 0;
private int vboiId = 0;
private int indicesCount = 0;
public TheQuadExampleDrawElements() {
// Initialize OpenGL (Display)
this.setupOpenGL();
this.setupQuad();
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();
}
public void setupOpenGL() {
// Setup an OpenGL context with API version 3.2
try {
PixelFormat pixelFormat = new PixelFormat();
ContextAttribs contextAtrributes = new ContextAttribs(3, 2)
.withProfileCore(true)
.withForwardCompatible(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);
}
public void setupQuad() {
// Vertices, the order is not important.
float[] vertices = {
-0.5f, 0.5f, 0f, // Left top ID: 0
-0.5f, -0.5f, 0f, // Left bottom ID: 1
0.5f, -0.5f, 0f, // Right bottom ID: 2
0.5f, 0.5f, 0f // Right left ID: 3
};
// Sending data to OpenGL requires the usage of (flipped) byte buffers
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
verticesBuffer.flip();
// OpenGL expects to draw vertices in counter clockwise order by default
byte[] indices = {
// Left bottom triangle
0, 1, 2,
// Right top triangle
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)
// A VAO can have up to 16 attributes (VBO's) assigned to it by default
vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);
// Create a new Vertex Buffer Object in memory and select it (bind)
// A VBO is a collection of Vectors which in this case resemble the location of each vertex.
vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, verticesBuffer, GL15.GL_STATIC_DRAW);
// Put the VBO in the attributes list at index 0
GL20.glVertexAttribPointer(0, 3, GL11.GL_FLOAT, false, 0, 0);
// Deselect (bind to 0) the VBO
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)
vboiId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, vboiId);
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL15.GL_STATIC_DRAW);
// Deselect (bind to 0) the VBO
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
}
public void loopCycle() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
// Bind to the VAO that has all the information about the vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);
// 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);
GL30.glBindVertexArray(0);
}
public void destroyOpenGL() {
// Disable the VBO index from the VAO attributes list
GL20.glDisableVertexAttribArray(0);
// 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);
Display.destroy();
}
}