1. 程式人生 > >【OpenGL ES】頂點緩衝區物件VBO與頂點陣列物件VAO

【OpenGL ES】頂點緩衝區物件VBO與頂點陣列物件VAO

頂點屬性即頂點資料,可以通過頂點陣列逐頂點指定,也可以為所有頂點指定一個常量,所有OpenGL ES 3.0實現必須支援至少16個頂點屬性,應用程式可以通過glGetIntegerv查詢GL_MAX_VERTEX_ATTRIBS得到支援的最大定點屬性個數。

1、常量頂點屬性

常量頂點屬性使用glVertexAttrib系列函式指定,有如下幾個函式:

void glVertexAttrib1f(GLuint index,
    GLfloat v0);

void glVertexAttrib2f(GLuint index,
    GLfloat v0,
    GLfloat v1);

void
glVertexAttrib3f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2); void glVertexAttrib4f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3); void glVertexAttribI4i(GLuint index, GLint v0, GLint v1, GLint v2, GLint v3); void glVertexAttribI4ui(GLuint index
, GLuint v0, GLuint v1, GLuint v2, GLuint v3); void glVertexAttrib1fv(GLuint index, const GLfloat *v); void glVertexAttrib2fv(GLuint index, const GLfloat *v); void glVertexAttrib3fv(GLuint index, const GLfloat *v); void glVertexAttrib4fv(GLuint index, const GLfloat *v); void
glVertexAttribI4iv(GLuint index, const GLint *v); void glVertexAttribI4uiv(GLuint index, const GLuint *v);

glVertexAttrib中,index為定點屬性索引,頂點屬性各分量的預設值為(0, 0, 0, 1),數字1、2、3、4表示設定從第一個開始的幾個分量,f、i、ui表示型別float、int、unsigned int,v表示頂點屬性分量儲存在向量(陣列)中,大寫I表示型別擴充套件為完整的int或unsigned int。

2、頂點屬性陣列

頂點(屬性)陣列使用glVertexAttribPointer和glVertexAttribIPointer指定,如下:

void glVertexAttribPointer(GLuint index,
    GLint size,
    GLenum type,
    GLboolean normalized,
    GLsizei stride,
    const GLvoid * pointer);

void glVertexAttribIPointer(GLuint index,
    GLint size,
    GLenum type,
    GLsizei stride,
    const GLvoid * pointer);

glVertexAttribPointer和glVertexAttribIPointer中,index為頂點屬性索引,從0到最大頂點屬性數減1,size為頂點屬性分量數量,從1到4,初始值為4也就是有4個屬性分量,type為資料格式,兩者都包括GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT、GL_UNSIGNED_INT ,前者還包括GL_HALF_FLOAT、GL_FLOAT、GL_FIXED、GL_INT_2_10_10_10_REV、GL_UNSIGNED_INT_2_10_10_10_REV,初始值為GL_FLOAT,normalized表示非浮點資料型別格式在轉化為浮點值時是否應該規範化,int從-1到1,unsigned int從0到1,type為GL_FIXED時忽略normalized,stride為頂點屬性分量之間的跨距,為0時表示順序儲存,初始值為0,pointer為儲存頂點屬性資料的緩衝區(頂點陣列)的指標或頂點緩衝區物件(VBO)內的偏移量,初始值為0,這個pointer引數的用法比較特殊,下面例子有所展示。

glVertexAttribPointer與glVertexAttribIPointer的唯一區別就是後者沒有normalized,在用於頂點著色器之前,頂點屬性在內部儲存為單精度浮點數,如果資料型別表示頂點屬性不是浮點數,頂點屬性將在用於頂點著色器之前轉換為單精度浮點數,normalized控制非浮點頂點屬性屬性到單精度浮點值的轉換,為GL_FALSE時直接轉換為浮點值,為GL_TRUE時才進行規範化,根據type型別規範化(對映)為從-1到1或者從0到1,而glVertexAttribIPointer則是用整數儲存頂點屬性資料。

分配和儲存頂點屬性資料有兩種常用的方法,結構陣列和陣列結構,前者在一個緩衝區中儲存頂點屬性,後者在單獨的緩衝區中儲存每個頂點屬性,簡單的說就是前者為一個大的緩衝區,後者為幾個小的緩衝區,兩者的緩衝區總大小相等。那麼,對於OpenGL ES 3.0硬體實現,哪種分配方法最高效?在大部分情況下是結構陣列,因為每個頂點的屬性資料可以順序讀取,對於記憶體訪問來說很可能是高效的,但也有其缺點,在修改特定屬性或者頂點屬性的一個子集時造成頂點緩衝區的跨距更新,將變得效率低下,當頂點緩衝區以緩衝區物件的形式提供時,需要重新載入整個頂點屬性緩衝區,可以通過將動態的頂點屬性儲存在單獨的緩衝區來避免這種效率低下的情況。有時出於效能考慮,頂點屬性儘可能使用低精度或低範圍的資料型別,如OpenGL ES 3.0提供的GL_HALF_FLOAT,為16位浮點格式,是紋理座標、法線、副法線、切向量等儲存每個分量的候選,顏色可以儲存為GL_UNSIGNED_BYTE,每個頂點顏色具有4個顏色分量,頂點位置則儲存為GL_FLOAT,這個type引數指定的頂點屬性資料格式不僅影響頂點屬性資料的圖形記憶體儲存需求,而且影響整體效能,資料空間佔用越小,需要的記憶體頻寬越小。

