2.6 着色器子程序
高级技巧
GLSL允许我们在着色器中定义函数,而这些函数的调用过程总是静态的。如果需要动态地选择调用不同的函数,那么可以创建两个不同的着色器,或者使用if语句来进行运行时的选择,如例2.5所示。
例2.5 静态着色器的控制流程
着色器子程序在概念上类似于C语言中的函数指针,它可以实现动态子程序选择过程。在着色器当中,可以预先声明一个可用子程序的集合,然后动态地指定子程序的类型。然后,通过设置一个子程序的uniform变量,从预设的子程序中选择一个并加以执行。
2.6.1 GLSL的子程序设置
当我们需要在着色器中进行子程序的选择时,通常需要三个步骤来设置一个子程序池。
1)通过关键字subroutine来定义子程序的类型:
其中returnType可以是任何类型的函数返回值,而subroutineType是一个合法的子程序名称。由于它相当于函数的原型,因此我们只需要给出参数的类型,不一定给出参数的名称(我们可以将它设想为C语言中的typedef,而subroutineType就是新定义的类型)。
2)使用刚才定义的subroutineType,通过subroutine关键字来定义这个子程序集合的内容,以便稍后进行动态的选择。某个子程序函数的原型定义类似于下面的形式:
3)最后,指定一个子程序uniform变量,其中保存了相当于“函数指针”的子程序选择信息,这可以在应用程序中更改:
将上面的三个步骤整合在一起,我们可以通过例2.6来实现环境光照和漫反射光照方式的动态选择。
例2.6 声明一个子程序集合
子程序并不一定只属于一个子程序类型(例如,例2.6中的LightFunc)。如果定义了多种类型的子程序,那么我们可以设置一个子程序属于多个类型,方法是在定义子函数时把类型添加到列表中,如下所示:
在上面的例子中,func_1可以使用Func_1和Func_2,这是因为两个子程序都指定了Type_1。但是,func_2就只能使用Func_1,而func_3只能使用Func_2。
2.6.2 选择着色器子程序
如果我们已经在着色器中定义了所有子程序类型和函数,那么只需要在链接后的着色器程序中查询一些数值,然后使用这些数值来选择合适的函数即可。
在之前所示的步骤3当中,我们声明了一个子程序的uniform变量,之后就可以获取它的位置并设置它的值。与其他的uniform变量不同的是,子程序的uniform需要使用glGetSubroutineUniformLocation()来获取自身的位置。
GLint glGetSubroutineUniformLocation(GLuint program, GLenum shadertype, const char* name);
返回名为name的子程序uniform的位置,相应的着色阶段通过shadertype来指定。name是一个以NULL结尾的字符串,而shadertype的值必须是GL_VERTEX_SHADER、GL_TESS_CONTROL_SHADER、GL_TESS_EVALUATION_SHADER、GL_GEOMETRY_SHADER或者GL_FRAGMENT_SHADER中的一个。
如果name不是一个激活的子程序uniform,则返回–1。如果program不是一个可用的着色器程序,那么会生成一个GL_INVALID_OPERATION错误。
当取得了子程序uniform数值之后,我们需要判断某个子程序在着色器中的索引号。这一步可以通过调用函数glGetSubroutineIndex()来完成。
GLuint glGetSubroutineIndex(GLuint program, GLenum shadertype, const char* name);
从程序program中返回name所对应的着色器函数的索引,相应的着色阶段通过shadertype来指定。name是一个以NULL结尾的字符串,而shadertype的值必须是GL_VERTEX_SHADER、GL_TESS_CONTROL_SHADER、GL_TESS_EVALUATION_SHADER、GL_GEOMETRY_SHADER或者GL_FRAGMENT_SHADER中的一个。
如果name不是shadertype着色器的一个活动子程序,那么会返回GGL_INVALID_INDEX。
当我们得到了子程序的索引以及uniform的位置之后,可以使用glUniformSubroutinesuiv()来指定在着色器中执行哪一个子程序函数。某个着色阶段中,所有的子程序uniform都必须先经过初始化的过程。
GLuint glUniformSubroutinesuiv(GLenum shadertype, GLsizei count, const GLuint* indices);
设置所有count个着色器子程序uniform使用indices数组中的值,相应的着色阶段通过shadertype来指定。shadertype的值必须是GL_VERTEX_SHADER、GL_TESS_CONTROL_SHADER、GL_TESS_EVALUATION_SHADER、GL_GEOMETRY_SHADER或者GL_FRAGMENT_SHADER中的一个。第i个子程序uniform对应于indices[i]的值。
如果count不等于当前绑定程序的着色阶段shadertype的GL_ACTIVE_SUBROUTINE_UNIFORM_LOCATIONS值,那么会产生一个GL_INVALID_VALUE错误。indices中的所有值都必须小于GL_ACTIVE_SUBROUTINES,否则会产生一个GL_INVALID_VALUE错误。
将上面的步骤组合在一起,可以得到下面的代码段,它演示了例2.6中的顶点着色器的调用过程。
调用glUseProgram()时会重新设置所有子程序uniform的值,具体的顺序与硬件实现相关。