原帖地址:http://ogldev.atspace.co.uk/www/tutorial05/tutorial05.html
在这篇教程中,我们将接触到一种新的shader变量uniform variables,这种变量和属性变量的区别:属性变量是指每个顶点shader调用时,都会根据属性的位置从顶点缓冲中装入该顶点的相应属性值,而uniform变量,则对每个draw调用保持不变,这意味着你在draw调用前装入该变量,然后draw中每个顶点shader执行时,都能访问该变量,而且该变量值会保持不变。uniform变量常用来存储一些draw执行时候的常量数据,比如光照参数、变化矩阵、纹理对象句柄等等。注:uniform变量类似于D3D11中的const buffer中的变量。
在这篇教程中,我们会使渲染的物体在屏幕上运动,实现动画的效果。主要通过绑定一个uniform变量以及一个idle回调函数来实现,uniform变量的值在每帧中都会变化。
GLUT不会重复调用我们的渲染函数,只有发生一些特殊事件的时候,才会执行渲染操作,比如窗口最大化、最小化,当前窗口被别的窗口遮挡等等,如果程序执行后,我们不做任何变化,则渲染函数只会执行一次,我们可以通过在渲染函数中增加一个print函数来验证它,在最大化、最小化窗口时候,该函数会在控制台窗口会打印相关信息。如果实现静态物体的渲染,这种方法当然可以,但是在本教程中,我们要实现动画,需要重复调用渲染函数,该怎么实现呢?我们可以注册一个idle回调函数,把渲染函数放在该函数中,或者直接把渲染函数注册成idle函数,在GLUT不接受windows系统事件时,idle函数会重复执行,这样结合idle函数和变化的uniform变量,我们就可以实现动画的效果。
主要代码:
glutIdleFunc(RenderSceneCB);
通过上面的代码,我们把渲染函数注册成idle函数,需要注意的是在渲染函数的末尾要加上glutPostRedisplay()函数调用,否则的话idle函数会反复执行,但渲染函数却没有,glutPostRedisplay()会重绘当前显示窗口,并保证下一次glut消息循环中,渲染函数会被调用。
gScaleLocation = glGetUniformLocation(ShaderProgram, "gScale");
assert(gScaleLocation != 0xFFFFFFFF);
通过查询shader程序对象,我们能够得到uniform变量的位置,该位置和该uniform变量在shader代码中的位置一致。通常情况下,我们不能直接访问和更新uniform变量。在glsl compiler编译shader代码时候,它会给每个uniform变量分配一个索引,在shader内部访问uniform变量都是通过这个索引来实现的,应用程序则是通过glGetUniformLocation函数来访问相应的uniform变量,函数的参数为shader程序句柄以及uniform变量名字。函数调用成功,则会返回索引值,失败的话返回-1,对返回值进行错误检测也非常重要,这样可以保证shader值被正确更新。在两种情况下,该函数会调用失败,第一种就是变量名字拼写错误,或者就是compiler进行的优化操作,这时也不能得到正确的索引值。
static float Scale = 0.0f;
Scale += 0.001f;
glUniform1f(gScaleLocation, sinf(Scale));
我们定义一个缩放因子变量,每次渲染函数调用时候,该变量都增加0.001。该变量的sin值通过函数glUniform1f传输到shader中去。使用sin函数值可以保证Scale范围在[-1,1]之间,注意sinf的参数是以弧度为单位。opengl提供了glUniform{1234}{if}函数的多个版本,分别用来装入1维、2维、3维、4维向量,i表示变量是整数,f表示变量是浮点,该函数也有向量和矩阵的版本,可以用来设置向量或矩阵类型的uniform变量。该函数的第一个参数是我们用glGetUniformLocation函数得到的uniform变量索引值。
下面我们看下VS中的代码变化(注意:FS和上篇教程中一致,没有变化)。
uniform float gScale;
首先在shader中声明该uniform类型变量。
gl_Position = vec4(gScale * Position.x, gScale * Position.y, Position.z, 1.0);
我们用uniform变量gScale,改变顶点的x、y值,这样每帧中x、y坐标值都不同,从而实现动画的效果。
程序运行后效果如下: