opengl教程翻译 #5统一变量
背景知识:
在这个课程我们遇见一种新的着色器变量--统一变量。属性变量和统一变量的不同在于,属性变量包含的是具体的顶点,所以每次着色器调用都会从顶点缓冲中重新加载新值,而统一变量则在整个draw call中维持不变。这意味着你在draw call执行之前加载了值,然后可以在顶点着色器的每一次调用中访问相同的值。统一变量在存储这些数据时较常用:光照参数(光照位置和方向等),矩阵变化,纹理对象句柄等等。
在这个课程中我们最终会得到某些在屏幕上移动的物体。为此我们要用一些每帧都改变的统一变量组合,以及GLUT提供的闲置函数会调用。问题是GLUT并不会重复调用我们的渲染回调函数,除非它必须这么做。GLUT在事件发生以后才回调用渲染回调,比如缩小或放大窗口,解开揭开。若我们在登陆应用后不做任何改变窗口布局的事,那回调函数只会被调用一次。你可以自己在渲染函数中加打印。你就会看到只有在缩小或放大窗口时才会再输出一次。在前期的课程中仅在GLUT中注册渲染回调是OK的,不过我们想重复海边变量的值。我们通过注册一个闲置函数回调实现它。这个闲置的函数会在没接收到系统窗口事件时调用。你可以用一个专门的函数做回调,用在一些需要记账的地方,比如时间更新,或者简单地把渲染回调函数注册为闲置函数。在这个课程中我们稍后会做,更新渲染函数内部的变量。
代码实践:
glutIdleFunc(RenderSceneCB);
这里我们把渲染回调注册为一个闲置函数。请注意如果你决定使用一个专用的闲置回调函数那么你需要在它的末尾增加一个glutPostRedisplay() 。否则虽然闲置函数会一遍接一遍地被调用,但渲染函数却不会被调用。glutPostRedisplay() 标记当前视窗为需要被重新显示,则经过GLUT的主循环的下一轮时渲染回调才会被调用。
gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);
在链接到程序后,我们向程序对象申请局部的统一变量。这是另一个例子,说明在应用中,在C/C++执行环境中哪些地方需要被映射成着色器执行环境。你没有任何途径访问着色器中的内容,你也不能直接更新着色器中的变量。当你编译着色器时GLSL编译器给每个统一变量分配一个索引。在编译器内部,访问着色器内部变量的解决方案是通过使用变量的索引。这个索引同样可以在应用程序中获得,通过glGetUniformLocation。你调用这个函数,参数是程序对象句柄和变量名。该函数返回变量索引,如果出错则返回-1。检查错误很重要(如我们上面用assertion检测)否则变量的很多更新不会传递给着色器。有2个主要原因导致函数调用失败。或者是拼写错了变量名,或者是被编译器优化掉了。如果GLSL编译器发现变量在着色器实际上并不会被使用,它就简单地丢掉它了。在那种情况下glGetUniformLocation 将会调用失败。
static float Scale = 0.0f;
Scale += 0.001f;
glUniform1f(gScaleLocation, sinf(Scale));
我们维持了一个很小增幅的静态浮点变量用于每次渲染函数调用(0.001很值得玩味,如果在你的机器上觉得跑起来太慢或太快,尝试改变它)。实际上被传到着色器的值是Scale变量的正弦值。这可是创建了一个在-1.0和1.0之间的完美循环。注意sinf()接受一个弧度而不是一个角度作为它的参数,不过在这我们不去考究。我们只是想得到正弦产生的波浪型变化。借助glUniform1f,sinf()的结果被传到着色器。OpenGL提供了多种样式的函数,通常形式为glUniform{1/2/3/4}{i/f}。你可以用它去加载向量个数为1~4维(基于glUniform后面跟着的数字)、类型为浮点或整型('i'或者'f'后缀)的值。也有用向量地址做参数的版本,以及专为矩阵准备的特殊版本。这个函数的第一个参数是我们用glGetUniformLocation()提取而来的索引的位置。
我们来看一下我们在顶点着色器中所做的改变(片段着色器保持不变)。
uniform float gScale;
在这里我们在着色器中声明了一个统一变量。
gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);
我们将位置向量中的X和Y值乘以应用程序中每帧改变的值。你能解释为什么三角形会在循环中途上下翻转吗?