最後,應用程式可以讓OpenGL ES使用常量資料或者來自頂點陣列的資料,glEnableVertexAttribArray和glDisableVertexAttribArray函式分別用於啟用和禁用通用頂點屬性陣列,如果某個通用屬性索引的頂點屬性陣列被禁用,將使用為該索引指定的常量頂點熟悉資料,頂點熟悉陣列預設是不可用的。

void glEnableVertexAttribArray(GLuint index);
void glDisableVertexAttribArray(GLuint index);

3、頂點屬性宣告

在頂點著色器中,變數通過in宣告為頂點屬性(輸入變數),屬性變數也可以選擇包含一個佈局限定符,提供屬性索引。in限定符只能用於內建的資料型別,所以,屬性變數不能宣告為陣列或者結構。頂點屬性為四分量向量,與編譯器自動打包的統一變數及頂點著色器輸出與片段著色器輸入(插值器)變數不同,屬性不進行打包。需要注意GL_MAX_VERTEX_ATTRIBS,是個有限資源。在頂點著色器中宣告為頂點屬性的變數其實是隻讀變數,在著色器中不能修改,其值通過客戶端API即應用程式通過glVertexAttrib等函式指定,在著色器中使用了屬性變數時才被認為是活動變數。

在OpenGL ES 3.0中,可以使用三種方法將通用頂點屬性索引對映到頂點著色器中的一個屬性變數名稱,一是在頂點著色器原始碼中用佈局限定符layout指定,推薦使用這種方式,二是OpenGL ES 3.0在程式連結階段為沒有指定索引的頂點繫結一個屬性索引,三是應用程式使用glBindAttribLocation進行繫結,相關函式如下所示。

void glGetProgramiv(GLuint program,
    GLenum pname, // GL_ACTIVE_ATTRIBUTES
    GLint *params);

void glGetActiveAttrib(GLuint program,
    GLuint index,
    GLsizei bufSize,
    GLsizei *length,
    GLint *size,
    GLenum *type,
    GLchar *name);

void glBindAttribLocation(GLuint program,
    GLuint index,
    const GLchar *name);

GLint glGetAttribLocation(GLuint program,
    const GLchar *name); 

glGetActiveAttrib查詢指定program與index的活動屬性的資訊。glBindAttribLocation將頂點屬性索引繫結到頂點著色器的一個屬性變數,在下一次程式連結時生效,不會改變當前連結的程式中使用的繫結,如果之前綁定了name,則它所指定的繫結被index代替,可以在頂點著色器連結到程式物件之前呼叫,繫結任何屬性名稱,不存在的屬性名稱或者在連結到程式物件的頂點著色器中不活動的屬性將被忽略。glGetAttribLocation查詢繫結到屬性變數name的頂點屬性索引,如果沒有繫結,則內部實現自動繫結到一個頂點屬性索引,這發生在連結階段,失敗時返回-1。

4、頂點陣列示例

下面是使用了常量頂點屬性(顏色,索引為1,所以三角形的顏色整個都為紅色)和頂點屬性陣列(位置,索引為0,所以指定了三角形的三個不同的頂點位置)的程式碼片段,結果為一個白色背景的紅色三角形。

// Example_6_3.c
int Init ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   const char vShaderStr[] =
      "#version 300 es                            \n"
      "layout(location = 0) in vec4 a_position;   \n"
      "layout(location = 1) in vec4 a_color;      \n"
      "out vec4 v_color;                          \n"
      "void main()                                \n"
      "{                                          \n"
      "    v_color = a_color;                     \n"
      "    gl_Position = a_position;              \n"
      "}";

   const char fShaderStr[] =
      "#version 300 es            \n"
      "precision mediump float;   \n"
      "in vec4 v_color;           \n"
      "out vec4 o_fragColor;      \n"
      "void main()                \n"
      "{                          \n"
      "    o_fragColor = v_color; \n"
      "}" ;

   GLuint programObject;
   // Create the program object
   programObject = esLoadProgram ( vShaderStr, fShaderStr );
   if ( programObject == 0 )
   {
      return GL_FALSE;
   }

   // Store the program object
   userData->programObject = programObject;

   glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
   return GL_TRUE;
}

void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat color[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
   // 3 vertices, with (x,y,z) per-vertex
   GLfloat vertexPos[3 * 3] =
   {
      0.0f,  0.5f, 0.0f, // v0
      -0.5f, -0.5f, 0.0f, // v1
      0.5f, -0.5f, 0.0f  // v2
   };

   glViewport ( 0, 0, esContext->width, esContext->height );
   glClear ( GL_COLOR_BUFFER_BIT );
   glUseProgram ( userData->programObject );

   glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vertexPos );
   glEnableVertexAttribArray ( 0 );
   glVertexAttrib4fv ( 1, color )

   glDrawArrays ( GL_TRIANGLES, 0, 3 );
   glDisableVertexAttribArray ( 0 );
}

