第六章 顶点属性、顶点数组和缓冲对象

  • 什么是顶点属性?
  • 如何指定属性和数据
  • 它可以支持什么格式
  • 怎样将顶点属性索引绑定到顶点着色器中
  • 怎样使用当前顶点属性

顶点数据又称为顶点属性,可以为每一个顶点指定顶点属性,绘制纯色图形,一般的坐标会不同,需要存储三个顶点,我们一般使用数组进行存储。

指定顶点属性

opengles2.0只支持顶点属性,属性数据可以根据顶点数组为每一个顶点指定,也可以用于图元所有顶点的常数值。
所有opengles实现必须至少八个顶点属性,应用程序可以查询实现支持顶点属性的确切值。

GLint maxVertexAttribs;   // n will be >= 8
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);

c++的传统,传统方法,传递地址,让代码赋值。

恒定顶点属性

常数顶点属性对于图元所有顶点都是相同的,所有只需要给图元一个值。
这个是设置单个属性的:

//顶点属性值设置  单个设置
void glVertexAttrib1f(GLuint index, GLfloat x);
void glVertexAttrib2f(GLuint index, GLfloat x, GLfloat y);
void glVertexAttrib3f(GLuint index, GLfloat x, GLfloat y, GLfloat z);
void glVertexAttrib4f(GLuint index, GLfloat x, GLfloat y, GLfloat z,GLfloat w);

举例:

glVertexAttrib1f(1,1.0F);

设置常量 数组的方式

一般常用的都是使用这个。

// 设置常量值
void glVertexAttrib1fv(GLuint index, const GLfloat *values);
void glVertexAttrib2fv(GLuint index, const GLfloat *values);
void glVertexAttrib3fv(GLuint index, const GLfloat *values);
void glVertexAttrib4fv(GLuint index, const GLfloat *values);

example:
int []arr = new int[1];
arr [0] = 1.0F;
glVertexAttrib1f(2,arr);

高版本会将常量顶点指定为字节,无符号数、int,float,double的函数,在opengles2.0中只支持float.原因是它们使用的情况比较少。

顶点数组

顶点数组指定每个顶点属性的数据,存储应用程序地址的空间缓存区,它们为指定顶点属性数据提供了一种高效灵活的方法。顶点数组是使用glVertexAttribPointer函数指定的。

void glVertexAttribuPointer(GLint index,GLint size,GLenum type,GLboolean normalized,GLsizei stride,const void *ptr)

参数解释一下:

  • 位置
  • 大小
  • 类型
  • 是否归一化
  • 跳过几个
  • 数组

数组这里可以分为结构数组和数组结构。

  • 将所有的属性都存储在一起,数组结构,将属性分开存储,结构数组。
Example 6-1 describes how these four vertex attributes are specified with 
glVertexAttribPointer.
Example 6-1 Array of Structures
#define VERTEX_POS_SIZE           3   // x, y and z
#define VERTEX_NORMAL_SIZE        3   // x, y and z
#define VERTEX_TEXCOORD0_SIZE     2   // s and t
#define VERTEX_TEXCOORD1_SIZE     2   // s and t
#define VERTEX_POS_INDX           0
#define VERTEX_NORMAL_INDX        1
#define VERTEX_TEXCOORD0_INDX     2
#define VERTEX_TEXCOORD1_INDX     3
// the following 4 defines are used to determine location of various
// attributes if vertex data is are stored as an array of structures
#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  = malloc(numVertices * VERTEX_ATTRIB_SIZE 
                   * sizeof(float));
// position is vertex attribute 0
//重点看一下跳跃的那个参数
glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, 
                      GL_FLOAT, GL_FALSE, 
                      VERTEX_ATTRIB_SIZE * sizeof(float),
                      p);
// normal is vertex attribute 1
glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE, 
                      GL_FLOAT, GL_FALSE, 
                      VERTEX_ATTRIB_SIZE * sizeof(float), 
                      (p +  VERTEX_NORMAL_OFFSET)); //这个参数
// texture coordinate 0 is vertex attribute 2
glVertexAttribPointer(VERTEX_TEXCOORD0_INDX, VERTEX_TEXCOORD0_SIZE,
                      GL_FLOAT, GL_FALSE, 
                      VERTEX_ATTRIB_SIZE * sizeof(float), 
                      (p +  VERTEX_TEXCOORD0_OFFSET));//这个参数
