顶点属性、顶点数组和缓冲区对象

转载请以链接形式标明出处:
本文出自:103style的博客

OpenGL ES 3.0学习汇总

  • OpenGL ES 3.0 学习记录汇总

指定顶点属性数据

所有OpenGL ES 3.0实现必须支持最少16个顶点属性。
以下代码实现了如何查询OpenGL ES 3.0实现真正支持的顶点属性数量。

GLint maxVertexAttribs;
glGetInterv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);

常量顶点属性

常量顶点属性对于一个图元的所有顶点都相同,所以对一个图元的所有顶点只需指定一个值。
可以用以下函数指定:

void glVertexAttrib1f(GLint index, GLfloat x);
void glVertexAttrib2f(GLint index, GLfloat x, GLfloat y);
void glVertexAttrib3f(GLint index, GLfloat x, GLfloat y,  GLfloat z);
void glVertexAttrib4f(GLint index, GLfloat x, GLfloat y,  GLfloat z, GLfloat w);

void glVertexAttrib1fv(GLint index, const GLfloat *values);
void glVertexAttrib2fv(GLint index, const GLfloat *values);
void glVertexAttrib3fv(GLint index, const GLfloat *values);
void glVertexAttrib4fv(GLint index, const GLfloat *values);

glVertexAttrib1fglVertexAttrib1fv 在通用顶点属性中加载(x, 0.0, 0.0, 1.0);
glVertexAttrib2fglVertexAttrib2fv 在通用顶点属性中加载(x, y, 0.0, 1.0);
glVertexAttrib4fglVertexAttrib3fv 在通用顶点属性中加载(x, y, z, 1.0);
glVertexAttrib5fglVertexAttrib4fv 在通用顶点属性中加载(x, y, z, w);

顶点数组

顶点数组指定每个顶点的属性 ,是保存在 应用程序 地址空间 (OpenGL ES 称为客户空间) 的缓冲区。
glVertexAttribPointerglVertexAttribIPointer 函数指定。

  • 一个缓冲区 中存储所有顶点属性—— 结构数组
  • 单独的缓冲区 中保存 每个顶点 的属性—— 数组结构

性能上, 结构数组 的分配方法 在OpenGL ES 3.0的硬件实现中更高效。
原因是,每个顶点的属性数据可以顺序方式读取,这最有可能造成高效的内存访问方式。
缺点 是在需要修改特定属性时,将 造成顶点缓冲区跨距更新。当顶点缓冲区以缓冲区对象提供时,需要 重新加载整个顶点属性缓冲区。可以通过 将动态的顶点属性保存在单独的缓冲区 来避免这种效率低下的情况。

结构数组

 #define VERTEX_POS_SIZE 3
 #define VERTEX_NORMAL_SIZE 3
 #define VERTEX_TEXCOORD0_SIZE 2
 #define VERTEX_TEXCOORD1_SIZE 2

 #define VERTEX_POS_INDX 0
 #define VERTEX_NORMAL_INDX 1
 #define VERTEX_TEXCOORD0_INDX 2
 #define VERTEX_TEXCOORD1_INDX 3

 #define VERTEX_POS_OFFSET 0
 #define VERTEX_NORMAL_OFFSET 3
 #define VERTEX_TEXCOORD0_OFFSET 6
 #define VERTEX_TEXCOORD1_OFFSET 8

 #define VERTEX_ATTRIB_SIZE (VERTEX_POS_SIZE + VERTEX_NORMAL_SIZE +VERTEX_TEXCOORD0_SIZE +VERTEX_TEXCOORD1_SIZE)

 float *p = (float*)malloc(numVertics * VERTEX_ATTRIB_SIZE * sizeof(float));

 //顶点属性0:坐标
 glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     p);

 //顶点属性1 :法线
 glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     (p + VERTEX_NORMAL_OFFSET));

 //顶点属性2 : 纹理坐标1
 glVertexAttribPointer(VERTEX_TEXCOORD0_INDX, VERTEX_TEXCOORD0_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     (p + VERTEX_TEXCOORD0_OFFSET));

 //顶点属性3 : 纹理坐标2
 glVertexAttribPointer(VERTEX_TEXCOORD1_INDX, VERTEX_TEXCOORD1_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     (p + VERTEX_TEXCOORD1_OFFSET));

数组结构

float *position = (float*)malloc(numVertices * VERTEX_POS_SIZE * sizeof(float));

