关键词:
Uniform Variables
文章目录
上一篇:属性变量 下一篇:统一块
一、说明
统一变量充当常量,至少在绘制调用期间如此。应用程序将这些变量提供给图形管道,并且它们可以在管道的所有阶段中访问,即任何着色器都可以访问每个统一变量,只要它声明了该变量。就着色器而言,这些变量是只读的。
二、关于uniform
2.1 变量介绍
在本教程中,我们遇到一种新型着色器变量 - 统一变量。属性变量和统一变量之间的区别在于,属性变量包含特定于顶点的数据,因此每次着色器调用都会从顶点缓冲区重新加载新值,而统一变量的值在整个绘制调用中保持不变。这意味着您在进行绘制调用之前加载该值,然后您可以在每次调用顶点着色器时访问相同的值。统一变量对于存储诸如光照参数(光照位置和方向等)、变换矩阵、纹理对象句柄等数据很有用。
在本教程中,我们终于在屏幕上看到了一些移动的东西。我们使用统一变量(其值每帧都会更改)和 GLUT 提供的空闲回调函数的组合来完成此操作。关键是 GLUT 不会重复调用我们的渲染回调函数 - 除非必须这样做。 GLUT 必须在事件发生后调用渲染回调,例如最小化和最大化窗口或被另一个窗口发现它。如果我们在启动应用程序后不更改窗口布局中的任何内容,则渲染回调只会被调用一次。您可以通过在渲染函数中添加 printf 调用来亲自查看。您只会看到一次输出,如果最小化然后最大化窗口,您将再次看到它。对于之前的教程来说,在 GLUT 中仅注册渲染回调就可以了,但在这里我们想要重复更改变量的值。我们通过注册一个空闲函数回调来做到这一点。当没有从窗口系统接收到任何事件时,空闲函数由 GLUT 调用。您可以为此回调指定一个专用函数,您可以在其中执行任何簿记操作,例如时间更新,或者简单地将渲染回调函数注册为空闲回调。在本教程中,我们将执行后面的操作并更新渲染函数内的变量。要点是:
- 如何定义
- 和CSGL程序关联
- 在着色器定义接收
- 如何刷新
2.2 变量定义
统一变量可以分组为块,也可以单独定义。在这里,我们将开始查看各个定义,下一节将查看统一块。
在着色器内部,使用关键字定义统一uniform。例如,要定义一个vec4命名,myVar我们可以在着色器中编写
uniform vec4 myVar;
Uniform 也可以在着色器内部初始化,例如要初始化 vec4,我们可以按如下方式进行:
uniform vec4 myVar = {0.5, 0.2, 0.7, 1.0};
2.3 如何查询Uniform变量访问位置
着色器内初始化很棒,因为它使我们不必设置制服。我们可以在着色器初始化中设置默认值,并且仅当我们需要统一变量的不同值时才需要从应用程序设置一个值。
在应用程序内部,要设置统一变量,我们必须首先获取其位置,可以使用以下函数检索该位置:
GLint glGetUniformLocation(GLuint program, const char *name);
参数:
活动统一变量是在着色器内部实际使用的变量,而不仅仅是声明的变量。编译器可以随意丢弃代码中未使用的变量。因此,即使在着色器中声明了uniform,只要不使用它,其报告的位置就可以为-1。
2.4 给Uniform变量赋值
假设程序已链接并且变量在代码中有效使用,则返回值是变量的位置,稍后可用于设置其值。为了设置值,OpenGL 提供了一个大的函数系列,涵盖所有数据类型,以及多种设置值的方法。例如,考虑myVar上面定义的变量。要设置vec4, 并假设p为链接程序的句柄,我们可以编写:
1)以散列数方式对vec4f变量赋值:
GLint myLoc = glGetUniformLocation(p, "myVar");
glUniform4f( myLoc, 1.0f, 2.0f, 2.5f, 2.7f);
上面对一个vec4f的变量赋值。观点是将vec4f看成4个独立数值。
如果用数组序列的观点,我们可以写…
2)以数组方式对vec4f变量赋值:
float myFloats[4] = {1.0f, 2.0f, 2.5f, 2.7f};
GLint myLoc = glGetUniformLocation(p, "myVar");
glUniform4fv( myLoc, 1, myFloats);
3)如果GLSL程序也很多,对于多个程序中某个进行Uniform操作,用
glProgramUniform4f函数 取代上文的glUniform4f函数。
这两个函数的签名如下:
void glProgramUniform4f(GLuint program, GLint location, GLfloat f1, …, GLfloat f4);
上文参数对应:
void glProgramUniform4fv(GLuint 程序、GLint 位置、GLsizei 计数、const GLfloat *值);
参数:
GLSL 中的所有基本数据类型和预定义矩阵都有类似的函数。
三、传递数组
现在考虑一下,在我们的着色器中,我们有一个数组类型的变量,声明如下:
uniform vec4 color[2];
从应用程序的角度来看,我们可以将变量视为数组并一次性设置所有组件。对于大多数基本类型(而不是结构)来说都是如此,请参见下文。
float myColors[8] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f};
GLint myLoc = glGetUniformLocation(p, "color");
glProgramUniform4fv(p, myLoc, 2, myColors);
请注意,我们将 的第三个参数设置glProgramUniform4fv为 2,因为我们要设置数组的两个元素,即两个vec4。应用程序数组myColors根据需要有八个浮点数。
另一种方法是单独设置每个元素。例如,假设我们只想设置 GLSL 数组的第二个元素,即color[1]。然后我们可以写:
GLfloat aColor[4] = {0.0f, 1.0f, 1.0f, 0.0f};
myLoc = glGetUniformLocation(p, "color[1]");
glProgramUniform4fv(p, myLoc, 1, aColor);
每个基本数据类型都有一个 glProgramUniform 函数,它们有两种形式,如 vec4 情况所示:
[stextbox]
void glProgramUniform4f(GLuint program, GLint location, GLfloat v1, …, GLfloat v4);
void glProgramUniform4fv(GLuint 程序、GLint 位置、GLsizei 计数、GLfloat *v);
参数:
程序:链接的程序句柄
对于矩阵,此函数有一个特殊版本,请参阅下面的 mat4 示例:
void glProgramUniformMatrix4fv(GLuint program, GLint location, GLsizei count, GLboolean transpose, GLfloat *v);
参数:
四、传递结构
在 GLSL 中,我们可以以类似于 C 的方式定义结构体。例如,以下代码片段声明一个具有两个 vec4 的结构体,并定义该类型的统一变量。
struct Colors{
vec4 Color1;
vec4 Color2;
};
为了在着色器中使用结构体的字段,我们使用与 C 中相同的表示法。例如,以下 main 函数假定上面的声明:
void main()
{
vec 4 aColor = myColors.Color1 + myColors.Color2;
...
}
为了在应用程序中设置这些变量,我们还使用与 C 中相同的表示法。我们无法将结构作为一个整体进行设置,因为它无法获取它的位置。我们必须逐个字段地设置它。以下示例显示了如何操作:
float myFloats[8] = {0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0};
GLint myLoc = glGetUniformLocation(p, "myColors.Color1");
glProgramUniform4fv(p, myLoc, 1, myFloats);
myLoc = glGetUniformLocation(p, "myColors.Color2");
glProgramUniform4fv(p, myLoc, 1, &(myFloats[4]));
使用结构数组很简单。考虑以下统一声明:
uniform Colors myColors[2];
在着色器中我们可以编写以下内容:
void main()
{
vec 4 aColor = myColors[0].Color1 + myColors[1].Color2;
...
}
在我们的应用程序中,我们以相同的方式进行:
GLint myLoc;
float myFloats[8] = {0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0};
float myOtherFloats[8[ = {1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0};
myLoc = glGetUniformLocation(p, "myColors[0].Color1");
glProgramUniform4fv(p, myLoc, 1, myFloats);
myLoc = glGetUniformLocation(p, "myColors[0].Color2");
glProgramUniform4fv(p, myLoc, 1, &(myFloats[4]));
myLoc = glGetUniformLocation(p, "myColors[1].Color1");
glProgramUniform4fv(p, myLoc, 1, myOtherFloats);
myLoc = glGetUniformLocation(p, "myColors[1].Color2");
glProgramUniform4fv(p, myLoc, 1, &(myOtherFloats[4]));
五、范例
给出两个点,A和B,在着色器中画出这两个点的线段。
5.1 Uniform的使用过程
分三个步骤
- 定义
- 寻址
- 赋值
5.2 定义Uniform变量
着色器程序中,如下定义是合法的:
struct TheStruct{
vec3 first;
vec4 second;
mat4x3 third;
};
uniform vec3 oneUniform;
uniform TheStruct aUniformOfArrayType;
uniform mat4 matrixArrayUniform[25];
uniform TheStruct uniformArrayOfStructs[10];
禁忌:
- 定义后变量不能在SLGL中赋值
- 不能作为输出变量输出。
5.3 获取Uniform变量指针
在OpenGL客户端获得Uniform的地址,通过glGetUniformLocation函数获取uniform的指针,输入变量:
GLint glGetUniformLocation( GLuint program, const GLchar *name);
参数:
- program :指定要查询的程序对象。
- name:指向一个以 null 结尾的字符串,其中包含要查询其位置的统一变量的名称。
例如:
local = glGetUniformLocation(shader, 'myVar'.encode('ascii'))
5.4 glGetUniformLocation细节
glGetUniformLocation 返回一个整数,表示程序对象中特定统一变量的位置。 name 必须是一个以 null 结尾的字符串,不包含空格。 name 必须是程序中的活动统一变量名称,它不是结构体、结构体数组或者向量或矩阵的子组件。如果名称不对应于程序中的活动统一变量,如果名称以保留前缀“gl_”开头,或者如果名称与原子计数器或命名统一块关联,则此函数返回-1。
作为结构体或结构体数组的统一变量可以通过为结构体中的每个字段调用 glGetUniformLocation 来查询。数组元素运算符“[]”和结构体字段运算符“.”可以在名称中使用,以便选择数组中的元素或结构中的字段。使用这些运算符的结果不允许是另一个结构、结构数组或向量或矩阵的子组件。除非名称的最后部分表示统一变量数组,否则可以使用数组的名称或使用附加“[0]”的名称来检索数组的第一个元素的位置。
在程序对象成功链接之前,分配给统一变量的实际位置是未知的。链接发生后,可以使用命令 glGetUniformLocation 来获取统一变量的位置。然后可以将该位置值传递给 glUniform 以设置统一变量的值,或传递给 glGetUniform 以查询统一变量的当前值。程序对象成功链接后,统一变量的索引值保持固定,直到下一个链接命令发生。如果链接成功,则只能在链接后查询统一变量位置和值。
5.5 给Uniform变量赋值
使用glUniformXX函数改变Uniform变量值。“XX”随变量类型不同而不同。
void glUniform4i( GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
更改Uniform变量值:
glUniform4f
例如:物体在屏幕上平移。可以得到随时间动态的效果。
while not glfw.window_should_close(window):
ct = glfw.get_time()
glUniform4f(local, sin(ct) , cos(ct) ,0, 1.0)