AttribPointerVertex() 与 glVertexAttribIPointer(), I 表示整形
在编程过程中遇到的一个小问题, 代码如下:
GLuint index[3]{ 0, 1, 2 };
//省略...
glEnableVertexAttribArray(indexN);
glVertexAttribPointer(indexN, 1, GL_FLOAT, GL_FALSE, 0, 0);//错误
//glVertexAttribIPointer(indexN, 1, GL_UNSIGNED_INT, 0, 0);//正确
最终渲染的时候,图形出不来,仔细看了一下,找到原因,记录一下。
glVertexAttribPointer()命令原型如下所示:
void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid* pointer);
如果在type中传入了GL_SHORT或者GL_UNSIGNED_INT这样的整数类型,那么OpenGL只能将这些数据类型存储到缓存对象的内存中。
OpenGL必须将这些数据转换为浮点数才可以将它们读取到浮点数的顶点属性中。执行这一转换过程可以通过normalize参数来控制,如果normalize为GL_FALSE,那么整数将直接被强制转换为浮点数的形式,然后再传入到顶点着色器中。换句话说,如果将一个整数 4 置入缓存,设置type为GL_INT,而normalize为GL_FALSE,那么着色器中传入的值就是 4.0。
如果normalize为GL_TRUE,那么数据在传入到顶点着色器之前需要首先进行归一化。为此,OpenGL会使用一个固定的依赖于输入数据类型的常数去除每个元素。如果数据类型是有符号的,那么相应的计算公式如下:
如果数据类型是无符号的,那么相应的计算公式如下:
这两个公式当中**,f的结果就是浮点数值**,c表示输入的整数分量,b表示数据类型的位数(例如GL_UNSIGNED_BYTE就是8,GL_SHORT就是16,以此类推)。注意,无符号数据类型在除以类型相关的常数之前,还需要进行缩放和偏移操作。之前我们向整数顶点属性中传入4作为示例,那么这里我们将得到:
它的结果相当于0.000000009313—这是一个非常小的数字!
整型顶点属性
如果你对浮点数值的工作方式比较熟悉的话,那么你应该也知道如果它的数值很大的时候,会造成精度的丢失,因此大范围的整数值不能直接使用浮点型属性传入顶点着色器中。因此,我们需要引入整型顶点属性。它们在顶点着色器中的表示方法为int、ivec2、ivec3以及ivec4,当然也有无符号的表现形式,即uint、uvec2、uvec3以及uvec4。
我们需要用到另一个顶点属性的函数将整数传递到顶点属性中,它不会执行自动转换到浮点数的操作。这个函数叫做glVertexAttribIPointer(),其中I表示整型的意思。
void glVertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer);
与glVertexAttribPointer()类似,不过它专用于向顶点着色器中传递整型的顶点属性。type必须是整型数据类型的一种,包括GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT,以及GL_UNSIGNED_INT。
注意,glVertexAttribIPointer()的参数与glVertexAttribPointer()是完全等价的,只是不再需要normalize参数。这是因为normalize对于整型顶点属性来说是没有意义的。这里的type参数只能使用GL_BYTE、GL_UNSIGNED_BYTE、GL_SHORT、GL_UNSIGNED_SHORT、GL_INT,以及GL_UNSIGNED_INT这些标识符。
双精度顶点属性
glVertexAttribPointer()的第三个变化就是glVertexAttribLPointer()—这里的L表示“long”。这个函数专门用于将属性数据加载到64位的双精度浮点型顶点属性中。
void glVertexAttribLPointer(GLuint index, GLint size, GLenum type, GLsizei stride, const GLvoid* pointer);
与glVertexAttribPointer()类似,不过对于传入顶点着色器的64位的双精度浮点型顶点属性来说,type必须设置为GL_DOUBLE。
这里再次说明,normalize参数依然是不需要的。
glVertexAttribPointer()中的normalize只是用来处理那些不适宜直接使用的整型类型,因此在这里它并不是必须的。如果glVertexAttribPointer()函数也使用了GL_DOUBLE类型,那么实际上数据在传递到顶点着色器之前会被自动转换到32位单精度浮点型方式—即使我们的目标顶点属性已经声明为双精度类型,例如double、dvec2、dvec3、dvec4,或者双精度的矩阵类型,例如dmat4。但是,glVertexAttribLPointer()可以保证输入数据的完整精度,并且将它们直接传递到顶点着色器阶段。
顶点属性的压缩数据格式
回到glVertexAttribPointer()命令,之前已经提及,size参数的可选值包括1、2、3、4,以及一个特殊的标识符GL_BGRA。此外,type参数也可以使用某些特殊的数值,即GL_INT_2_10_10_10_REV或者GL_UNSIGNED_INT_2_10_10_10_REV,它们都对应于GLuint数据类型。这些特殊的标识符可以用来表达OpenGL支持的压缩数据格式。GL_INT_2_10_10_10_REV和GL_UNSIGNED_INT_2_10_10_10_REV标识符表示了一种有四个分量的数据格式,前三个分量均占据10个字节,第四个分量占据2个字节,这样压缩后的大小是一个32位单精度数据(GLuint)。GL_BGRA可以被简单地视为GL_ZYXW的格式。根据32位字符类型的数据布局方式,我们可以得到如图3-3的数据划分方式。
图3-3中,顶点元素分布在一个32位单精度整数中,顺序为w、x、y、z—反转之后就是z、y、x、w,或者用颜色分量来表示就是b、g、r、a。图3-4中,各个分量的压缩顺序为w、z、y、x,反转并写作颜色分量的形式就是r、g、b、a。
顶点数据可以使用GL_INT_2_10_10_10_REV或者GL_UNSIGNED_INT_2_10_10_10_REV这两种格式中的一种来设置。如果glVertexAttribPointer()的type参数设置为其中一种标识符,那么顶点数组中的每个顶点都会占据32位。这个数据会被分解为各个分量然后根据需要进行归一化(根据normalize参数的设置),最后被传递到对应的顶点属性当中。这种数据的排布方式对于法线等类型的属性设置特别有益处,三个主要分量的大小均为10位,因此精度可以得到额外的提高,并且此时通常不需要达到半浮点数的精度级别(每个分量占据16位)。这样节约了内存空间和系统带款,因此有助于提升程序性能。
参考:
https://book.2cto.com/201412/48547.html