顶点数据,也称为顶点属性,指每一个顶点数据。指能被用来描述每个顶点的数据,或能被所有顶点使用的常量值。例如你想绘制一个具有颜色的立方体三角形。你指定一个恒定的值用于三角形的所有三个顶点颜色。但三角形的三个顶点位置是不同的,你需要指定一个顶点矩阵存储三个位置值。
指定顶点属性数据
顶点属性数据可以使用顶点数组或常量值指定每个顶点数据,OpenGL ES 3.0 必须至少支持16 个顶点属性。应用应该能够查询编译器支持的确切属性数。下面的程序指出如何查询。
GLint maxVertexAttribs; // n will be >= 8glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs);
常量顶点属性
常量顶点属性是指基元的所有顶点属性是相同的,因此仅仅对基元的所有顶点仅仅需要指定一个值。顶点属性常量使用下面的函数指定:
void glVertexAttriblf(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);void glVertexAttriblfv(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);
glVertexAttrib*通过索引来装在一般顶点属性,glVertexAttriblf和glVertexAttriblfv加载(x, 0.0, 0.0, 1.0)到顶点属性,glVertexAttrib2f and glVertexAttrib2fv 装载(x, y, 0.0, 1.0),glVertexAttrib3f 和glVertexAttrib3fv 装载(x, y, z, 1.0),glVertexAttrib4f and
glVertexAttrib4fv 装载(x, y, z, w)
顶点数组
顶点数组指定每个顶点的属性数据即存储在应用程序地址空间(OpenGL ES 叫clientspace)缓冲区的数据。它们提供有效的和灵活的方法指定顶点属性数据。顶点数组使用glVertexAttribPointer 或glVertexAttribIPointer函数指定:
存储所有的顶点属性在一个缓冲区中,这种存储顶点属性的方法叫结构数组,这种方法描述每个顶点的所有属性。存储每个顶点属性到分开的缓冲区,这种存储顶点属性的方法叫数组结构每个顶点有四个属性—位置、法线、两个贴图坐标,这些属性被存储在一个缓冲区中,被所有顶点分享。顶点位置属性是三个浮点数(x, y, z)的矢量。法线也是三个浮点数的矢量,每个贴图坐标是两个浮点数的矢量。
结构数组代码示例
#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_TEXCOORDl_SIZE 2 // s and t#define VERTEX_POS_INDX 0#define VERTEX_NORMAL_INDX 1#define VERTEX_TEXCOORD0_INDX 2#define VERTEX_TEXCOORDl_INDX 3// the following 4 defines are used to determine the locations// of various attributes if vertex data are stored as an array// of structures#define VERTEX_POS_OFFSET 0#define VERTEX_NORMAL_OFFSET 3#define VERTEX_TEXCOORD0_OFFSET 6#define VERTEX_TEXC00RD1_0FFSET 8#define VERTEX_ATTRIB_SIZE (VERTEX_POS_SIZE + \ VERTEX_NORMAL_SIZE + \ VERTEX_TEXCOORD0_SIZE + \ VERTEX_TEXC00RD1_SIZE)
float *p = (float*) malloc(numVertices * VERTEX_ATTRIB_SIZE* sizeof(float));// position is vertex attribute 0glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, GL_FLOAT, GL_FALSE, VERTEX_ATTRIB_SIZE * sizeof(float), p);// normal is vertex attribute 1glVertexAttribPointer(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 2glVertexAttribPointer(VERTEX_TEXCOORDO_INDX, VERTEX_TEXCOORD0_SIZE, GL_FLOAT, GL_FALSE, VERTEX_ATTRIB_SIZE * sizeof(float), (p + VERTEX_TEXCOORD0_OFFSET));// texture coordinate 1 is vertex attribute 3glVertexAttribPointer(VERTEX_TEXCOORDl_INDX, VERTEX_TEXC00RD1_SIZE, GL_FLOAT, GL_FALSE, VERTEX_ATTRIB_SIZE * sizeof(float), (p + VERTEX_TEXC00RD1_0FFSET));
数组结构示例代码
float *position = (float*) malloc(numVertices *VERTEX_POS_SIZE * sizeof(float));float *normal = (float*) malloc(numVertices *VERTEX_NORMAL_SIZE * sizeof(float));float *texcoordO = (float*) malloc(numVertices *VERTEX_TEXCOORD0_SIZE * sizeof(float));float *texcoordl = (float*) malloc(numVertices *VERTEX_TEXC00RD1_SIZE * sizeof(float));// position is vertex attribute 0glVertexAttribPointer(VERTEX_POS_INDX, VERTEX_POS_SIZE, GL_FLOAT, GL_FALSE, VERTEX_POS_SIZE * sizeof(float), position);// normal is vertex attribute 1glVertexAttribPointer(VERTEX_NORMAL_INDX, VERTEX_NORMAL_SIZE, GL_FLOAT, GL_FALSE, VERTEX_NORMAL_SIZE * sizeof(float), normal);// texture coordinate 0 is vertex attribute 2glVertexAttribPointer(VERTEX_TEXCOORDO_INDX, VERTEX_TEXCOORD0_SIZE, GL_FLOAT, GL_FALSE, VERTEX_TEXCOORD0_SIZE *sizeof(float), texcoordO);// texture coordinate 1 is vertex attribute 3glVertexAttribPointer(VERTEX_TEXCOORDl_INDX, VERTEX_TEXC00RD1_SIZE, GL_FLOAT, GL_FALSE, VERTEX_TEXC00RD1_SIZE * sizeof(float), texcoordl);
性能提示
1.如何存储不同的顶点属性。
使用结构数组要比使用数组结构性能更有优,原因是每个顶点的属性数据能够被连续的读出,这种内存结构更有效。但使用array of structures 不好的是当我们想去修改指定的属性时。如果一个顶点属性需要被修改(像贴图坐标),这将必须更新顶点缓冲区。当顶点缓冲区作为缓冲区对象时,整个
顶点属性缓冲区将需要更新加载,
2.顶点属性使用哪种数据格式
顶点属性数据格式 通过调用glVertexAttribPointer 函数的参数type指定,这样做不但影响顶点属性的绘图数据的存储要求,也影响全部的执行工作,它是渲染帧时的内存带宽的要求。数据量越小,对带宽要求越低。OpenGL ES 3支持16位浮点顶点格式命名gl_half_float,建议尽量使用gl_half_float,Texture coordinates, normals, binormals, tangent vectors都适合使用gl_half_float来存储,颜色使用四个GL_UNSIGNED_BYTE来存储每个顶点颜色,顶点位置应该存储为GL_FLOAT。
3.如何标准化glVertexAttribPointer 工作
顶点属性在被顶点着色器使用前,作为单一精度的浮点值被存储在内存中。如果顶点属性的数据类型不是浮点数,那么它们的值将在着色器使用前转变为浮点值。normalized标志指示非浮点顶点属性数据转化为单一精度的浮点值。如果normalized符为false,顶点数值被直接转化为浮点值,转化非浮点变量为浮点类型是相似的
GLfloat f;GLbyte b;f = (GLfloat)b; // f represents values in the range [-128.0,// 127.0]
如果normalized为true,顶点数据类型如果是GL_BYTE, GL_SHORT 或 GL_FIXED 被匹配到[-1.0,1.0],数据类型如果是GL_UNSIGNED_BYTE or GL_UNSIGNED_SHORT 被匹配到[0.0,1.0]。下面描述非浮点值数据类型normalized转换过程
也有可能访问整数顶点属性数据为整数的顶点着色器,而不将它们转换为浮点数。在这种情况下,glvertexattribipointer功能应使用顶点属性应该被声明为一个整数类型的顶点着色。
4.在常量顶点属性和顶点数组之间选择
int Init ( ESContext *esContext ){ UserData *userData = (UserData*) esContext->userData; const char vShaderStr[] = "#version 300 es \n" "layout(location = 0) in vec4 a_color; \n" "layout(location = 1) in vec4 a_position; \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 ( 0.0f, 0.0f, 0.0f, 0.0f ); return GL_TRUE;}
void Draw ( ESContext *esContext ){ UserData *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 ); glVertexAttrib4fv ( 0, color ); glVertexAttribPointer ( 1, 3, GL_FLOAT, GL_FALSE, 0, vertexPos ); glEnableVertexAttribArray ( 1 ); glDrawArrays ( GL_TRIANGLES, 0, 3 ); glDisableVertexAttribArray ( 1 );}
在顶点着色器中声明顶点属性变量
在顶点着色器中,通过限定符【in】来声明一个变量作为顶点属性。属性变量也可以加【layout】限定符,用于提供属性索引。如下
layout(location = 0) in vec4 a_position;layout(location = 1) in vec2 a_texcoord;layout(location = 2) in vec3 a_normal;
【in】限度符支持的数据类型包括float, vec2,vec3, vec4, int, ivec2, ivec3, ivec4, uint, uvec2, uvec3,uvec4, mat2, mat2x2, mat2x3, mat2x4, mat3, mat3x3, mat3x4,=mat4, mat4x2, and mat4x3.它并不支持数组和结构体。下面声明会发生编译错误
in foo_t a_A; // foo_t is a structurein vec4 a_B[10];
声明的顶点属性变量是只读的,不可被修改
顶点缓存对象
顶点数据通过顶点数组存储在客户端内存中,当使用glDrawArrays or glDrawElements绘制时,此数据必须从客户端内存复制到图形内存。顶点缓存区对象允许应用程序顶点数据分配和缓存在具有高性能的图形内存并渲染。而避免重发数据的每一次图元的绘制。缓存对象支持2种类型,数据缓存对象和元素数组缓存对象,数组缓存对象使用GL_ARRAY_BUFFER令牌来指定,元素数组缓存对象使用GL_ELEMENT_ARRAY_BUFFER来指定,在使用缓冲区对象之前,我们需要分配缓冲对象并将顶点数据和元素索引上传到缓冲对象。
void initVertexBufferObjects(vertex_t *vertexBuffer, GLushort *indices, GLuint numVertices, GLuint numlndices, GLuint *vboIds){ 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 indicesglBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIds[1]);glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices * sizeof(GLushort), indices, GL_STATIC_DRAW);}
glGenBuffers用于创建缓存区对象。N为0的值被系统保留不会分配,所以当使用0时会发生错误。
glBindBuffer函数是一个缓存区对象设置为当前在使用的缓存区对象。glGenBuffers 在被使用glBindBuffer 绑定前,不要求被指定缓冲区对象名,一个应用能指定一个未使用的缓冲区对象名字去绑定。但我们推荐使用glGenBuffers 返回的名字而不是自己指定。
与缓存对象关联的状态有如下
顶点数组数据和元素顶点数组数据被创建和初始化使用glBufferData 函数
#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//// 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 primitives// 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 = (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 = (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 );}
顶点数组对象
OpenGL ES 3.0推出的顶点数组对象。负责单一对象的顶点数组/顶点缓冲区对象转换配置。
#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_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 }; 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 set up 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 ( 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 ); 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 );}