顶点数据,又称顶点属性,给定了每个顶点的数据。
这类每个顶点的数据可以每个顶点分别给定,也可以给定一个所有顶点共用的常量。
在OpenGL ES 1.1中,顶点属性名称是预定义的,如position、normal、color、texture coordinates。这是因为OpenGL ES 1.1的固定功能流水线仅需要这些预定义的顶点属性。
在OpenGL ES 2.0中,顶点属性都是通用顶点属性。
给定顶点属性数据
可以使用GL_MAX_VERTEX_ATTRIBS来查询最多支持的顶点属性个数。
常数顶点属性
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);
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);
顶点数组
顶点数组指定了每个顶点的数据,它是保存在应用地址空间(即OpenGL ES所谓的客户端空间)中的缓冲。
顶点数组用以下函数指定:
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void *ptr);
type可以是GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_FLOAT、GL_FIXED、GL_HALF_FLOAT_OES。GL_HALF_FLOAT_OES不一定支持。
顶点数组缓冲的保存有两种方式:
- 结构体数组:将一个顶点的所有属性保存为一个结构体,并将所有顶点的属性保存为一个数组
- 数组的结构体:将所有顶点的每个属性保存为一个数组,然后将每个属性数组组成一个结构体
性能提示
如何保存顶点的不同属性
在OpenGL ES 2.0硬件实现中,结构体数组的保存方式具有更好的性能。因为一个顶点的所有属性保存在一起,在使用时更加连续。但是,结构体数组的保存方式会使得对顶点某个属性更新时,出现不连续的访存,而当顶点缓冲是一个缓冲对象时,整个顶点属性缓冲都需要更新。为了避免这种情况,可以将动态的顶点属性保存到独立的缓冲中。
顶点属性使用何种数据类型
应使用合适的、最小体积的数据类型,如颜色使用GL_UNSIGNED_BYTE,其他使用GL_HALF_FLOAT_OES。
normalized是怎样工作的
如果顶点属性不是单精度浮点类型,在被vertex shader用于计算之前,它会被转换成单精度浮点类型。
normalized控制这类型转换的方式,当其为0时,数据被直接转换成单精度浮点类型,当其为1时,会将原类型的整体表示范围映射到
归一化的范围内,具体如下:
顶点数据格式 | 转换到浮点操作 |
---|---|
GL_BYTE | \(\frac{2c+1}{2^8-1}\) |
GL_UNSIGNED_BYTE | \(\frac{c}{2^8-1}\) |
GL_SHORT | \(\frac{2c+1}{2^{16}-1}\) |
GL_UNSIGNED_SHORT | \(\frac{c}{2^{16}-1}\) |
GL_FIXED | \(\frac{c}{2^{16}}\) |
GL_FLOAT | \(c\) |
GL_HALF_FLOAT_OES | \(c\) |
选择常量顶点属性或顶点数组
void glEnableVertexAttribArray(GLuint index);
void glDisableVertexAttribArray(GLuint index);
当顶点数组矩阵被禁用时,将会使用常量顶点属性数据。
在Vertex Shader中声明属性
在vertex shader中,可以对变量使用attribute修饰符。attribute修饰符不能用于数组和结构体,不能用于fragment shader。
可以使用GL_MAX_VERTEX_ATTRIBS查询OpenGL ES 2.0实现支持的vec4顶点属性数量。
float、vec2、vec3类型的属性会被计作一个vec4属性。
不像uniform和varying,属性不会被编译器自动打包。
被定义为attribute的变量是只读的。
如果attribute变量仅被声明而没有使用,该变量将不被认为是激活的。
可以使用GL_ACTIVE_ATTRIBUTES查询激活的属性数量:
void GLGetProgramiv(prgoram, GL_ACTIVE_ATTRIBTES, &numActivateAttribs);
可以用以下函数查询激活的顶点属性列表及其数据类型:
void GLGetActiveAttrib(GLuint program, GLint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, GLchar *name);
绑定顶点属性和vertex shader中的属性变量
OpenGL ES 2.0提供了两种方法将通用顶点属性索引映射到vertex shader中的属性变量名:
- OpenGL ES 2.0绑定通用顶点属性索引到属性名称
- 应用绑定顶点属性索引到变量名称
glBindAttribLocation命令被用于绑定通用顶点属性索引到vertex shader中的一个属性变量。该绑定在程序下次link时生效,它不会修改当前已经完成link的程序的绑定关系。
void glBindAttribLocation(GLuint program, GLuint index, const GLchar *name);
在link阶段,OpenGL ES 2.0实现首先检查每个属性变量是否被使用glBindAttribLocation绑定,如果有,则使用指定的绑定,否则,就给它分配一个通用顶点属性索引。
因此,对于OpenGL ES 2.0自动分配的索引,需要先获取其分配结果,然后再指定数据。
使用以下函数获取属性变量的位置:
GLint glGetAttribLocation(GLuint program, const GLchar *name);
顶点缓冲对象
使用顶点数组给定的顶点数据保存在客户端内存中,每次调用glDrawArrays或glDrawElements(统称为draw call)时都需要复制到图形存储中。而缓冲对象允许OpenGL ES 2.0应用分配高性能图形存储并缓存顶点数据,直接从中进行渲染,而不必每次绘制时复制数据。
OpenGL ES支持两种缓冲对象:数组缓冲对象(array buffer objects)和元数组缓冲对象(element array buffer object)。数组缓冲对象用来保存顶点数据,以GL_ARRAY_BUFFER创建。元数组缓冲对象用来保存图元的索引,以GL_ELEMENT_ARRAY_BUFFER创建。
创建buffer object,分配buffer object name,注意buffer object name的命名空间为正整数:
void glGenBuffers(GLsizei n, GLuint *buffers);
绑定buffer object,当buffer object第一次进行绑定时,会为buffer object分配默认状态,绑定后分配的buffer object会成为渲染上下文中当前使用的array buffer object或element array buffer object:
void glBindBuffer(GLenum target, GLuint buffer);
与buffer object相关的状态有:
- GL_BUFFER_SIZE:buffer object中数据的大小,默认为0
- GL_BUFFER_USAGE:应用将如何使用buffer object中存储的数据的提示,具体如下表所示。
buffer usage索引 | 描述 |
---|---|
GL_STATIC_DRAW | 缓冲对象的数据将给定一次,然后被多次使用 |
GL_DYNAMIC_DRAW | 缓冲对象的数据将给定多次,并多次使用 |
GL_STREAM_DRAW | 缓冲对象的数据给定一次,然后使用少量几次 |
注意,GL_BUFFER_USAGE仅仅是一个提示而非保证,可能与实际使用情况不同。
buffer object数据建立和初始化:
void glBufferData(GLenum target, GLsizeiptr size, const void *data, GLenum usage);
其中,target可以为GL_ARRAY_BUFFER或GL_ELEMENT_ARRAY_BUFFER。
glBufferData保留了size大小的空间,如果data是合法指针,数据将被复制到分配的数据存储空间,如果data是NULL,分配的存储空间将是未初始化的。
buffer object的数据存储空间可以使用以下命令来部分初始化:
void glBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void *data);
删除buffer object:
void glDeleteBuffers(GLsizei n, GLuint *buffers);
映射缓冲对象
OES_map_buffer扩展允许应用映射buffer object到应用的地址空间和解除映射,映射后,可以对buffer object的内容进行更新。
OpenGL ES 2.0仅允许对映射空间的写操作。
void *glMapBufferOES(GLenum target, GLenum access);
GLboolean glUnmapBufferOES(GLenum target);
其中,access只能是GL_WRITE_ONLY_OES,命令返回一个指向映射后地址空间的指针。
性能提示
glMapBufferOES应该只用于更新全部的buffer,使用该命令更新部分区域是不建议的,glMapBufferOES中也没有机制指定一个子区域。
如果需要更新的buffer正在被使用,GPU应该等待之前的正在使用该buffer的渲染命令完成后再返回指针。
合理的使用方法是,先使用glBufferData分配一个未初始化的buffer,紧接着使用glMapBufferOES更新全部区域,这种情况下,OpenGL ES实现可以对其正确地进行优化。
概念辨析
generic vertex attribute,通用顶点属性,vertex attribute,顶点属性,是同一概念,是从OpenGL ES 2.0的视角对顶点属性的描述。
attribute,属性,attribute variable,属性变量,是同一概念,是指vertex shader中声明的变量,是从shader的视角对顶点属性的描述。
属性变量的index,和其location是不同的概念。前者是查询属性变量列表时属性变量在列表中的排序,后者是属性变量与通用顶点属性绑定之后,属性变量对应的通用顶点属性的index。