float *normal = (float*)malloc(numVertices * VERTEX_NORMAL_SIZE * sizeof(float));

float *texcoord0 = (float*)malloc(numVertices * VERTEX_TEXCOORD0_SIZE * sizeof(float));

float *texcoord1 = (float*)malloc(numVertices * VERTEX_TEXCOORD1_SIZE * sizeof(float));

//顶点属性0:坐标
glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     position);

//顶点属性1 :法线
glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     normal);

//顶点属性2 : 纹理坐标1
glVertexAttribPointer(VERTEX_TEXCOORD0_INDX, VERTEX_TEXCOORD0_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     texcoord0);


//顶点属性3 : 纹理坐标2
glVertexAttribPointer(VERTEX_TEXCOORD1_INDX, VERTEX_TEXCOORD1_SIZE,
     GL_FLOAT, GL_FALSE,
     VERTEX_ATTRIB_SIZE * sizeof(float),
     texcoord1);

顶点属性使用的数据格式

数据格式 空间占用越小,需要的内存带宽就越小
OpenGL ES 3.0 建议使用 GL_HALF_FLOAT 的16位浮点顶点格式。
纹理、坐标、法线、副法线、切向量 等都应该使用 GL_HALF_FLOAT 存储每个分量的候选。
颜色 可以存储为 GL_UNSIGNED_BYTE,每个顶点颜色具有 4 个分量。
顶点位置 建议用 GL_HALF_FLOAT ,在不可行时,存储为 GL_FLOAT

顶点属性在内部保存为 单精度浮点数, 如果数据类型不是,将被转换为 单精度浮点数

GLfloat f;
Glbyte b;
b = (GLfloat) b;

数据转换:

顶点数据格式 转换为浮点数
GL_BYTE max(c / (27 - 1),-1.0 )
GL_UNSIGNED_BYTE c / (28 - 1)
GL_SHORT max(c / (216 - 1),-1.0 )
GL_UNSIGNED_SHORT c / (216 - 1)
GL_FIXED c / 216
GL_FLOAT c
GL_HALF_FLOAT_OES c

在顶点着色器中, 也可能按照 整数 的形式访问整数型顶点数据属性,而不转换为浮点数,这种情况使用 glVertexAttribIPointer 函数。

在常量顶点属性和顶点数组之间选择

glEnableVertexAttribArrayglDisableVertexAttribArray 分别用于启用和禁用通用顶点属性数组。

glEnableVertexAttribArray(GLint index)
glDisableVertexAttribArray(GLint index)

index : 指定通用顶点属性索引, 范围[0, size - 1]

以下示例是 使用常量和顶点数组属性 绘制三角形

int Init(ESContext *esContext)
{
  UserData *userData = (UserData*) esContext → userData;
  const vShaderStr[] = "#version 300 es \n"
      "layout(location = 0) in vec4 a_color; \n"
      "layout(location = 1) in vec4 a_postion; \n"
      "out vec4 v_color; \n"
      "void main() \n"
      "{ \n"
      "    v_color = a_color;  \n"
      "    gl_Position =  a_postion; \n"
      "}";
  const fShaderStr = "#version 300 es \n"
      "precision mediump float; \n"
      "in vec4 c_color; \n"
      "out vec4 o_fragColor; \n"
      "void main() \n"
      "{ \n"
      "    o_fragColor = v_color; \n"
      "}";
  
    //创建着色器程序
    GLint programObject;
    programObject = esLoadProgram(vShaderStr, fShaderStr);

    if(program == 0)
    {
        return GL_FALSE;
    }    
    //保存着色器程序
    userData->programObject = programObject;

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

void Draw(ESContect *esContext)
{
     UserData *userData =(UserData*) esContext->userData;
     //顶点属性常量 颜色 
     GLfloat color[4] = {1.0f, 0.0f, 0.0f, 1.0f};
    
     //三个顶点坐标 
     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);
    
     glVertexAttrib4fv(0, color);

     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, vertexPos);

     glEnableVertexAttribArray(1);
     
     glDrawArrays(GL_TRIANGLES. 0 , 3);

     glDisableVertexAttribArray(1);
}

在顶点着色器中声明顶点属性变量

顶点属性、顶点数组和缓冲区对象_第1张图片
为绘制一个或者多个图元指定和绑定顶带你属性.png

通过 in 限定符声明顶点属性,属性也包含一个 布局限定符
示例:

layout(location = 0) in vec4 a_postion;
layout(location = 1) in vec2 a_texcoord;
layout(location = 2) in vec3 a_normal;

