OpenGL ES 高級進階:VBO和IBO

你們好,這是個人OpenGL ES 高級進階系列文章,在個人github上有一個與本系列文章對應的項目,歡迎關注,連接:github.com/kenneycode/…java

今天給你們介紹VBO(Vertex Buffer Object)IBO(Index Buffer Object),讓咱們先從一段代碼開始,逐步介紹它們:git

// 將三角形頂點數據放入buffer中
// Put the triangle vertex data into the vertexDataBuffer
vertexDataBuffer = ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE / 8)
    .order(ByteOrder.nativeOrder())
    .asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)

// 啓動對應位置的參數,這裏直接使用LOCATION_ATTRIBUTE_POSITION,而無需像OpenGL 2.0那樣須要先獲取參數的location
// Enable the parameter of the location. Here we can simply use LOCATION_ATTRIBUTE_POSITION, while in OpenGL 2.0 we have to query the location of the parameter
GLES30.glEnableVertexAttribArray(LOCATION_ATTRIBUTE_POSITION)

// 指定a_position所使用的頂點數據
// Specify the data of a_position
GLES30.glVertexAttribPointer(LOCATION_ATTRIBUTE_POSITION, VERTEX_COMPONENT_COUNT, GLES30.GL_FLOAT, false, 0, vertexDataBuffer)
複製代碼

這段代碼你們應該很熟悉了,它的做用就是將頂點數據指定給vertex shader中的attribute變量,這裏有個細節以前沒提到過,就是glVertexAttribPointer()這個方法指定了頂點數據,但它不會將頂點數據一直存儲在顯存中,而咱們渲染的時候,全部要訪問的數據必需在顯存中,所以,每次渲染時,OpenGL會有一個將這些頂點數據從內存複製到顯存的操做,這樣會帶來一些問題:github

  • 一個是所以每次渲染都要複製一次,所以內存中的頂點數據要一直留着,否則複製的時候就沒有數據來複制了。ide

  • 另外一個是若是頂點數據量大的時候,每次渲染都作這樣的一次複製,性能上會有問題,咱們的例子中,頂點算是很是少的,那何時頂點會多呢?例如作一些形變效果時,每每會劃分網格,通常來講劃分得越多,效果越細膩,這時頂點就會不少,另外,在作3D渲染時,一般頂點也不少,單個3D模型甚至能夠有上萬個頂點。post

那有什麼辦法來避免複製?這時就要用到VBO,它能夠和頂點數據綁定,綁定後的頂點數據是一直存儲在顯存中的,當須要用這些頂點數據的時候,直接綁定這個VBO就好了,不會有複製過程。性能

IBO又是什麼呢?它和VBO做用很相似,VBO是爲了不頂點數據的複製,IBO是則是爲了不頂點索引數據的複製,什麼是頂點索引呢?咱們先來看一份頂點數據:優化

// 三角形頂點數據
// The vertex data of a triangle
private val vertexData = floatArrayOf(
                            -1f, -1f,   // 左下角
                            -1f, 1f,    // 左上角
                            1f, 1f,     // 右上角
                            -1f, -1f,   // 左下角
                            1f, 1f,     // 右上角
                            1f, -1f     // 右下角
                        )
複製代碼

這是一份很普通的頂點數據,在咱們的教程中屢次用到過,它用2個三角形組成了一個矩形,對應用GL_TRIANGLES的繪製模式進行渲染(繪製模式可參考個人一篇文章《Android OpenGL ES 2.0 手把手教學(5)- 繪製模式》),咱們能夠很容易地看到,這6個頂點是有重複的,一個矩形只須要4個頂點就好了,有些點是不一樣三角形之間共用的,那麼如何讓不一樣三角形之間共用?這就要用到頂點索引,它能讓咱們用索引的方法告訴OpenGL咱們的頂點,而不是每一個點都用座標的方式給出,這樣能夠減小咱們的頂點數據量,這在面片數量較大時頗有用。ui

那麼下面咱們來看看具體如何使用VBOIBO,先來看看頂點數據和頂點索引數據:spa

// 三角形頂點、紋理數據
// The vertex data and texture coordinate data of triangles
private val vertexData = floatArrayOf(
                            -1f, -1f,   0f, 1f,     // x, y, u, v
                            -1f, 1f,    0f, 0f,
                            1f, 1f,     1f, 0f,
                            1f, -1f,    1f, 1f
                        )
