2.7 独立的着色器对象
高级技巧
在OpenGL 4.1版本之前(不考虑扩展功能),在应用程序中,同一时间只能绑定一个着色器程序。如果你的程序需要使用多个片元着色器来处理来自同一个顶点着色器的几何体变换数据,那么这样会变得很不方便。此时只能将同一个顶点着色器复制多份,并且多次绑定到不同的着色器程序,从而造成了资源的浪费和代码的重复。
独立的着色器对象可以将不同程序的着色阶段(例如顶点着色)合并到同一个程序管线中。
第一步,我们需要创建用于着色器管线的着色器程序。我们可以调用glProgramParameteri()函数并且设置参数为GL_PROGRAM_SEPARABLE,然后再链接着色器程序。这样该程序就被标识为在程序管线中使用。如果想要简化这个过程,还可以直接使用新增的glCreateShaderProgramv()来封装着色器编译过程,并且将程序标识为可共享(如上文所述),然后链接到最终的对象。
将着色器程序集合合并之后,就需要用这个新的着色器管线结构来合并多个程序中的着色阶段。对于OpenGL中的大部分对象来说,都有一个生成-绑定-删除的过程,以及对应可用的函数。着色器管线的创建可以调用glGenProgramPipelines(),即创建一个未使用的程序管线标识符,然后将它传入glBindProgramPipeline(),使得该程序可以自由编辑(例如,添加或者替换着色阶段)和使用。与其他生成的对象相似,程序管线可以通过glDeleteProgramPipelines()来删除。
当绑定了一个程序管线之后,可以调用glUseProgramStages()将之前标记为独立的程序对象关联到管线上,它通过位域的方式来描述该管线处理几何体和着色片元时,给定程序所处的着色阶段。而之前的glUseProgram()只能直接调用一个程序并且替换当前绑定的程序管线。
为了确保管线可以使用,着色器阶段之间的接口—in和out变量—也必须是匹配的。非独立的着色器对象在程序链接时就可以检查这些接口的匹配情况,与之相比,使用独立程序对象的着色器管线只能在绘制–调用过程中进行检查。如果接口没有正确匹配,那么所有的可变变量(out变量)都未定义。
内置的gl_PerVertex块必须重新声明,以便显式地指定固定管线接口中的哪些部分可以使用。如果管线用到了多个程序,那么这一步是必需的。
例如:
这样我们就建立了着色器的输出接口,它将用于后继的管线阶段当中。这里必须使用gl_PerVertex自己的内置成员。如果不同的着色器程序都用到了同一个内置的块接口,那么所有的着色器都必须使用相同的方式重新声明这个内置的块。
因为独立的着色器对象可以有各自独立的程序uniform集合,所以我们可以使用两种方法来设置uniform变量的值。第一种方法是通过glActiveShaderProgram()来选择一个活动的着色器程序,然后调用glUniform()和glUniformMatrix()来设置某个着色器程序的uniform变量的值。另一种方法,也是我们推荐的方法,是调用glProgramUniform()和glProgramUniformMatrix()函数,它们有一个显式的program对象参数,这样可以独立地设置某个程序的uniform变量的值。
void glProgramUniform{1234}{fdi ui}(GLuint program, GLint location, TYPE value);
void glProgramUniform{1234}{fdi ui}v(GLuint program, GLint location, GLsizei count, const TYPE* values);
void glProgramUniformMatrix{234}{fd}v(GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* values);
void glProgramUniformMatrix{2x3,2x4,3x2,3x4,4x2,4x3}{fd}v( GLuint program, GLint location, GLsizei count, GLboolean transpose, const GLfloat* values);
glProgramUniform()和glProgramUniformMatrix()函数的使用与glUniform()和glUniformMatrix()的使用是一样的,唯一的区别是使用一个program参数来设置准备更新uniform变量的着色器程序。这些函数的主要优点是,program可以不是当前绑定的程序(即最后一个使用glUseProgram()指定的着色器程序)。