原文: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();
}
}