1. 着色器编译
1.1 着色器编译的命令序列
1.2 加载着色器步骤
对于每个着色器对象 :
- 创建一个着色器对象;
- 将着色器源代码编译为对象;
- 验证着色器的编译是否成功。
然后需要将多个着色器对象链接为一个着色器程序,包括 :
- 创建一个着色器程序;
- 将着色器对象关联到着色器程序;
- 链接着色器程序;
- 判断着色器的链接过程是否成功完成;
- 使用着色器来处理顶点和片元。
2. 着色器子程序
2.1 函数的动态调用
如果需要动态地选择调用不同的函数:
- 可以创建两个不同的着色器;
- 或者使用
if
语句来进行运行时的选择,例如:
#version 330 core
void func_1() { ... }
void func_2() { ... }
uniform int func;
void
main()
(
if (func == 1)
func_1();
else
func_2();
)
- 着色器子程序在概念上类似于C语言中的函数指针,它可以实现动态子程序选择过程。
- 在着色器当中,可以预先声明一个可用子程序的集合,然后动态地指定子程序的类型。
- 然后,通过设置一个子程序的
uniform
变量,从预设的子程序中选择一个并加以执行。
2.2 GLSL 的子程序设置
当我们需要在着色器中进行子程序的选择时,通常需要三个步骤来设置一个子程序池。
2.2.1 声明子程序
通过关键字 subroutine 来定义子程序的类型:
subroutine returnType subroutineType(type param, ...);
returnType
可以是任何类型的函数返回值;subroutineType
是一个合法的子程序名称;- 由于它相当于函数的原型,因此我们只需要给出参数的类型,不一定给出参数的名称(可以将它设想为C语言中的
typedef
,而subroutineType
就是新定义的类型)。
2.2.2 定义子程序
使用刚才定义的 subroutineType ,通过 subroutine 关键字来定义这个子程序集合的内容,以便稍后进行动态的选择。某个子程序函数的原型定义类似于下面的形式:
subroutine (subroutineType) returnType functionName(...);
2.2.3 指定 uniform 变量
指定一个子程序 uniform 变量,其中保存了相当于 “函数指针” 的子程序选择信息,这可以在应用程序中更改:
subroutine uniform subroutineType variableName;
2.2.4 示例代码
// 声明子程序
subroutine vec4 LightFunc(vec3);
// 定义子程序:环境光照
subroutine (LightFunc) vec4 ambient(vec3 n)
{
return Materials.ambient;
}
// 定义子程序:漫反射光照
subroutine (LightFunc) vec4 diffuse(vec3 n)
{
return Materials.diffuse * max(dot(normalize(n), LightVec.xyz), 0.0);
}
// 指定一个 uniform 变量 materialShader 为子程序 LightFunc
subroutine uniform LightFunc materialShader;
2.2.5 多类型子程序
子程序并不一定只属于一个子程序类型,如果定义了多种类型的子程序,那么我们可以设置一个子程序属于多个类型,方法是在定义子函数时把类型添加到列表中:
subroutine void Type_1();
subroutine void Type_2();
subroutine void Type_3();
subroutine (Type_1, Type_2) Func_1 ();
subroutine (Type_1, Type_3) Func_2 ();
subroutine uniform Type_1 myFunc_1;
subroutine uniform Type_2 myFunc_2;
subroutine uniform Type_3 myFunc_3;
Note :
myFunc_1
可以使用Func_1()
和Func_2()
,因为这两个子程序都指定了Type_1
;myFunc_2
只能使用Func_1()
;myFunc_3
只能使用Func_2()
。
GLint materialShaderLoc;
GLuint ambientIndex;
GLuint diffuseIndex;
glUseProgram(program);
// 获取子程序的 uniform 的自身位置
materialShaderLoc = glGetSubroutineUniformLocation ( program, GL_VERTEX_SHADER, "materialShader");
if (materialShaderLoc < 0) {
// 错误: materialShader 不是着色器中启用的子程序 uniform
// uniform in the shader.
}
// 获取某个子程序在着色器中的索引号
// 相应的着色阶段用 shaderType 指定
ambientIndex = glGetSubroutinelndex(program, GL_VERTEX_SHADER, "ambient" );
diffuseIndex = glGetSubroutinelndex(program, GL_VERTEX_SHADER, "diffuse" );
if (ambientlndex == GL_INVALID_INDEX || diffuseindex == GL_INVALID_INDEX) {
// 错误:指定的子程序在 GL_VERTEX_SHADER 阶段当前绑定的程序中没有启用
}
else {
// 获取了子程序的索引以及 uniform 的位置之后
// 指定在着色器中执行哪一个子程序函数
// 某个着色阶段中,所有的子程序 uniform 都必须先经过初始化的过程
// 获取着色器子程序的个数
GLsizei n;
glGetIntegerv(GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS, &n);
// 维护一个 indices 数组
// 将子程序的 uniform 变量 materialShader 对应位置设置为环境光照子程序索引
// 例如 uniform 块中 materialShader 是第二个变量,则 indice[1] = ambientIndex;
GLuint * indices = new GLuint[n];
indices[materialShaderLoc] = ambientIndex;
// 设置所有 n 个着色器子程序 uniform 使用 indices 数组中的值
// 着色阶段指定为顶点着色器
glUniformSubroutinesuiv(GL_VERTEX_SHADER, n, indices);
delete[] indices;
}
3. 独立的着色器对象
当需要使用多个片元着色器来处理来自同一个顶点着色器的几何体变换数据时,可以使用 独立的着色对象 ,将不同程序的着色阶段(例如顶点着色)合并到同一个程序管线中。
// TODO