我的OpenGL学习进阶之旅统一变量和属性

Posted 欧阳鹏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的OpenGL学习进阶之旅统一变量和属性相关的知识,希望对你有一定的参考价值。

一、统一变量和属性

一旦链接了程序对象,就可以在对象上进行许多查询。

首先,你可能需要找出程序中的活动统一变量

统一变量(uniform)是存储应用程序通过OpenGL ES 3.0 API传递给着色器的只读常数值的变量。

统一变量被组合成两类统一变量块。

  • 第一类是命名统一变量块
    命名统一变量块,统一变量的值由所谓的统一变量缓冲区对象支持。命名统一变量块被分配一个统一变量块索引。
    下面例子声明了一个名为TransformBlock并包含 3个统一变量(matViewProj、matNormal、matTexGen)的统一变量块:
uniform TransformBlock

	mat4 matViewProj;
	mat3 matNormal;
	mat3 matTexGen;

  • 第二类是默认的统一变量块
    默认的统一变量块,用于在命名统一变量块之外声明的统一变量。和命名统一变量块不同,默认统一变量块没有名称或者统一变量块索引。
    下面的例子在命名统一变量块之外声明同样的3个统一变量:
uniform mat4 matViewProj;
uniform mat3 matNormal;
uniform mat3 matTexGen;

关于统一变量块更详细的内容将在后面的博客介绍。

如果统一变量在顶点着色器和片段着色器均有声明,则声明的类型必须相同,且在两个着色器中的值也需相同。

在链接阶段,链接程序将为程序中与默认统一变量块相关的活动统一变量指定位置。这些位置是应用程序用于加载统一变量的标识符。

链接程序还将为与命名统一变量块相关的活动统一变量分配偏移和跨距(对于数组和矩阵类型的统一变量)。

1.1 获取和设置统一变量

要查询程序中活动统一变量的列表,首先要用GL_ACTIVE_UNIFORMS参数调用glGetProgramiv(在上一篇博客【我的OpenGL学习进阶之旅】程序对象 介绍过)。 这样可以获得程序中活动统一变量的数量。

这个列表包含命名统一变量块中的统一变量、着色器代码中声明的默认统一变量块中的统一变量以及着色器代码中使用的内建统一变量。

如果统一变量被程序使用,就认为它是“活动”的。换言之,如果你在一个着色器中声明了一个统一变量,但是从未使用,链接程序可能会在优化时将其去掉,不在活动统一变量列表中返回。

你还可能发现程序中最大统一变量名称的字符数量(包括null终止符);这可以用GL_ACTIVE_UNIFORM_MAX_LENGTH参数调用glGetProgramiv获得。

知道活动统一变量和存储统一变量名称所需的字符数之后,我们可以用glGetActiveUniformglGetActiveUniformsiv找出每个统一变量的细节。

1.1.1 glGetActiveUniform

void glGetActiveUniform (GLuint program, GLuint index, 
						GLsizei bufSize, GLsizei *length, 
						GLint *size, GLenum *type, GLchar *name);


参数说明:

  • program
    程序对象句柄
    +index
    查询的统一变量索引

  • bufSize
    名称数组中的字符数

  • length
    如果不是NULL,则是名称数组中写入的字符数(不含null终止符)

  • size
    如果查询的统一变量是个数组,这个变量将写入程序中使用的最大数组元素(加1);
    如果查询的统一变量不是数组,则该值为1

  • type
    将写入统一变量类型,可以为:

  • name
    写入统一变量名称,最大字符数为bufSize,这是一个以null终止的字符串。

1.1.2 glGetActiveUniformsiv

void glGetActiveUniformsiv (GLuint program, GLsizei uniformCount, 
							const GLuint *uniformIndices, 
							GLenum pname, GLint *params);


参数说明:

  • program
    程序对象句柄

  • uniformCount
    索引(indices)数组中的元素数量

  • uniformIndices
    统一变量索引列表

  • pname
    统一变量索引中每个统一变量的属性,将被写入params元素。可以是:

  • params
    将写入由对应于统一变量索引中每个统一变量的pname所指定的结果

1.1.3 glGetUniformLocation

使用glGetActiveUniform,可以确定几乎所有统一变量的属性。你可以确定统一变量的名称和类型。此外,可以发现变量是不是数组以及数组中使用的最大元素。