這裡寫圖片描述

修改上面的程式碼,將三角形的顏色設定也用和位置設定一樣的方法,通過頂點陣列指定三個頂點為不同的顏色以繪製顏色漸變三角形。

void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   GLfloat color[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
   // 3 vertices, with (x,y,z) per-vertex
   GLfloat vertexPos[3 * 3] =
   {
      0.0f,  0.5f, 0.0f, // v0
      -0.5f, -0.5f, 0.0f, // v1
      0.5f, -0.5f, 0.0f  // v2
   };
   // 3 vertices, with (r,g,b,a) per-vertex
   GLfloat vertexColor[3 * 4] =
   {
       1.0f, 0.0f, 0.0f, 1.0f,
       0.0f, 1.0f, 0.0f, 1.0f,
       0.0f, 0.0f, 1.0f, 1.0f
   };

   glViewport ( 0, 0, esContext->width, esContext->height );
   glClear ( GL_COLOR_BUFFER_BIT );
   glUseProgram ( userData->programObject );

   glVertexAttribPointer ( 0, 3, GL_FLOAT, GL_FALSE, 0, vertexPos );
   glEnableVertexAttribArray ( 0 );
//   glVertexAttrib4fv ( 1, color );
   glVertexAttribPointer ( 1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 4, vertexColor );
   glEnableVertexAttribArray ( 1 );

   glDrawArrays ( GL_TRIANGLES, 0, 3 );

   glDisableVertexAttribArray ( 0 );
   glDisableVertexAttribArray ( 1 );
}

這裡寫圖片描述

5、頂點緩衝區物件

頂點緩衝區物件即Vertex Buffer Object,簡稱VBO。頂點陣列資料儲存在客戶記憶體中,使用glDrawArrays或glDrawElements繪圖時,這些資料必須從客戶記憶體複製到圖形記憶體,如果資料過大或者頻繁繪圖時,將帶來效能問題,於是引入了VBO,在圖形記憶體中分配和緩衝頂點資料並且進行渲染,這樣可以提高渲染效能,降低記憶體頻寬和電力消耗,不僅是頂點資料,描述圖元頂點的索引、作為glDrawElements引數傳遞的元素索引也可以快取。

OpenGL ES 3.0支援兩類緩衝區物件,陣列緩衝區物件GL_ARRAY_BUFFER用於頂點資料,元素陣列緩衝區物件GL_ELEMENT_ARRAY_BUFFER用於圖元索引,使用VBO涉及如下幾個函式。

void glGenBuffers(GLsizei n,
    GLuint * buffers);

void glBindBuffer(GLenum target,
    GLuint buffer);

void glBufferData(GLenum target,
    GLsizeiptr size,
    const GLvoid * data,
    GLenum usage);

void glBufferSubData(GLenum target,
    GLintptr offset,
    GLsizeiptr size,
    const GLvoid * data);

void glDeleteBuffers(GLsizei n,
    const GLuint * buffers);

glGenBuffers用於建立緩衝區物件,n為緩衝區物件數量,buffers儲存建立的緩衝區物件。glBindBuffer用於繫結緩衝區物件,buffer為緩衝區物件,不存在時將自動建立,之前的繫結將失效,0為保留值,將解綁所有繫結的緩衝區物件,target如下:

GL_ARRAY_BUFFER
GL_ATOMIC_COUNTER_BUFFER // from GL ES 3.1
GL_COPY_READ_BUFFER // from GL ES 3.0
GL_COPY_WRITE_BUFFER // from GL ES 3.0
GL_DISPATCH_INDIRECT_BUFFER // from GL ES 3.1
GL_DRAW_INDIRECT_BUFFER // from GL ES 3.1
GL_ELEMENT_ARRAY_BUFFER
GL_PIXEL_PACK_BUFFER // from GL ES 3.0
GL_PIXEL_UNPACK_BUFFER // from GL ES 3.0
GL_SHADER_STORAGE_BUFFER // from GL ES 3.1
GL_TEXTURE_BUFFER // from GL ES 3.2
GL_TRANSFORM_FEEDBACK_BUFFER // from GL ES 3.0
GL_UNIFORM_BUFFER // from GL ES 3.0

glBindBuffer之後,可以通過glGet檢視GL_ARRAY_BUFFER_BINDING、GL_ELEMENT_ARRAY_BUFFER_BINDING等繫結狀態。glBufferData用於建立並初始化緩衝區物件的資料,之前的資料將被清除,target同上,size為緩衝區物件的資料長度,data為緩衝區物件的資料,可以為NULL,usage如下:

GL_STREAM_DRAW // 修改一次,較少使用,應用程式修改資料,用於GL繪圖和圖片相關的命令
GL_STREAM_READ // 修改一次,較少使用,GL中讀取的資料修改這個資料,用於返回到應用程式
GL_STREAM_COPY // 修改一次,較少使用,GL中讀取的資料修改這個資料,用於GL繪圖和圖片相關的命令
GL_STATIC_DRAW // 修改一次,多次使用,應用程式修改資料,用於GL繪圖和圖片相關的命令
GL_STATIC_READ // 修改一次,多次使用,GL中讀取的資料修改這個資料,用於返回到應用程式
GL_STATIC_COPY // 修改一次,多次使用,GL中讀取的資料修改這個資料,用於GL繪圖和圖片相關的命令
GL_DYNAMIC_DRAW // 修改多次,多次使用,應用程式修改資料,用於GL繪圖和圖片相關的命令
GL_DYNAMIC_READ // 修改多次,多次使用,GL中讀取的資料修改這個資料,用於返回到應用程式
GL_DYNAMIC_COPY // 修改多次,多次使用,GL中讀取的資料修改這個資料,用於GL繪圖和圖片相關的命令

glBufferData之後,可以通過glGetBufferParameter檢視GL_BUFFER_SIZE、GL_BUFFER_USAGE等緩衝區物件相關資訊。glBufferSubData用於更新緩衝區物件的資料,offset為偏移量。glBufferData或glBufferSubData之後,客戶緩衝區中的資料已經緩衝到緩衝區物件,可以釋放。最後,glDeleteBuffers用於刪除緩衝區物件。

6、頂點緩衝區物件示例

下面的例子畫了兩個相同的三角形,頂點屬性的緩衝區都是使用了上面提到的結構陣列的形式,即位置和顏色資料都在一個數組中,左邊的使用的是同前面例子中一樣的頂點陣列,右邊的使用了兩個VBO,一個數組緩衝區和一個元素陣列緩衝區,兩個三角形的偏移量通過uniform變數進行設定,程式碼如下:

// VertexBufferObjects.c
typedef struct
{
   // Handle to a program object
   GLuint programObject;
   // VertexBufferObject Ids
   GLuint vboIds[2];
   // x-offset uniform location
   GLuint offsetLoc;
} UserData;

#define VERTEX_POS_SIZE       3 // x, y and z
#define VERTEX_COLOR_SIZE     4 // r, g, b, and a
#define VERTEX_POS_INDX       0
#define VERTEX_COLOR_INDX     1

int Init ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   const char vShaderStr[] =
      "#version 300 es                            \n"
      "layout(location = 0) in vec4 a_position;   \n"
      "layout(location = 1) in vec4 a_color;      \n"
      "uniform float u_offset;                    \n"
      "out vec4 v_color;                          \n"
      "void main()                                \n"
      "{                                          \n"
      "    v_color = a_color;                     \n"
      "    gl_Position = a_position;              \n"
      "    gl_Position.x += u_offset;             \n"
      "}";

   const char fShaderStr[] =
      "#version 300 es            \n"
      "precision mediump float;   \n"
      "in vec4 v_color;           \n"
      "out vec4 o_fragColor;      \n"
      "void main()                \n"
      "{                          \n"
      "    o_fragColor = v_color; \n"
      "}" ;

   GLuint programObject;

   // Create the program object
   programObject = esLoadProgram ( vShaderStr, fShaderStr );
   userData->offsetLoc = glGetUniformLocation ( programObject, "u_offset" );
   if ( programObject == 0 )
   {
      return GL_FALSE;
   }

   // Store the program object
   userData->programObject = programObject;
   userData->vboIds[0] = 0;
   userData->vboIds[1] = 0;

   glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
   return GL_TRUE;
}

// vertices   - pointer to a buffer that contains vertex
//              attribute data
// vtxStride  - stride of attribute data / vertex in bytes
// numIndices - number of indices that make up primitive
//              drawn as triangles
// indices    - pointer to element index buffer.
void DrawPrimitiveWithoutVBOs ( GLfloat *vertices,
                                GLint vtxStride,
                                GLint numIndices,
                                GLushort *indices )
{
   GLfloat   *vtxBuf = vertices;

   // 解除繫結
   glBindBuffer ( GL_ARRAY_BUFFER, 0 );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 );

   glEnableVertexAttribArray ( VERTEX_POS_INDX );
   glEnableVertexAttribArray ( VERTEX_COLOR_INDX );

   glVertexAttribPointer ( VERTEX_POS_INDX, VERTEX_POS_SIZE,
                           GL_FLOAT, GL_FALSE, vtxStride,
                           vtxBuf );
   vtxBuf += VERTEX_POS_SIZE;
   glVertexAttribPointer ( VERTEX_COLOR_INDX,
                           VERTEX_COLOR_SIZE, GL_FLOAT,
                           GL_FALSE, vtxStride, vtxBuf );

   glDrawElements ( GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT,
                    indices );

   glDisableVertexAttribArray ( VERTEX_POS_INDX );
   glDisableVertexAttribArray ( VERTEX_COLOR_INDX );

}