in 限定符只能用于 非bool值类型标量、浮点向量、整型向量、无符号整形向量、矩阵
顶点属性变量 不能 声明为 数组 或者 结构
顶点着色器 中 顶点属性的变量是 只读变量,不能修改

以下代码编译会报错:

in vec4 a_pos;
uniform vec4  u_v;
void main()
{
    a_pos = u_v; // can not assign to a_pos as it is read-only.
}

属性可以在顶点着色器内部声明, 如果 没有使用 就不会 被认为是活动属性
如果 活动属性 数量大于 GL_MAX_VERTEX_ATTRIBS, 这个顶点着色器就无法链接。

以下代码展示了如何获得 活动属性 的数量.

glGetProgramiv(program, GL_ACTIVE_ATTRIBUTES, &numActiveAttributes);

查看活动属性列表和类型可以用 glGetActiveAttrib 命令查询。

将顶点属性绑定到顶点着色器中的属性变量

顶点属性、顶点数组和缓冲区对象_第2张图片
为绘制一个或者多个图元指定和绑定顶带你属性

通用顶点属性映射到顶点着色器有以下三种方法:

  • 索引可以在顶点着色器源代码中用 layout(location = N)限定符指定。 (推荐)
  • OpenGL ES 3.0将通过顶点属性索引绑定到属性名称。
  • 应用程序可以将顶点属性索引绑定到属性名称.

glBindAttribLocation 命令可用于将通用顶点属性索引绑定到顶点着色器的一个属性变量,在下一次程序链接时生效。
这个调用可以在 顶点着色器链接到程序对象之前 调用,可以绑定任何属性名称。

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

index : 通用顶点属性索引
name : 属性变量名称

另一个选项是让 OpenGL ES 3.0 将属性变量名称绑定到一个通用顶点属性索引。在程序链接时进行。
应用程序可以通过 glGetAttribLocation 命令查询分配的绑定。

void glGetAttribLocation(GLuint program, const GLchar *name)

name : 属性变量名称

顶点缓冲区对象 (VBO)

OpenGL ES 3.0 支持两类缓冲区对象,

  • 数组缓冲区对象 GL_ARRAY_BUFFER
  • 元素数组缓冲区对象 GL_ELEMENT_ARRAY_BUFFER

以下是创建和绑定顶点缓冲区对象的示例

void initVertexBuffterObjects(vertex_t  *vertexBuffer, GLushort *indices, GLuint numVertices,
                              GLuint numIndices, GLuint *vboIds )
{
  //获取vboIds中两个未使用的缓冲区对象名称
  glGenBuffers(2, vboIds);
  
  //数组缓冲区  用于保存一个或多个顶点属性的索引
  glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
  //指定数组数据
  glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(vertex_t), vertexBuffer, GL_STATIC_DRAW);

  //元素缓冲区  用于保存一个或多个图元的索引
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId[1]);
  //指定元素数据数据
  glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices * sizeof(GLushort), indices, GL_STATIC_DRAW);
}

上面的代码 创建了两个缓冲区对象: 一个用于保存实际的顶点属性数据, 另一个用以保存组成图元的元素索引。

void glGenBuffer(GLsizei n, GLuint *bufferrs)
n : 返回缓冲区对象名称数量
buffers :指向n个条目的数组指针

void glBufferData(GLenum target, GLuint buffer)
target : GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER....
buffer : 分配给目标作为当前对象的缓冲区对象

glGenBuffer 分配了 n 个 缓冲区对象名称(0 以外的无符号整数),并在buffers中返回它们。
glBindBuffer 用于指定当前缓冲区对象。第一次绑定缓冲区对象名称时,以默认状态保存,如果分配成功,则分配的对象为目标的当前缓冲区对象。

注意:在 glBindBuffer之前,并不需要 glGenBuffers。 应用程序可以用 glBindBuffer 指定一个未使用的缓冲区对象。不过建议OpenGL ES应用程序调用 glGenBuffers,并使用其返回的缓冲区对象名称,而不是指定它们自己的缓冲区对象名称。

顶点数组数据 或者 元素数组数据 存储 用 glBufferData 命令创建和初始化。

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

target : GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER....
size : 缓冲区数据存储大小,以字节数表示
data : 应用程序提供的缓冲区数据的指针, 可为 NULL
usage : 应用程序将如何使用缓冲区对象中存储的数据的提示