统一变量的名称对于找到统一变量的位置是必要的,要知道如何加载统一变量的数据,需要统一变量的类型和大小。

一旦有了统一变量的名称,就可以使用glGetUniformLocation找到它的位置。

统一变量的位置是一个整数值,用于标识统一变量在程序中的位置(注意,命名统一变量块中的统一变量没有指定位置)。这个位置值用于加载统一变量值的后序调用(例如,glUniformlf)

GLint glGetUniformLocation (GLuint program, const GLchar *name);

参数说明:

  • program
    程序对象句柄
  • name
    需要获取位置的统一变量名称

    这个函数将返回由name指定的统一变量的位置。如果这个统一变量不是程序中的活动统一变量,这个值将为-1。

1.1.4 加载统一变量的值glUniform*

有了统一变量的位置及其类型和数组大小,我们就可以加载统一变量的值。
加载统一变量的值由很多不同的函数,每种统一变量类型都对应不同的函数。

GL_APICALL void GL_APIENTRY glUniform1f (GLint location, GLfloat v0);
GL_APICALL void GL_APIENTRY glUniform1fv (GLint location, GLsizei count, const GLfloat *value);

GL_APICALL void GL_APIENTRY glUniform1i (GLint location, GLint v0);
GL_APICALL void GL_APIENTRY glUniform1iv (GLint location, GLsizei count, const GLint *value);

GL_APICALL void GL_APIENTRY glUniform2f (GLint location, GLfloat v0, GLfloat v1);
GL_APICALL void GL_APIENTRY glUniform2fv (GLint location, GLsizei count, const GLfloat *value);

GL_APICALL void GL_APIENTRY glUniform2i (GLint location, GLint v0, GLint v1);
GL_APICALL void GL_APIENTRY glUniform2iv (GLint location, GLsizei count, const GLint *value);

GL_APICALL void GL_APIENTRY glUniform3f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
GL_APICALL void GL_APIENTRY glUniform3fv (GLint location, GLsizei count, const GLfloat *value);

GL_APICALL void GL_APIENTRY glUniform3i (GLint location, GLint v0, GLint v1, GLint v2);
GL_APICALL void GL_APIENTRY glUniform3iv (GLint location, GLsizei count, const GLint *value);