void DrawPrimitiveWithVBOs ( ESContext *esContext,
                             GLint numVertices, GLfloat *vtxBuf,
                             GLint vtxStride, GLint numIndices,
                             GLushort *indices )
{
   UserData *userData = esContext->userData;
   GLuint   offset = 0;

   // vboIds[0] - used to store vertex attribute data
   // vboIds[l] - used to store element indices
   if ( userData->vboIds[0] == 0 && userData->vboIds[1] == 0 )
   {
      // Only allocate on the first draw
      glGenBuffers ( 2, userData->vboIds );

      glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
      glBufferData ( GL_ARRAY_BUFFER, vtxStride * numVertices,
                     vtxBuf, GL_STATIC_DRAW );
      glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );
      glBufferData ( GL_ELEMENT_ARRAY_BUFFER,
                     sizeof ( GLushort ) * numIndices,
                     indices, GL_STATIC_DRAW );
   }

   glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );

   glEnableVertexAttribArray ( VERTEX_POS_INDX );
   glEnableVertexAttribArray ( VERTEX_COLOR_INDX );

   glVertexAttribPointer ( VERTEX_POS_INDX, VERTEX_POS_SIZE,
                           GL_FLOAT, GL_FALSE, vtxStride,
                           ( const void * ) offset );
   offset += VERTEX_POS_SIZE * sizeof ( GLfloat );
   glVertexAttribPointer ( VERTEX_COLOR_INDX,
                           VERTEX_COLOR_SIZE,
                           GL_FLOAT, GL_FALSE, vtxStride,
                           ( const void * ) offset );

   glDrawElements ( GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT,
                    0 );

   glDisableVertexAttribArray ( VERTEX_POS_INDX );
   glDisableVertexAttribArray ( VERTEX_COLOR_INDX );

   glBindBuffer ( GL_ARRAY_BUFFER, 0 );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 );
}

void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;

   // 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
   GLfloat vertices[3 * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE )] =
   {
      -0.5f,  0.5f, 0.0f,        // v0
      1.0f,  0.0f, 0.0f, 1.0f,  // c0
      -1.0f, -0.5f, 0.0f,        // v1
      0.0f,  1.0f, 0.0f, 1.0f,  // c1
      0.0f, -0.5f, 0.0f,        // v2
      0.0f,  0.0f, 1.0f, 1.0f,  // c2
   };
   // Index buffer data
   GLushort indices[3] = { 0, 1, 2 };

   glViewport ( 0, 0, esContext->width, esContext->height );
   glClear ( GL_COLOR_BUFFER_BIT );
   glUseProgram ( userData->programObject );
   glUniform1f ( userData->offsetLoc, 0.0f );

   DrawPrimitiveWithoutVBOs ( vertices,
                              sizeof ( GLfloat ) * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE ),
                              3, indices );

   // Offset the vertex positions so both can be seen
   glUniform1f ( userData->offsetLoc, 1.0f );

   DrawPrimitiveWithVBOs ( esContext, 3, vertices,
                           sizeof ( GLfloat ) * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE ),
                           3, indices );
}

void Shutdown ( ESContext *esContext )
{
   UserData *userData = esContext->userData;

   glDeleteProgram ( userData->programObject );
   glDeleteBuffers ( 2, userData->vboIds );
}

int esMain ( ESContext *esContext )
{
   esContext->userData = malloc ( sizeof ( UserData ) );

   esCreateWindow ( esContext, "VertexBufferObjects", 320, 240, ES_WINDOW_RGB );

   if ( !Init ( esContext ) )
   {
      return GL_FALSE;
   }

   esRegisterShutdownFunc ( esContext, Shutdown );
   esRegisterDrawFunc ( esContext, Draw );

   return GL_TRUE;
}

這裡寫圖片描述

7、頂點陣列物件

前面介紹了載入頂點屬性的兩種方法,頂點陣列和VBO,VBO效能更好,因為VBO減少了CPU與GPU間複製的資料量,不過使用VBO每次也都要呼叫glBindBuffer、glVertexAttribPointer、glEnableVertexAttribArray等,OpenGL ES 3.0引入了一個新的概念,VAO,即頂點陣列物件,可以更快地在頂點陣列配置之間切換,使頂點陣列的使用更加高效。VAO提供包含在頂點陣列和VBO配置之間切換所需要的所有狀態的單一物件,實際上,OpenGL ES 3.0中總是有一個活動的VAO,預設值為0,管理VAO涉及如下幾個函式。

void glGenVertexArrays(GLsizei n,
    GLuint *arrays);

void glBindVertexArray(GLuint array);

GLboolean glIsVertexArray(GLuint array);

void glDeleteVertexArrays(GLsizei n,
    const GLuint *arrays);