缓冲区对象数据存储内容可以用 glBufferSubData 命令初始化或者更新。

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

target : GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER....
offset : 缓冲区数据存储的偏移
size : 被修改的数据存储字节数
data : 需要被复制到缓冲区独享数据存储的客户数据指针

在用 glBufferData 或者 glBufferSubData 初始化或者更新 缓冲区对象数据存储 之后,客户数据存储不再需要的,
静态的几何图形 可以释放 客户数据存储,减少应用程序消耗的内存动态几何图形则无法做到

使用和不使用顶点缓冲区对象进行绘制的示例(例 6 - 5):

#include "esUtil.h"

#define VERTEX_POS_SIZE  3
#define VERTEX_COLOR_SIZE  4

#define VERTEX_POS_INDX  0
#define VERTEX_COLOR_INDX  1

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 = (UserData*)esContext->userData;
    GLuint offset = 0;

    //vboIds[0] : 保存顶点属性数据 
    //vboIds[1] : 保存元素索引
    if (userData->vboIds[0] == 0 && userData->vboIds[1] == 0)
    {
        //只在第一次绘制时分配
        glGenBuffers(2, userData->boIds);

        glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
        glBufferData(GL_ARRAY_BUFFER, vtxStride * numIndices, 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 = (UserData*)esContext->userData;

    //三个顶点  
    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
    };
    //索引缓冲数据
    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);

    glUniform1f(userData->offsetLoc, 1.0f);

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

每个属性使用一个缓冲区对象绘制(例 6 - 6):

#include "esUtil.h"

#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

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

    //vboId[0] : 用于存储 顶点位置
    //vboId[1] : 用于存储 顶点颜色
    //vboId[2] : 用于存储 元素索引
    if (userData->vboIds[0] == 0 && userData->vboIds[1] == 0 && userData->vboIds[2] == 0)
    {
        //只在第一次绘制时分配
        glGenBuffers(3, userData->vboIds);

        glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
        glBufferData(GL_ARRAY_BUFFER, vtxStrides[0] * numIndices, vtxBuf[0], GL_STATIC_DRAW);

        glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[1]);
        glBufferData(GL_ARRAY_BUFFER, vtxStrides[1] * numIndices, vtxBuf[1], GL_STATIC_DRAW);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[2]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * numIndices, indices, GL_STATIC_DRAW);
    }

    glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[0]);
    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, GL_FLOAT, GL_FALSE, vtxStrides[0], 0);

    glBindBuffer(GL_ARRAY_BUFFER, userData->vboIds[1]);
    glEnableVertexAttribArray(VERTEX_COLOR_INDX);
    glVertexAttribPointer(VERTEX_COLOR_INDX, VERTEX_COLOR_SIZE, GL_FLOAT, GL_FALSE, vtxStrides[1], 0);


    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, userData->vboIds[2]);

    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);
}

在应用程序结束使用缓冲区对象之后,可以用 glDeleteBuffers 命令删除它们。

void  glDeleteBuffers(GLsizei n, const GLuint *buffers)

n : 删除的缓冲区对象数量
buffers : 包含要删除的缓冲区对象的有n个元素的数组

顶点数组对象(VAO)

要创建顶点数组对象,可以使用 glGenVertexArrays

void glGenVertexArrays(GLsizei n, GLuint *arrays)

n : 要返回的顶点数组对象名称的数量
arrays : 指向一个n个元素的数组的指针

void glBindVertexArray(GLuint array)

array : 被指定得为当前顶点数组对象的对象

用顶点数组绘图

#include "esUtil.h"

#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 = (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_positon; \n"
        "}";

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

    GLuint programObject;
    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
    };

    GLushort indices[3] = { 0, 1, 2 };

    //创建着色器程序
    programObject = esLoadProgram(vShaderStr, fShaderStr);

    if (programObject == 0) {
        return GL_FALSE;
    }

    //保存着色器程序
    userData->programObject = programObject;

    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);

    //创建顶点数组对象
    glGenVertexArrays(1, &userData->vaoId);
    
    //绑定顶点数组对象 并 设置·顶点属性
    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 * VERTEX_COLOR_SIZE));

    //重置顶点数组对象
    glBindVertexArray(0);

    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

    return GL_TRUE;
}


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

    glViewport(0, 0, esContext->width, esContext->height);

    glClearColor(GL_COLOR_BUFFER_BIT);

    glUseProgram(userData->programObject);

    //绑定顶点数组对象
    glBindVertexArray(userData->vaoId);

    //根据顶点数组对象属性绘制
    glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_SHORT, (const void*)0);

    //返回默认的顶点数组对象
    glBindVertexArray(0);

}

