原文:http://wiki.lwjgl.org/wiki/The_Quad_with_DrawArrays
Introduction 介紹
隨着3.0發佈,“棄用”機制被引入。所有被標記爲棄用的函數,在未來的版本會被移除。因此開發者應避免使用它們並把已經用到的儘量重構出去。許多輔助類被移除,比如matrix/stack操作和默認光照。但是更重要的是,定義了基元,無需再用glVertex了,取而代之使用頂點數組對象VAO(Vertex Array Object)和頂點緩衝對象VBO(Vertex Buffer Object)。可以用它們來在屏幕上畫方形。
“Vertex Array Object” and “Vertex Buffer Object”
一種渲染提速算法是把數據放在GPU裏而不是每次都從CPU往GPU發,這就是VAO和VBO做的事情,你將可以使用一定量的顯存。
把VAO想象成一個完整的對象定義,它由VBO組成。每個VBO可以保存特殊的定義的數據(混合或交插保存都是可行的),VBO可以用來保存:頂點位置、顏色、法線、紋理座標……。VAO保存各種各樣的VBO在它的屬性列表裏,默認將在一個VAO裏有從0到15共16個屬性可用。
設置VAO或者其他對象,都要按下面的規則:
- 請求一個內存空間(返回一個整數,作爲對象的ID)
- 用ID綁定對象
- 操作對象(此對象是指OpenGL目前綁定上的對象)
- 用ID解綁對象
如果是VAO,用代碼寫出來就是:
int vaoId = GL30.glGenVertexArrays();
GL30.glBindVertexArray(vaoId);
// Do something with it
GL30.glBindVertexArray(0);
如果是VBO,用代碼寫出來就是:
int vboId = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vboId);
// Do something with it
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
Setting up the quad VBO and VAO 用VBO和VAO設置方形
在把VBO放進VAO的屬性列表之前,應該先調用glVertextAttributePointer綁定VAO,此舉需要列表ID(默認是從0到15)。在本例中,我們要用VAO和VBO設置一個方形。VBO含有頂點座標數據,VAO將作爲主物體(即方形)定義而活動。
首先需聲明頂點,OpenGL以三角形的方式畫所有的東西。三角形頂點默認是按逆時針方向描畫的。這意味着,既然你要按逆時針順序指定頂點,需要先定義哪邊是前哪邊是後。這很有用,OpenGL可以由此在渲染管線中以最優方式剔除那些看不到的面。你可以改掉這種默認的行爲,但是那通常是自找麻煩,所以這裏我們不再深入討論。
回到頂點定義,一個方形不是一個三角形而是兩個三角形,因此定義方形實際需要的是6個頂點而不是4個(這種低效的方式下次我們再解決):
我們默認的OpenGL座標系所有軸都是從-1到1,這與我們整個屏幕是相對應的,像這樣:
我們會用三個數字定義頂點,每座標軸一個數字(X, Y, Z)。實際上OpenGL用的是4個數字,(X, Y, Z, W)。先不管W,把它設成1。數字是浮點型,在將它們用在OpenGL方法前,需要在bytebuffer(或者FloatBuffer)裏交換它們。
// OpenGL expects vertices to be defined counter clockwise by default
float[] vertices = {
// Left bottom triangle
-0.5f, 0.5f, 0f,
-0.5f, -0.5f, 0f,
0.5f, -0.5f, 0f,
// Right top triangle
0.5f, -0.5f, 0f,
0.5f, 0.5f, 0f,
-0.5f, 0.5f, 0f
};
// Sending data to OpenGL requires the usage of (flipped) byte buffers
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
verticesBuffer.flip();
vertexCount = 6;
需要記錄頂點數目,因此我們需要讓OpenGL稍候用glDrawArrays畫圖時知道有多少個頂點需要畫。
現在我們的頂點是可用格式,可以開始定義VAO和VBO了。在VBO裏放位置數據,在VAO裏把VBO放在屬性列表的0位置上。連接VBO和VAO的屬性列表很容易(當不需要使用交叉數據的時候)。只需要用glVertextAttribPointer即可,它的參數如下:
- 屬性列表序號(在本例中,就是0)
- 一個數據定義裏含了幾個值(在本例中,3個浮點數算是一個位置數據定義)
- 值的數據類型(在本例中,是浮點數)
- 步進和偏移量(暫時還不需要用,在交叉數據裏才用)
用glBufferData將數據放入VBO中,這方法的參數如下:
- 類型(我們用的是GL_ARRAY_BUFFER, 默認就是這個)
- 緩衝區(我們的那個帶有頂點數據的FloatBuffer)
- 用途(我們的頂點不動也不變,所以就用簡單的GL_STATIC_DRAW)
別忘了解綁我們的對象,因爲不需要再操作它們了。綜合以上所有步驟,代碼如下:
// 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);
vaoID和vboId(還有vertexCount)是全局定義的整數值。
Rendering with glDrawArrays 用glDrawArrays渲染
爲了實際畫出方形,前面提過,用glDrawArrays方法,它的參數:
- 怎樣畫頂點(我們用簡單的GL_TRIANGLES)
- 第一個序號(我們從頭開始,就是0)
- 頂點數目(這值我們存在vertextCount變量裏)
OpenGL還必須有VAO(連着VBO的那一個)激活在內存中,因此必須在畫前綁定好它們。VBO已經和VAO屬性列表0號位連好了,因此只需要啓用此列表。當畫完後,我們要解綁並禁用一切。渲染代碼如下:
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
// Bind to the VAO that has all the information about the quad vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);
// Draw the vertices
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, vertexCount);
// Put everything back to default (deselect)
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);
Cleaning up our memory 清除內存
最後,退出程序之前,需要做內存管理:
// Disable the VBO index from the VAO attributes list
GL20.glDisableVertexAttribArray(0);
// Delete the VBO
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboId);
// Delete the VAO
GL30.glBindVertexArray(0);
GL30.glDeleteVertexArrays(vaoId);
The result 結果
最後畫出我們的方形:
Complete source code 完整代碼
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;
import org.lwjgl.util.glu.GLU;
public class TheQuadExampleDrawArrays {
// Entry point for the application
public static void main(String[] args) {
new TheQuadExampleDrawArrays();
}
// Setup variables
private final String WINDOW_TITLE = "The Quad: glDrawArrays";
private final int WIDTH = 320;
private final int HEIGHT = 240;
// Quad variables
private int vaoId = 0;
private int vboId = 0;
private int vertexCount = 0;
public TheQuadExampleDrawArrays() {
// 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)
.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("Error in setupOpenGL");
}
public void setupQuad() {
// OpenGL expects vertices to be defined counter clockwise by default
float[] vertices = {
// Left bottom triangle
-0.5f, 0.5f, 0f,
-0.5f, -0.5f, 0f,
0.5f, -0.5f, 0f,
// Right top triangle
0.5f, -0.5f, 0f,
0.5f, 0.5f, 0f,
-0.5f, 0.5f, 0f
};
// Sending data to OpenGL requires the usage of (flipped) byte buffers
FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(vertices.length);
verticesBuffer.put(vertices);
verticesBuffer.flip();
vertexCount = 6;
// 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);
this.exitOnGLError("Error in setupQuad");
}
public void loopCycle() {
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT);
// Bind to the VAO that has all the information about the quad vertices
GL30.glBindVertexArray(vaoId);
GL20.glEnableVertexAttribArray(0);
// Draw the vertices
GL11.glDrawArrays(GL11.GL_TRIANGLES, 0, vertexCount);
// Put everything back to default (deselect)
GL20.glDisableVertexAttribArray(0);
GL30.glBindVertexArray(0);
this.exitOnGLError("Error in loopCycle");
}
public void destroyOpenGL() {
// Disable the VBO index from the VAO attributes list
GL20.glDisableVertexAttribArray(0);
// Delete the VBO
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL15.glDeleteBuffers(vboId);
// Delete the VAO
GL30.glBindVertexArray(0);
GL30.glDeleteVertexArrays(vaoId);
Display.destroy();
}
public 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);
}
}
}