每個VAO都包含一個完整的狀態向量,描述所有頂點緩衝區繫結和啟用的頂點客戶狀態,繫結VAO時,它的狀態向量提供頂點緩衝區狀態的當前設定,用glBindVertexArray繫結VAO後,更改頂點陣列狀態的後續呼叫如glBindBuffer、glEnableVertexAttribArray等將影響新的VAO。然後,應用程式可以通過繫結一個已經設定狀態的VAO快速地在頂點陣列配置之間切換,所有變化可以在一個函式呼叫中完成,直接看下面的例子,結果同上面示例的彩色三角形。

// VertexArrayObjects.c
typedef struct
{
   // Handle to a program object
   GLuint programObject;
   // VertexBufferObject Ids
   GLuint vboIds[2];
   // VertexArrayObject Id
   GLuint vaoId;
} UserData;

#define VERTEX_POS_SIZE       3 // x, y and z
#define VERTEX_COLOR_SIZE     4 // r, g, b, and a

#define VERTEX_POS_INDX       0
#define VERTEX_COLOR_INDX     1

#define VERTEX_STRIDE         ( sizeof(GLfloat) *     \
                                ( VERTEX_POS_SIZE +    \
                                  VERTEX_COLOR_SIZE ) )

int Init ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   const char vShaderStr[] =
      "#version 300 es                            \n"
      "layout(location = 0) in vec4 a_position;   \n"
      "layout(location = 1) in vec4 a_color;      \n"
      "out vec4 v_color;                          \n"
      "void main()                                \n"
      "{                                          \n"
      "    v_color = a_color;                     \n"
      "    gl_Position = a_position;              \n"
      "}";

   const char fShaderStr[] =
      "#version 300 es            \n"
      "precision mediump float;   \n"
      "in vec4 v_color;           \n"
      "out vec4 o_fragColor;      \n"
      "void main()                \n"
      "{                          \n"
      "    o_fragColor = v_color; \n"
      "}" ;

   GLuint programObject;

   // 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
   GLfloat vertices[3 * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE )] =
   {
      0.0f,  0.5f, 0.0f,        // v0
      1.0f,  0.0f, 0.0f, 1.0f,  // c0
      -0.5f, -0.5f, 0.0f,        // v1
      0.0f,  1.0f, 0.0f, 1.0f,  // c1
      0.5f, -0.5f, 0.0f,        // v2
      0.0f,  0.0f, 1.0f, 1.0f,  // c2
   };
   // Index buffer data
   GLushort indices[3] = { 0, 1, 2 };

   // Create the program object
   programObject = esLoadProgram ( vShaderStr, fShaderStr );
   if ( programObject == 0 )
   {
      return GL_FALSE;
   }

   // Store the program object
   userData->programObject = programObject;

   // Generate VBO Ids and load the VBOs with data
   glGenBuffers ( 2, userData->vboIds );

   glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
   glBufferData ( GL_ARRAY_BUFFER, sizeof ( vertices ),
                  vertices, GL_STATIC_DRAW );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );
   glBufferData ( GL_ELEMENT_ARRAY_BUFFER, sizeof ( indices ),
                  indices, GL_STATIC_DRAW );

   // Generate VAO Id
   glGenVertexArrays ( 1, &userData->vaoId );

   // Bind the VAO and then setup the vertex
   // attributes
   glBindVertexArray ( userData->vaoId );

   glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );

   glEnableVertexAttribArray ( VERTEX_POS_INDX );
   glEnableVertexAttribArray ( VERTEX_COLOR_INDX );

   glVertexAttribPointer ( VERTEX_POS_INDX, VERTEX_POS_SIZE,
                           GL_FLOAT, GL_FALSE, VERTEX_STRIDE, ( const void * ) 0 );

   glVertexAttribPointer ( VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE,
                           GL_FLOAT, GL_FALSE, VERTEX_STRIDE,
                           ( const void * ) ( VERTEX_POS_SIZE * sizeof ( GLfloat ) ) );

   // Reset to the default VAO
   glBindVertexArray ( 0 );

   glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
   return GL_TRUE;
}

void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;

   glViewport ( 0, 0, esContext->width, esContext->height );
   glClear ( GL_COLOR_BUFFER_BIT );
   glUseProgram ( userData->programObject );

   // Bind the VAO
   glBindVertexArray ( userData->vaoId );

   // Draw with the VAO settings
   glDrawElements ( GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, ( const void * ) 0 );

   // Return to the default VAO
   glBindVertexArray ( 0 );
}

void Shutdown ( ESContext *esContext )
{
   UserData *userData = esContext->userData;

   glDeleteProgram ( userData->programObject );
   glDeleteBuffers ( 2, userData->vboIds );
   glDeleteVertexArrays ( 1, &userData->vaoId );
}

int esMain ( ESContext *esContext )
{
   esContext->userData = malloc ( sizeof ( UserData ) );

   esCreateWindow ( esContext, "VertexArrayObjects", 320, 240, ES_WINDOW_RGB );

   if ( !Init ( esContext ) )
   {
      return GL_FALSE;
   }

   esRegisterShutdownFunc ( esContext, Shutdown );
   esRegisterDrawFunc ( esContext, Draw );

   return GL_TRUE;
}

8、對映緩衝區物件