// texture coordinate 1 is vertex attribute 3
glVertexAttribPointer(VERTEX_TEXCOORD1_INDX, VERTEX_TEXCOORD1_SIZE, 
                      GL_FLOAT, GL_FALSE, 
                      VERTEX_ATTRIB_SIZE * sizeof(float), 
                      (p + VERTEX_TEXCOORD1_OFFSET));//这个参数

单独存储的时候

Example 6-2 Structure of Arrays
float *position  = malloc(numVertices * VERTEX_POS_SIZE * 
                          sizeof(float));
float *normal    = malloc(numVertices * VERTEX_NORMAL_SIZE * 
                          sizeof(float));
float *texcoord0 = malloc(numVertices * VERTEX_TEXCOORD0_SIZE * 
                          sizeof(float));
float *texcoord1 = malloc(numVertices * VERTEX_TEXCOORD1_SIZE * 
                          sizeof(float));
// position is vertex attribute 0
glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, 
                      GL_FLOAT, GL_FALSE,
                      VERTEX_POS_SIZE * sizeof(float), position);
// normal is vertex attribute 1
glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE, 
                      GL_FLOAT, GL_FALSE, 
                      VERTEX_NORMAL_SIZE * sizeof(float), normal);
// texture coordinate 0 is vertex attribute 2
glVertexAttribPointer(VERTEX_TEXCOORD0_INDX, VERTEX_TEXCOORD0_SIZE,
                      GL_FLOAT, GL_FALSE, VERTEX_TEXCOORD0_SIZE * 
                      sizeof(float), texcoord0);
// texture coordinate 1 is vertex attribute 3
glVertexAttribPointer(VERTEX_TEXCOORD1_INDX, VERTEX_TEXCOORD1_SIZE, 
                      GL_FLOAT, GL_FALSE, 
                      VERTEX_TEXCOORD1_SIZE * sizeof(float),
                      texcoord1);

在数组结构和结构数组之间选择

命令glEnableVertexAttribArray和glDisableVertexAttribArray用于启用和禁用通用顶点属性数组。如果对一般属性索引禁用顶点属性数组,将使用为该索引指定的恒定顶点属性数据。

Example 6-3 describes how to draw a triangle where one of the vertex 
attributes is constant and the other is specified using a vertex array. 
Example 6-3 Using Constant and Vertex Array Attributes
GLbyte vertexShaderSrc[] =  
      "attribute vec4 a_position;    \n"
      "attribute vec4 a_color;       \n"
      "varying vec4   v_color;       \n"
      "void main()                   \n"
      "{                             \n"
      "    v_color = a_color;        \n"
      "    gl_Position = a_position; \n"
      "}";
GLbyte fragmentShaderSrc[] =
      "varying vec4 v_color;         \n"
      "void main()                   \n"
    "{                             \n"
      "    gl_FragColor = v_color;   \n"
      "}";
GLfloat   color[4] = { 1.0f, 0.0f, 0.0f, 1.0f };
GLfloat   vertexPos[3 * 3];  // 3 vertices, with (x,y,z) per-vertex
GLuint    shaderObject[2];
GLuint    programObject;
shaderObject[0] = LoadShader(vertexShaderSrc, GL_VERTEX_SHADER);
shaderObject[1] = LoadShader(fragmentShaderSrc, GL_FRAGMENT_SHADER);
programObject = glCreateProgram();
glAttachShader(programObject, shaderObject[0]);
glAttachShader(programObject, shaderObject[1]);
//------------------------------------
glVertexAttrib4fv(0, color);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, vertexPos);

/////////////////////////////////////////////////
glEnableVertexAttribArray(1);
/////////////////////////////////////////////////////////////
//-------------------------------------
glBindAttribLocation(programObject, 0, "a_color");
glBindAttribLocation(programObject, 1, "a_position");
glLinkProgram(programObject);
glUseProgram(programObject);
glDrawArrays(GL_TRIANGLES, 0, 3);

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

我们会了查询位置,会将数据传递给着色器,可以怎么样定义一个属性值呢??