GL_APICALL void GL_APIENTRY glUniform4f (GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
GL_APICALL void GL_APIENTRY glUniform4fv (GLint location, GLsizei count, const GLfloat *value);

GL_APICALL void GL_APIENTRY glUniform4i (GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
GL_APICALL void GL_APIENTRY glUniform4iv (GLint location, GLsizei count, const GLint *value);

GL_APICALL void GL_APIENTRY glUniformMatrix2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GL_APICALL void GL_APIENTRY glUniformMatrix3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GL_APICALL void GL_APIENTRY glUniformMatrix4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

GL_APICALL void GL_APIENTRY glUniform1ui (GLint location, GLuint v0);
GL_APICALL void GL_APIENTRY glUniform2ui (GLint location, GLuint v0, GLuint v1);
GL_APICALL void GL_APIENTRY glUniform3ui (GLint location, GLuint v0, GLuint v1, GLuint v2);
GL_APICALL void GL_APIENTRY glUniform4ui (GLint location, GLuint v0, GLuint v1, GLuint v2, GLuint v3);
GL_APICALL void GL_APIENTRY glUniform1uiv (GLint location, GLsizei count, const GLuint *value);
GL_APICALL void GL_APIENTRY glUniform2uiv (GLint location, GLsizei count, const GLuint *value);
GL_APICALL void GL_APIENTRY glUniform3uiv (GLint location, GLsizei count, const GLuint *value);
GL_APICALL void GL_APIENTRY glUniform4uiv (GLint location, GLsizei count, const GLuint *value);

GL_APICALL void GL_APIENTRY glUniformMatrix2x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GL_APICALL void GL_APIENTRY glUniformMatrix3x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GL_APICALL void GL_APIENTRY glUniformMatrix2x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GL_APICALL void GL_APIENTRY glUniformMatrix4x2fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GL_APICALL void GL_APIENTRY glUniformMatrix3x4fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
GL_APICALL void GL_APIENTRY glUniformMatrix4x3fv (GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);

参数说明:

  • location
    要加载值的统一变量的位置
  • count
    指定需要加载的数组元素的数量(针对向量命令)或者要修改的矩阵数量(针对矩阵命令)
  • transpose
    对于矩阵命令,指定矩阵是采用列有限顺序(GL_FALSE)还是行优先顺序(GL_TRUE)
  • v0,v1,v2,v3
    更新的统一变量值
  • value
    指向计数元素数组的指针。

加载统一变量的函数大部分都不言自明。

加载统一变量所需的函数根据glGetActiveUniform函数返回的type确定。例如:如果返回的类型是GL_FLOAT_VEC4,那么可以使用glUniform4f 或者glUniform4fv 。 如果glGetActiveUniform返回的size大于1,则使用glUniform4f在一次调用中加载整个数组。如果统一变量不是数组,则可以使用glUniform4f 或者glUniform4fv

值得注意的一点是,glUniform* 调用不以程序对象句柄作为参数。原因是,glUniform* 总是在与glUseprogram绑定的当前程序上操作。 统一变量值本身保存在程序对象中。也就是说,一旦在程序对象中设置一个统一变量的值,即使你让另一个程序处于活动状态,该值仍然保留在原来的程序对象中。从这个意义上,我们可以说统一变量值是程序对象局部所有的

1.2 实例代码

下面的代码块演示了用上述函数查询程序对象中的统一变量信息的方法。


		GLint maxUniformLen;
		GLint numUniformLen;
		char *uniformName;
		GLint index;

		glGetProgramiv(mProgram, GL_ACTIVE_UNIFORMS, &numUniformLen);
		glGetProgramiv(mProgram, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxUniformLen);

		uniformName = (char * ) malloc(sizeof ( char ) * maxUniformLen );
		for( index = 0; index < numUniformLen; index ++)
		
			GLint size;
			GLenum type;
			GLint location;
			
			// Get the uniform info
			glGetActiveUniform(mProgram,index,maxUniformLen, 
					  nullptr, &size, &type, uniformName);
			
			// Get the uniform location
			location = glGetUniformLocation(mProgram,uniformName);
			switch( type )
			
				case GL_FLOAT:
					//
					break;
				case GL_FLOAT_VEC2:
					//
					break;
				case GL_FLOAT_VEC3:
					//
					break;
				case GL_FLOAT_VEC4:
					//
					break;
				case GL_INT:
					//
					break;
				// ... Check for all the types ...
				
				default:
					// Unknown type
					break;
			
		

1.3 统一变量缓冲区对象

1.3.1 统一变量缓冲区对象的定义

可以使用缓冲区对象存储统一变量数据,从而在程序中的着色器之间甚至程序之间共享统一变量。这种缓冲区对象被称作统一变量缓冲区对象。

1.3.2 使用统一变量缓冲区对象的好处

  1. 使用统一变量缓冲区对象,你可以在更新大的统一变量块时降低API开销。
  2. 此外,这种方法增加了统一变量的可用存储,因为你可以不受默认统一变量块大小的限制。

1.3.3 更新统一变量缓冲区对象中的统一变量数据的方法

要更新统一变量缓冲区对象中的统一变量数据,你可以用glBufferData、glBufferSubData、glMapBufferRange和glUnmapBuffer 等命令修改缓冲区对象的内容,而不是使用前面介绍的glUniform*命令。

1.3.4 统一变量在内存中的形式

在统一变量缓冲区对象中,统一变量在内存中以如下形式出现:

  • 类型为bool、int、uint和float的成员保存在内存的特定偏移,分别作为单个unint、int、unint和float类型的分量。
  • 基本数据类型bool、int、uint和float的向量保存在始于特定偏移的连续内存位置中,第一个分量在最低偏移处
  • C列R行的列优先矩阵被当成C浮点列向量的一个数组对待,每个向量包含R个分量。 相类似, R行C列的行优先矩阵被当成R浮点行向量的一个数组,每个向量包含C个分量。 列向量或者行向量连续存储,但是有些实现的存储中可能有缺口。 矩阵中两个向量之间的偏移量被称作 列跨距或者行跨距(GL_UNIFORM_MATRIX_STRIDE),可以在链接的程序中用glGetActiveUniformsiv查询
  • 标量、向量和矩阵的数组按照元素的顺序存储于内存中,成员0放在最低偏移处。数组中每对元素之间的偏移量是一个常数,被称作数组跨距(GL_UNIFORM_ARRAY_STRIDE),,可以在链接的程序中用glGetActiveUniformsiv查询

除非你使用std140统一变量块布局(默认),否则需要查询程序对象得到字节偏移和跨距,以在统一变量缓冲区对象中设置统一变量数据。

Std140布局保证使用由OpenGL ES 3.0规范定义的明确布局规范进行特定包装。因此,使用std140,你就可以在不同的OpenGL ES 3.0实现之间贡献统一变量块。 其他包装格式可能使某些OpenGL ES 3.0实现以比std140布局更紧凑的方式打包数据。

下面是一个使用std140布局的命名统一变量块LightBlock的例子:

layout(std140) uniform LightBlock

	vec3 lightDirection;
	vec4 lightPosition;
;

1.3.5 Std140布局规定

Std140布局规定如下(改编自 OpenGL ES 3.0 规范)。

当统一变量块包含如下成员时:

更多可以参考 GLSL std140布局规则

1.3.6 用glGetUniformBlockIndex 检索统一变量块索引

与统一变量位置值用于引用统一变量类似,统一变量块索引用于引用统一变量块,可以用glGetUniformBlockIndex 检索统一变量块索引。

GLuint glGetUniformBlockIndex (GLuint program, const GLchar *uniformBlockName);
  • program
    程序对象句柄
  • uniformBlockName
    需要获取索引的统一变量块名称

1.3.7 glGetActiveUniformBlockName和glGetActiveUniformBlockiv

从统一变量块索引,你可以用glGetActiveUniformBlockName(获取块名) 和 glGetActiveUniformBlockiv(获取统一变量块的许多属性)确定活动统一变量块的细节。

void glGetActiveUniformBlockName (GLuint program, 
									GLuint uniformBlockIndex, 
									GLsizei bufSize, 
									GLsizei *length, 
									GLchar *uniformBlockName);

参数说明:

  • program
    程序对象句柄
  • uniformBlockIndex
    需要查询的统一变量块索引
  • bufSize
    名称数组中的字符数
  • length
    如果不为NULL,将写入名称数组中的字符数(减去null终止符)
  • uniformBlockName
    将写入统一变量名称,最大字符数为bufSize个字符,这是一个以null终止的字符串。
void glGetActiveUniformBlockiv (GLuint program, 
								GLuint uniformBlockIndex, 
								GLenum pname, 
								GLint *params);

参数说明:

  • program
    程序对象句柄

  • uniformBlockIndex
    需要查询的统一变量块索引

  • pname
    写入params的统一变量块索引属性。可以是:

    • GL_UNIFORM_BLOCK_BINDING
    • GL_UNIFORM_BLOCK_DATA_SIZE
    • GL_UNIFORM_BLOCK_NAME_LENGTH
    • GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS
    • GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES
    • GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER
    • GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER
  • params
    写入pname指定的结果

1.3.7.1 查询GL_UNIFORM_BLOCK_BINDING

查询GL_UNIFORM_BLOCK_BINDING返回统一变量块的最后一个缓冲区绑定点(如果该块不存在则为0)

1.3.7.2 查询GL_UNIFORM_BLOCK_DATA_SIZE

查询GL_UNIFORM_BLOCK_DATA_SIZE返回保存统一变量块中所有统一变量的最小总缓冲区对象尺寸

1.3.7.3 查询GL_UNIFORM_BLOCK_NAME_LENGTH

查询GL_UNIFORM_BLOCK_NAME_LENGTH 返回统一变量块名称的总长度(包括null终止符)

1.3.7.4 查询GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS

查询GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS返回统一变量块中活动统一变量的数量

1.3.7.5 查询GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES

查询GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES返回统一变量块中活动统一变量索引的列表

1.3.7.6 查询GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER

查询GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 返回一个布尔值,表示统一变量块由程序中的顶点着色器引用。

1.3.7.7 查询GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER

查询GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER 返回一个布尔值,表示统一变量块由程序中片段着色器引用。

1.3.8 glUniformBlockBinding

一旦有了统一变量块索引,就可以调用glUniformBlockBinding,将索引和程序中的统一变量块绑定点关联。

void glUniformBlockBinding (GLuint program, 
							GLuint uniformBlockIndex,
							GLuint uniformBlockBinding);


参数说明:

  • program
    程序对象索引
  • uniformBlockIndex
    统一变量块的索引
  • uniformBlockBinding
    统一变量缓冲区对象绑定点

1.3.9 glBindBufferRange和glBindBufferBase

最后,可以用glBindBufferRange或者glBindBufferBase将统一变量缓冲区对象绑定到GL_UNIFORM_BUFFER目标和程序中的统一变量块绑定点。

void glBindBufferRange (GLenum target, 
						GLuint index, 
						GLuint buffer,
						GLintptr offset, 
						GLsizeiptr size);

void glBindBufferBase (GLenum target, 
					   GLuint index, 
					   GLuint buffer);

参数说明:

  • target
    必须是GL_UNIFORM_BUFFER 或者 GL_TRANSFORM_FEEDBACK_BUFFER
  • index
    绑定索引
  • buffer
    缓冲区对象句柄
  • offset
    以字节数计算的缓冲区对象起始偏移(仅glBindBufferRange)
  • size
    可以从缓冲区对象读取或者写入缓冲区对象的数据量(以字节数计算,仅glBindBufferRange)

参考链接:

1.4 编程统一变量块时要注意的限制

编程统一变量块时,要注意如下的限制:

  • 顶点或者片段着色器使用的最大活动统一变量块的数量可以分别用带 GL_MAX_VERTEX_UNIFORM_BLOCKSGL_MAX_FRAGMENT_UNIFORM_BLOCKS参数的glGetIntegerv查询。所有实现中最小的支持数量为12。
  • 程序中所有着色器使用的最大总活动统一变量块的数量可以用GL_MAX_COMBINED_UNIFORM_BLOCKS参数的glGetIntegerv查询。所有实现中最小的支持数量为24。
  • 每个统一变量缓冲区的最大可用存储量可以用带 GL_MAX_UNIFORM_BLOCK_SIZE参数的glGetInteger64v查询,返回的大小以字节数表示。所有实现中最小的支持数量为16KB。
  • 如果违反了这些限制,程序将无法链接。


1.5 实例代码

下面例子说明如何使用前面描述的统一变量块LightBlock建立一个统一变量缓冲区对象:

layout(std140) uniform LightBlock

	vec3 lightDirection;
	vec4 lightPosition;
;

	GLuint blockId, bufferedId;
	GLint blockSize;
	GLuint bindingPoint = 1;
	GLfloat lightData[] =
	
	    // lightDirection (padded to vec4 based on std140 rule)
		1.0f, 	0.0f, 	0.0f, 	0.0f,

		// lightPosition
		0.0f,	0.0f,	0.0f,	1.0f
	;

	// Retrieve the uniform block index
	blockId = glGetUniformBlockIndex(mProgram, "LightBlock");

	// Associate the uniform block index with a binding point
	glUniformBlockBinding(mProgram,blockId,bindingPoint);

    // Get the size of lightData; alternatively,
    // we can calculate it using sizeof(lightData) in this example
    glGetActiveUniformBlockiv(mProgram,blockId,GL_UNIFORM_BLOCK_DATA_SIZE,&blockSize);

    // Create and fill a buffer object
    glGenBuffers(1, &bufferedId);
    glBindBuffer(GL_UNIFORM_BUFFER,bufferedId);
    glBufferData(GL_UNIFORM_BUFFER, blockSize, lightData, GL_DYNAMIC_DRAW);

    // Bind the buffer object to the uniform block binding point
    glBindBufferBase(GL_UNIFORM_BUFFER, bindingPoint, buffer);

1.7 获取和设置属性

除了查询程序对象上的统一变量信息之外,还需要使用程序对象设置顶点属性。

对顶点属性的查询和统一变量的查询非常相似。你可以使用GL_ACTIVE_ATTRIBUTES查询找到活动属性列表,可以用glGetActiveAttrib找到某个属性的特性。 然后,有一组例程可用于设置顶点数组,以加载顶点属性值。

顶点属性的设置确实需要更好地理解图元和顶点着色器,但是,我们现在不做这么深入的研究,而是在后面的博客再专门介绍顶点属性和顶点数组。

以上是关于我的OpenGL学习进阶之旅统一变量和属性的主要内容,如果未能解决你的问题,请参考以下文章

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (下)

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (下)

我的OpenGL学习进阶之旅顶点属性顶点数组

我的OpenGL学习进阶之旅顶点属性顶点数组

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (上)

我的OpenGL学习进阶之旅OpenGL ES 着色语言 (上)