前面介紹了使用glBufferData將資料載入到緩衝區物件,在OpenGL ES 3.0中,應用程式也可以將緩衝區物件的資料對映到應用程式的地址空間,這種方式可以減少應用程式的記憶體空間,返回GPU儲存緩衝區的地址空間的直接指標,避免複製步驟,從而實現更好的更新效能,涉及如下幾個函式。

void *glMapBufferRange(GLenum target,
    GLintptr offset,
    GLsizeiptr length,
    GLbitfield access);

GLboolean glUnmapBuffer(GLenum target);

void glFlushMappedBufferRange(GLenum target,
    GLintptr offset,
    GLsizeiptr length);

glMapBufferRange返回指向緩衝區物件的指定範圍的資料的指標,供應用程式使用,以讀取或者更新緩衝區物件的內容,出錯時返回NULL。glUnmapBuffer指示更新已經完成並解除對映,成功時返回GL_TRUE,之前對映的指標不再可以使用,失敗時很有可能是資料已被破壞,如在執行時螢幕解析度變為更大的寬度、高度和畫素位數,則對映的記憶體可能不得不釋放。glUnmapBuffer會重新整理整個對映範圍,如果想只重新整理對映範圍的一個子區域,使用glFlushMappedBufferRange,此時glMapBufferRange的access可以是GL_MAP_WRITE_BIT和GL_MAP_FLUSH_EXPLICIT_BIT的組合,如果使用了GL_MAP_FLUSH_EXPLICIT_BIT,而又沒有明確地通過glFlushMappedBufferRange重新整理修改後的區域,它的內容將是未定義的。target同上面提到的glBufferData的target,如GL_ARRAY_BUFFER、GL_ELEMENT_ARRAY_BUFFER等,offset和length一起指定對映範圍,access為訪問標記,必須至少是GL_MAP_READ_BIT和GL_MAP_WRITE_BIT中的一個,還可以新增其它的標記,具體用法如下:

GL_MAP_READ_BIT // 從緩衝區物件讀取
GL_MAP_WRITE_BIT // 修改緩衝區物件
GL_MAP_INVALIDATE_RANGE_BIT // 指定範圍的緩衝區內容可能被釋
GL_MAP_INVALIDATE_BUFFER_BIT // 整個緩衝區內容可能被釋放
GL_MAP_FLUSH_EXPLICIT_BIT // 應用程式需使用glFlushMappedBufferRange重新整理對對映範圍的操作
GL_MAP_UNSYNCHRONIZED_BIT // 返回緩衝區範圍的指標之前不需要等到緩衝區物件上的未決操作

下面是對映緩衝區物件的一個例子,使用了VBO,但緩衝區物件的資料不是通過glBufferData指定,而是先使用glMapBufferRange進行對映,再使用memcpy拷貝資料,結果同上面示例中的彩色三角形。

// MapBuffer.c
typedef struct
{
   // Handle to a program object
   GLuint programObject;

   // VertexBufferObject Ids
   GLuint vboIds[2];
} UserData;

#define VERTEX_POS_SIZE       3 // x, y and z
#define VERTEX_COLOR_SIZE     4 // r, g, b, and a

#define VERTEX_POS_INDX       0
#define VERTEX_COLOR_INDX     1

int Init ( ESContext *esContext )
{
   UserData *userData = esContext->userData;
   const char vShaderStr[] =
      "#version 300 es                            \n"
      "layout(location = 0) in vec4 a_position;   \n"
      "layout(location = 1) in vec4 a_color;      \n"
      "out vec4 v_color;                          \n"
      "void main()                                \n"
      "{                                          \n"
      "    v_color = a_color;                     \n"
      "    gl_Position = a_position;              \n"
      "}";

   const char fShaderStr[] =
      "#version 300 es            \n"
      "precision mediump float;   \n"
      "in vec4 v_color;           \n"
      "out vec4 o_fragColor;      \n"
      "void main()                \n"
      "{                          \n"
      "    o_fragColor = v_color; \n"
      "}" ;

   GLuint programObject;

   // Create the program object
   programObject = esLoadProgram ( vShaderStr, fShaderStr );
   if ( programObject == 0 )
   {
      return GL_FALSE;
   }

   // Store the program object
   userData->programObject = programObject;
   userData->vboIds[0] = 0;
   userData->vboIds[1] = 0;

   glClearColor ( 1.0f, 1.0f, 1.0f, 0.0f );
   return GL_TRUE;
}