在顶点着色器中,使用属性限定符将变量声明为顶点属性。属性限定符只能在顶点着色器中使用。如果在片段着色器中使用了属性限定符,则在编译片段着色器时会导致错误。

attribute vec4   a_position;
attribute vec2   a_texcoord;
attribute vec3   a_normal;

属性限定符只能用于数据类型float、vec2、vec3、vec4、mat2、mat3和mat4。属性变量不能声明为数组或结构。
OpenGL ES 2.0实现支持GL _ MAX _ VENTURE _ ATTRIBS vec 4顶点属性。声明为float或vec2或vec3的顶点属性将被视为一个vec4属性。声明为mat2、mat3或mat4的顶点属性将分别计为两个、三个或四个vec4属性。不同于由编译器自动打包的统一变量和可变变量,属性不会被打包。每个组件由实现内部存储为32位单精度浮点值。当声明尺寸小于vec4的顶点属性时,请仔细考虑,因为可用顶点属性的最大数量是有限的资源。最好将它们打包成一个vec4属性,而不是在顶点着色器中将它们声明为单独的顶点属性。
还有最重要一个就是 只读。

note:刚才有个查属性数量的,

一个属性可以在顶点着色器中声明,但是如果它没有被使用,那么它就不被认为是活动的,并且不计算在内。如果顶点着色器中使用的属性数量大于GL_MAX_VERTEX_ATTRIBS,顶点着色器将无法链接。

查询活跃的数量

glGetProgramiv(progam, GL_ACTIVE_ATTRIBUTES, &numActiveAttribs);

使用之后才可以认为是活跃的,如果没有被使用就不能认为是活跃的。

活跃的属性的参数看可以使用

程序使用的活动顶点属性列表及其数据类型可以使用glGetActiveAttrib命令进行查询。
void GlGetActiveAttribb(Gluint program,GLuint index,GLsizei bufsize,GLsizei *length,GLint * size,GLenum *type,GLchar *name)
  • 以前成功链接的程序对象的名称索引指定
  • 指定要查询的顶点属性,其值介于0…GL _ ACTIVE _ ATTRIBUTES–1之间。GL_ACTIVE_ATTRIBUTES的值由glGetProgramiv确定
  • 指定可以写入名称的最大字符数,包括空终止符
  • 如果长度不为空,则返回写入名称的字符数,不包括空终止符
  • 返回属性的类型。有效值包括:
    GL_FLOAT
    GL_FLOAT_VEC2
    GL_FLOAT_VEC3
    GL_FLOAT_VEC4
    GL_FLOAT_MAT2
    GL_FLOAT_MAT3
    GL_FLOAT_MAT4
    
  • 返回属性的大小。这是以类型返回的类型为单位指定的。如果变量不是数组,大小将始终为1。如果变量是一个数组,那么size返回数组的大小
  • 顶点着色器中声明的属性变量的名称

glGetActiveAttrib调用提供了有关索引所选属性的信息。如上所述,索引必须是介于0和GL _ ACTIVE _ ATTRIBUTES–1之间的值。GL_ACTIVE_ATTRIBUTES的值是使用glGetProgramiv查询的。索引0选择第一个活动属性,索引GL _ ACTIVE _ ATTRIBUTES–1选择最后一个顶点属性。

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

我们讨论了在顶点着色器中,顶点属性变量由属性限定符指定,活动属性的数量可以使用glGetProgamiv查询,程序中的活动属性列表可以使用GlGetActiveAttribute查询。我们还描述了范围从0到(GL _ MAX _ VERTEX _ ATTRIBS–1)的通用属性索引用于启用通用顶点属性,并使用glVertexAttrib和glVertexAttribPointer命令指定常数或每个顶点(即顶点数组)的值。现在我们描述如何将这个通用属性索引映射到顶点着色器中声明的适当属性变量。该映射将允许适当的顶点数据被读入顶点着色器中正确的顶点属性变量。

OpenGL ES 2.0有两种方法可以将通用顶点属性索引映射到顶点着色器中的属性变量名。这些方法可以分类如下:

  • OpenGL ES 2.0将把通用顶点属性索引绑定到属性名上。
  • 应用程序可以将顶点属性索引绑定到属性名。