在应用程序结束使用缓冲区对象之后,可以用 glDeleteArrays 命令删除它们。

void glDeteletArrays(GLsizei n, GLuint *arrays)

n : 要删除的顶点数组对象的数量
arrays :  包含需要删除的顶点数组的有n个元素的数组

映射缓冲区对象

应用程序映射缓冲区 不使用 glBufferData 或者 glBufferSubData 加载的理由:

  • 映射缓冲区可以减少应用程序的内存占用,因为可能只需要存储数据的一个副本。
  • 在使用共享内存的架构上,映射缓冲区返回GPU存储的地址空间的直接指针。

通过映射缓冲区,应用程序可以避免复制步骤,从而实现更好的性能。

glMapBufferRange 命令返回指向所有或者一部分缓冲区对象数据存储的指针。
如果出现错误,则返回NULLglUnmapBuffer 命令可以取消之前的缓冲区映射,取消成功返回 GL_TRUE,如果缓冲区映射之后已经破坏,则返回 GL_FLASE

void glMapBufferRange(Glenum target, GLintptr offset, GLsizeiptr length, GLbitField access)

target : GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER....
offset : 缓冲区数据存储的偏移量,以字节计算
length : 需要映射的缓冲区数据字节数
access : 访问标记的位域组合。

以下为 写入映射缓冲区对象

#include"esUtil.h"

GLfloat *vtxMappedBuf;
GLushort *idxMappedBuf;

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;
}
//复制数据到映射缓冲区
memcpy(vtxMappedBuf, vtxBuf, vtxStride * numVertics);

//取消数组缓冲区对象映射
if (glUnmapBuffer(GL_ARRAY_BUFFER) == GL_FALSE)
{
    esLogMessage("Error unmapping array buffer object.");
    return;
}

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 buffer object.");
    return;
}

//复制数据到映射缓冲区
memcpy(idxMappedBuf, indices, sizeof(GLushort) * numIdices);
//取消元素数组缓冲区对象映射
if (glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER) == GL_FALSE)
{
    esLogMessage("Error unmapping  element buffer object");
    return;
}

刷新映射的缓冲区

应用程序可能希望用 glMapBufferRange 来映射缓冲区对象的一个范围,但是只更新部分子区域,
可以用 GL_MAP_FLUSH_EXPLICIT_BITGL_MAP_WRITE_BIT 组合映射。
当完成部分更新时,可以用 glFlushMappedBufferRange 指出这个事实。
如果没有明确的调用 glFlushMappedBufferRange 刷新修改后的区域,它的内容将是未定义的。

void *glFlushMappedBufferRange(GLenum target,Glintptr offset,GLsizeiptr length)

target : GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER, GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER....
offset : 从映射缓冲区七十点的偏移量,以字节数表示
length: 从偏移点开始刷新的缓冲区字节数

复制缓冲区对象

到此,我们已知道 glBufferDataglBufferSubDataglMapBufferRange 加载缓冲区对象。
OpenGL ES 3.0 还可以用 glCopyBufferSubData 从一个缓冲区对象将数据完全复制到设备。

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

readtarget : 读取的缓冲区对象目标
writetarget :写入的缓冲区对象目标
readoffset : 需要复制的读缓冲区数据中的偏移量,以字节表示
writeoffset : 需要渎职的写缓冲区数据中的偏移量,以字节表示
size : 从读缓冲区数据都知道写缓冲区数据的字节数

调用 glCopyBufferSubData 将从绑定的 readtarget 的缓冲区复制指定的字节到 writetarget,缓冲区绑定更具每个目标的最后一次 glBindBuffer 调用确定。

小结

本文介绍了在OpenGL ES 3.0中指定顶点属性和数据的方法:

  • 如何使用 glVertexAttrib* 函数指定常量顶点属性和用 glVertexAttrib[I]Pointer 函数指定顶点数组。
  • 如何在顶点缓冲区对象中创建和存储顶点属性以及元素数据。
  • 顶点数组状态在顶点数组对象中如何封装,以及如何使用 VAO(顶点数组对象)改进性能。
  • 加载缓冲区对象数据的各种方法:glBuffer[Sub]DataglMapBufferRangeglCopyBufferSubData.

你可能感兴趣的:(顶点属性、顶点数组和缓冲区对象)