void DrawPrimitiveWithVBOsMapBuffers ( ESContext *esContext,
                                       GLint numVertices, GLfloat *vtxBuf,
                                       GLint vtxStride, GLint numIndices,
                                       GLushort *indices )
{
   UserData *userData = esContext->userData;
   GLuint   offset = 0;

   // vboIds[0] - used to store vertex attribute data
   // vboIds[l] - used to store element indices
   if ( userData->vboIds[0] == 0 && userData->vboIds[1] == 0 )
   {
      GLfloat *vtxMappedBuf;
      GLushort *idxMappedBuf;

      // Only allocate on the first draw
      glGenBuffers ( 2, userData->vboIds );

      glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
      glBufferData ( GL_ARRAY_BUFFER, vtxStride * numVertices,
                     NULL, GL_STATIC_DRAW );

      vtxMappedBuf = ( GLfloat * )
                     glMapBufferRange ( GL_ARRAY_BUFFER, 0, vtxStride * numVertices,
                                        GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT );

      if ( vtxMappedBuf == NULL )
      {
         esLogMessage ( "Error mapping vertex buffer object." );
         return;
      }

      // Copy the data into the mapped buffer
      memcpy ( vtxMappedBuf, vtxBuf, vtxStride * numVertices );

      // Unmap the buffer
      if ( glUnmapBuffer ( GL_ARRAY_BUFFER ) == GL_FALSE )
      {
         esLogMessage ( "Error unmapping array buffer object." );
         return;
      }

      // Map the index buffer
      glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );
      glBufferData ( GL_ELEMENT_ARRAY_BUFFER,
                     sizeof ( GLushort ) * numIndices,
                     NULL, GL_STATIC_DRAW );
      idxMappedBuf = ( GLushort * )
                     glMapBufferRange ( GL_ELEMENT_ARRAY_BUFFER, 0, sizeof ( GLushort ) * numIndices,
                                        GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT );

      if ( idxMappedBuf == NULL )
      {
         esLogMessage ( "Error mapping element array buffer object." );
         return;
      }

      // Copy the data into the mapped buffer
      memcpy ( idxMappedBuf, indices, sizeof ( GLushort ) * numIndices );

      // Unmap the buffer
      if ( glUnmapBuffer ( GL_ELEMENT_ARRAY_BUFFER ) == GL_FALSE )
      {
         esLogMessage ( "Error unmapping element array buffer object." );
         return;
      }
   }

   glBindBuffer ( GL_ARRAY_BUFFER, userData->vboIds[0] );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[1] );

   glEnableVertexAttribArray ( VERTEX_POS_INDX );
   glEnableVertexAttribArray ( VERTEX_COLOR_INDX );

   glVertexAttribPointer ( VERTEX_POS_INDX, VERTEX_POS_SIZE,
                           GL_FLOAT, GL_FALSE, vtxStride,
                           ( const void * ) offset );

   offset += VERTEX_POS_SIZE * sizeof ( GLfloat );
   glVertexAttribPointer ( VERTEX_COLOR_INDX,
                           VERTEX_COLOR_SIZE,
                           GL_FLOAT, GL_FALSE, vtxStride,
                           ( const void * ) offset );

   glDrawElements ( GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT,
                    0 );

   glDisableVertexAttribArray ( VERTEX_POS_INDX );
   glDisableVertexAttribArray ( VERTEX_COLOR_INDX );

   glBindBuffer ( GL_ARRAY_BUFFER, 0 );
   glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 );
}

void Draw ( ESContext *esContext )
{
   UserData *userData = esContext->userData;

   // 3 vertices, with (x,y,z) ,(r, g, b, a) per-vertex
   GLfloat vertices[3 * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE )] =
   {
      0.0f,  0.5f, 0.0f,        // v0
      1.0f,  0.0f, 0.0f, 1.0f,  // c0
      -0.5f, -0.5f, 0.0f,        // v1
      0.0f,  1.0f, 0.0f, 1.0f,  // c1
      0.5f, -0.5f, 0.0f,        // v2
      0.0f,  0.0f, 1.0f, 1.0f,  // c2
   };
   // Index buffer data
   GLushort indices[3] = { 0, 1, 2 };

   glViewport ( 0, 0, esContext->width, esContext->height );
   glClear ( GL_COLOR_BUFFER_BIT );
   glUseProgram ( userData->programObject );

   DrawPrimitiveWithVBOsMapBuffers ( esContext, 3, vertices,
                                     sizeof ( GLfloat ) * ( VERTEX_POS_SIZE + VERTEX_COLOR_SIZE ),
                                     3, indices );
}

void Shutdown ( ESContext *esContext )
{
   UserData *userData = esContext->userData;

   glDeleteProgram ( userData->programObject );
   glDeleteBuffers ( 2, userData->vboIds );
}

int esMain ( ESContext *esContext )
{
   esContext->userData = malloc ( sizeof ( UserData ) );

   esCreateWindow ( esContext, "MapBuffers", 320, 240, ES_WINDOW_RGB );

   if ( !Init ( esContext ) )
   {
      return GL_FALSE;
   }

   esRegisterShutdownFunc ( esContext, Shutdown );
   esRegisterDrawFunc ( esContext, Draw );

   return GL_TRUE;
}

9、複製緩衝區物件

最後介紹一個直接複製緩衝區物件的技術,是OpenGL ES 3.0提供的,直接從一個緩衝取物件的內容複製到另一個緩衝區物件,函式如下:

void glCopyBufferSubData(GLenum readtarget,
    GLenum writetarget,
    GLintptr readoffset,
    GLintptr writeoffset,
    GLsizeiptr size);