glBindAttribLocation:将通用顶点属性索引绑定到顶点着色器中的属性变量。该绑定在程序下次链接时生效。它不会更改当前链接程序使用的绑定。
另一个选项是让OpenGL ES 2.0将属性变量名绑定到一个通用的顶点属性索引.
当程序定义的程序对象最后一次链接时,glGetAttribLocation返回绑定到属性变量名的通用属性索引。如果名称不是活动的属性变量,或者如果程序不是有效的程序对象或链接不成功,则返回–1,表示无效的属性索引。

总结一下:
一种是将索引值绑定到名称上,一种是顶点属性绑定到属性名上。我 一般使用的是glBindAttribLocation。我的所有案例都使用它。

顶点缓冲对象

使用顶点数组指定的顶点数据存储在客户端内存中。当调用glDrawArrays或glDrawElements时,这些数据必须从客户端内存复制到图形内存。

如果我们不必在每次绘图调用时复制顶点属性、顶点数组和缓冲对象,而是将数据缓存在图形内存中,那就更好了。这可以显著提高渲染性能,并额外降低内存带宽和功耗要求,这两者对于手持设备都非常重要。

这是顶点缓冲对象可以提供帮助的地方。顶点缓冲区对象允许OpenGL ES 2.0应用程序在高性能图形内存中分配和缓存顶点数据,并从该内存中进行渲染,从而避免每次绘制图元时重新发送数据。不仅顶点数据,甚至描述图元顶点索引并作为参数传递给glDrawElements的元素索引也可以缓存。

总结一下:数据每次绘制的时候都需要从本地内存然后复制到绘制内存 ,为了提高效率不需要这么麻烦,可以将数据直接的放入到缓存区中,这样就避免了每次都发送数据。

OpenGL ES支持两种类型的缓冲对象:数组缓冲对象和元素数组缓冲对象

  • GL_ARRAY_BUFFER标记指定的数组缓冲区对象用于创建存储顶点数据的缓冲区对象
  • GL_ELEMENT_ARRAY_BUFFER标记指定的元素数组缓冲区对象用于创建缓冲区对象,该对象将存储基元的索引。

在我们可以使用缓冲对象渲染之前,我们需要分配缓冲对象,并将顶点数据和元素索引上传到适当的缓冲对象中

glGenBuffers(2, vboIds);
glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
glBufferData(GL_ARRAY_BUFFER, numVertices * sizeof(vertex_t), 
                 vertexBuffer, GL_STATIC_DRAW);
// bind buffer object for element indices
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
                 numIndices * sizeof(GLushort),indices, 
                 GL_STATIC_DRAW);

首先 创建两个缓存区,
1.第一个缓存区存放array,绑定缓存区,存入数据
2.第二个缓存区存放element,绑定缓存区,存入数据

glGenBuffers

glGenBuffers(int size,uint *buffers)

创建size个缓存区,并返回它们

glBindBuffer

使缓存区对象成为当前数组缓存前对象或者元素数组缓存区对象,第一次调用的时候,缓存区对象将分配默认状态,如果分配成功,分配的对象将被绑定为渲染上下文的当前数组缓冲区对象或当前元素数组缓冲区对象。
与缓冲区对象相关联的状态可以分类如下:

  • GL_BUFFER_SIZE。这是指由glBufferData指定的缓冲区对象数据的大小。首次使用glBindBuffer绑定缓冲区对象时的初始值为零。
  • GL_BUFFER_USAGE。这是一个关于应用程序如何使用存储在缓冲区对象中的数据的提示。这在表6-2中有详细描述。初始值是GL_STATIC_DRAW。

顶点数组数据或元素数组数据存储是使用glBufferData命令创建和初始化的。
glBufferData将根据大小值保留适当的数据存储。数据参数可以是空值,表示保留的数据存储仍未初始化。如果数据是有效的指针,那么数据的内容被复制到分配的数据存储中。缓冲区对象数据存储的内容可以使用glBufferSubData命令进行初始化或更新。
使用glBufferData或glBufferSubData初始化或更新缓冲区对象数据存储后,不再需要客户端数据存储,可以将其释放。对于静态几何,应用程序可以释放客户端数据存储,并减少应用程序消耗的整体系统内存。这对于动态几何可能是不可能的。