複製代碼

這裏咱們將頂點和紋理座標組合越來,這也是配合VBOIBO的常規優化用法,它的好處讓頂點和紋理座標在存儲上靠近,利於OpenGL取數據,提升性能,特別是在3D渲染時,數據通常都是這樣組織的。code

接下來用glGenBuffers建立VBOIBO:

// 建立VBO和IBO
// Create VBO and IBO
val buffers = IntArray(2)
GLES30.glGenBuffers(buffers.size, buffers, 0)
vbo = buffers[0]
ibo = buffers[1]
複製代碼

咱們能夠看到,在建立的時候其實並無VBOIBO的區別,都是叫buffer,它們是在真正用的時候才能體現出區別。

下面將頂點的紋理數據加載到VBO中:

// 將頂點數據載入VBO
// Load vertex data into VBO
val vertexDataBuffer = ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE / 8)
                                    .order(ByteOrder.nativeOrder())
                                    .asFloatBuffer()
vertexDataBuffer.put(vertexData)
vertexDataBuffer.position(0)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo)
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexDataBuffer.capacity() * java.lang.Float.SIZE / 8, vertexDataBuffer, GLES30.GL_STATIC_DRAW)
GLES30.glEnableVertexAttribArray(LOCATION_ATTRIBUTE_POSITION)
GLES30.glEnableVertexAttribArray(LOCATION_ATTRIBUTE_TEXTURE_COORDINATE)
GLES30.glVertexAttribPointer(LOCATION_ATTRIBUTE_POSITION, 2, GLES30.GL_FLOAT, false, 16, 0)
GLES30.glVertexAttribPointer(LOCATION_ATTRIBUTE_TEXTURE_COORDINATE, 2, GLES30.GL_FLOAT, false, 16, 8)

複製代碼

主要的關鍵點是先用glBindBuffer綁定咱們在操做的buffer,這一點和操做textureframe buffer時很像,操做前都先綁定。接着用glBufferData給它喂數據,這裏最後一個參數用於提示OpenGL以便於它作一些優化,例如這裏咱們傳了GL_STATIC_DRAW,即咱們的數據是不會變的,還有一些其它的能夠設置,如GL_DYNAMIC_DRAW則提示OpenGL這個buffer的數據是會變的。

glVertexAttribPointer指定頂點和紋理數據時,咱們再也不像以前那樣,直接把數據的傳進來,由於這時咱們的數據已經在VBO中了,這裏不須要再傳,這裏重點關注最後兩個參數,倒數第二個參數是指定stride,即OpenGL去取一份數據時的跨度,這裏由於咱們把頂點和紋理數據組合在了一塊兒,所以一份數據是2個頂點和2個紋理座標,即4個float,16個字節。倒數第一個參數是指定這個數據在一份數據中的開始位置,由於咱們在一份數據中是先放頂點再放紋理座標,所以對於頂點,開始位置是0,對於紋理座標,開始位置是第8個字節。

這樣咱們就設置好了VBOIBO,在渲染的時候,直接綁定VBOIBO就可使用對應的頂點和紋理數據了:

override fun onDrawFrame(gl: GL10?) {

    // 設置清屏顏色
    // Set the color which the screen will be cleared to
    GLES30.glClearColor(0.9f, 0.9f, 0.9f, 1f)

    // 清屏
    // Clear the screen
    GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)

    // 設置視口,這裏設置爲整個GLSurfaceView區域
    // Set the viewport to the full GLSurfaceView
    GLES30.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)

    // 設置好狀態,準備渲染
    // Set the status before rendering
    GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo)
    GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ibo)
    GLES30.glActiveTexture(GLES30.GL_TEXTURE0)
    GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, imageTexture)

    // 調用draw方法用TRIANGLES的方式執行渲染,頂點數量爲3個
    // Call the draw method with GL_TRIANGLES to render 3 vertices
    GLES30.glDrawElements(GLES30.GL_TRIANGLES, indexData.size, GLES30.GL_UNSIGNED_INT, 0)

}
複製代碼

來看看效果,就是渲染出一張圖來,從效果上看不出什麼區別哈:

代碼在我githubOpenGLESPro項目中,本文對應的是SampleVBOAndIBO,項目連接:github.com/kenneycode/…

感謝閱讀!

相關文章
相關標籤/搜索