GLuint   offset = 0;
//创建两个句柄
GLuint   vboIds[2];
// vboIds[0] – used to store vertex attribute data
// vboIds[1] – used to store element indices
//生成两个缓存区句柄
glGenBuffers(2, vboIds);
//数组 缓存区
glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
//存值
glBufferData(GL_ARRAY_BUFFER, vtxStride * numVertices,vtxBuf, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLushort) * numIndices, indices, GL_STATIC_DRAW);
glEnableVertexAttribArray(VERTEX_POS_INDX);
glEnableVertexAttribArray(VERTEX_NORMAL_INDX); 
glEnableVertexAttribArray{VERTEX_TEXCOORD0_INDX);
glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, GL_FLOAT, GL_FALSE, vtxStride,  (const void*)offset);
offset += VERTEX_POS_SIZE * sizeof(GLfloat);
glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE, GL_FLOAT, GL_FALSE, vtxStride, (const void*)offset);
offset += VERTEX_NORMAL_SIZE * sizeof(GLfloat);
glVertexAttribPointer(VERTEX_TEXCOORD0_INDX,   VERTEX_TEXCOORD0_SIZE,GL_FLOAT, GL_FALSE, vtxStride, (const void*)offset);
glBindAttribLocation(program, VERTEX_POS_INDX, "v_position");
glBindAttribLocation(program, VERTEX_NORMAL_INDX, "v_normal");
glBindAttribLocation(program, VERTEX_TEXCOORD0_INDX, "v_texcoord");
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0);
glDeleteBuffers(2, vboIds);
}

上面的案例将 数据存储在一个数组中,下来是将 数据存储在多个数组中。

Example 6-6 Drawing with a Buffer Object per Attribute
#define VERTEX_POS_SIZE         3   // x, y and z
#define VERTEX_NORMAL_SIZE      3   // x, y and z
#define VERTEX_TEXCOORD0_SIZE   2   // s and t
#define VERTEX_POS_INDX         0
#define VERTEX_NORMAL_INDX      1
#define VERTEX_TEXCOORD0_INDX   2
//
// numVertices – number of vertices
// vtxBuf – an array of pointers describing attribute data
// vtxStrides – an array of stride values for each attribute
// numIndices – number of element indices of primitive
// indices – actual element index buffer
//
void   drawPrimitiveWithVBOs(GLint numVertices, 
                             GLfloat **vtxBuf, GLint *vtxStrides, 
                             GLint numIndices, GLushort *indices)
{
    GLuint   vboIds[4];
    // vboIds[0] – used to store vertex position
    // vboIds[1] – used to store vertex normal
    // vboIds[2] – used to store vertex texture coordinate 0
     // vboIds[3] – used to store element indices
    glGenBuffers(4, vboIds);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glBufferData(GL_ARRAY_BUFFER, vtxStrides[0] * numVertices, 
                 vtxBuf[0], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[1]);
    glBufferData(GL_ARRAY_BUFFER, vtxStrides[1] * numVertices, 
                 vtxBuf[1], GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[2]);
    glBufferData(GL_ARRAY_BUFFER, vtxStrides[2] * numVertices, 
                 vtxBuf[2], GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[3]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, 
                 sizeof(GLushort) * numIndices, 
                 indices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[0]);
    glEnableVertexAttribArray(VERTEX_POS_INDX);
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[1]);
    glEnableVertexAttribArray(VERTEX_NORMAL_INDX); 
    glBindBuffer(GL_ARRAY_BUFFER, vboIds[2]);
    glEnableVertexAttribArray{VERTEX_TEXCOORD0_INDX);
    glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, 
                          GL_FLOAT, GL_FALSE, vtxStrides[0], 0);
    glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStrides[1], 0);
    glVertexAttribPointer(VERTEX_TEXCOORD0_INDX,
                          VERTEX_TEXCOORD0_SIZE,
                          GL_FLOAT, GL_FALSE, vtxStrides[2], 0);
    glBindAttribLocation(program, VERTEX_POS_INDX, "v_position");
    glBindAttribLocation(program, VERTEX_NORMAL_INDX, "v_normal");
    glBindAttribLocation(program, VERTEX_TEXCOORD0_INDX, 
                         "v_texcoord");
    glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0);
    glDeleteBuffers(4, vboIds)
}

其实看起来也不是很清楚,下来整个案例来一下就可以了。

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