第二章 着色器基础
做色漆语言概述
变量的声明
GLSL中的基本数据类型:float double int uint bool
GLSL中的不透明类型包括采样器(sampler)图像(image)以及原子计数器(atomic counter)
变量的初始化
所有变量必须在声明的同时进行初始化。
构造函数
GLSL中支持的隐式类型
所需类型 | 可以从这些类型隐式转换 |
---|---|
uint | int |
float | int uint |
double | int uint float |
除此之外的其他数值转换都需要提供显示的转换构造函数,例如
float f = 10.0
int ten = int(f);
聚合类型
矩阵类型需要给出两个维度的信息,例如mat4x3,其中第一个值标识列数,第二个值表示行数
如果矩阵的构造函数只穿入了一个值,表示该矩阵的对角线元素全部为这个值。
m
=
m
a
t
3
(
4.0
)
=
(
4.0
0.0
0.0
0.0
4.0
0.0
0.0
0.0
4.0
)
m = mat3(4.0) = \begin{pmatrix} 4.0 \ 0.0 \ 0.0 \\ 0.0 \ 4.0 \ 0.0 \\ 0.0 \ 0.0 \ 4.0\\ \end{pmatrix}
m=mat3(4.0)=⎝⎛4.0 0.0 0.00.0 4.0 0.00.0 0.0 4.0⎠⎞
矩阵也可以指定每一个元素的值,矩阵的指定需要遵循列住序的原则,也就是数据首先填充列,在填充行,例如:
mat M = mat3(1.0, 2.0, 3.0 ,4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
或者是
vec3 col1 = vec3(1.0, 2.0, 3.0);
vec3 col2 = vec3(4.0, 5.0, 6.0);
vec3 col3 = vec3(7.0, 8.0, 9.0);
mat3 M = (col1, col2, col3);
都表示同一个矩阵
(
1.0
4.0
7.0
2.0
5.0
8.0
3.0
6.0
9.0
)
\begin{pmatrix} 1.0 \ 4.0 \ 7.0 \\ 2.0 \ 5.0 \ 8.0 \\ 3.0 \ 6.0 \ 9.0 \\ \end{pmatrix}
⎝⎛1.0 4.0 7.02.0 5.0 8.03.0 6.0 9.0⎠⎞
访问向量和矩阵中的元素
分量访问符 | 符号描述 |
---|---|
(x, y, z, w) | 与位置相关的分量 |
(r, g, b, a) | 与颜色相关的分量 |
(s, t, p, q) | 与纹理坐标相关的分量 |
数组
数组属于GLSL的first-class类型,所以需要构造函数
float coeff[3] = float[3](2.3, 3.1, 42.0);
其中构造函数的维度数值可以不填。
数组的长度可以通过length()方法取得。
存储限制符
类型修饰符 | 描述 |
---|---|
const | 讲一个变量定义为只读形式,如果他初始化时用的是一个编译时常量,那么他本身也会成为编译时常量 |
in | 设置这个变量为着色器阶段的输入变量 |
out | 设置这个变量为着色器阶段的输出变量 |
uniform | 设置这个变量为用户应用程序传递给着色器的数据,他对于给定的图元而言是一个常量 |
buffer | 设置应用程序共享的一块可读写的内存。这块内存也作为着色器中的存储缓存(storage buffer)使用 |
shared | 设置变量是本地工作组(local work group)中共享的。只能用于计算着色器中 |
获取uniform索引
uniform变量在所有可用着色器阶段是共享的,只能在应用程序中设置,着色器无法写入到uniform变量,也不能更改他的值。
要设置uniform的只需要首先获取uniform的索引:
GLint glGetUniformLocation(GLuint program, const char* name);
获取着色器程序中uniform变量name对应的索引值,如果name没有在着色器中查找到或者是一个内部变量名称,则返回-1。
除非重新链接着色器程序(调用glLinkProgram),否则对于同一个着色器变量,该返回值是不变的。
设置uniform值
void glUniform{1234}{fdi ui}(GLuint location, TYPE value);
void glUniform{1234}{fdi ui}v(GLuint location, GLsizei count, const TYPE* values);
void glUniformMatrix{234}{fd}v(GLuint location, GLsizei count, GLboolean tranpose, const GLfloat * values);
void glUniformMatrix{2x3,2x4,3x2,3x4,4x2,4x3}{fd}v(GLuint location, GLsizei count, GLboolean transpose, const GLfloat* value);
{1234}指定变量需要几个{fdi ui}类型的值,例如GLSL中vec3需要3个float变量。注意:如果一个uniform在GLSL中定义但是未使用,编译器会默认移除这个变量,如果在OpenGL端设置它就会出现错误。
流控制语句
可以使用break, continue, return, discard来终止相关层级代码的进行。
其中discard只能用于片元着色器。
函数参数限制符
包括四种限制符:in(默认),const in,out,inout
计算不变性
invariant限制符:可以确保如果两个着色器的输出变量使用了相同的表达式,且表达式中的变量也是相同值,那么计算产生的结果是相同的。对于着色器的自定义输出以及内置输出都可以使用invariant修饰:
invariant gl_position;
invariant centroid out vec3 Color;
如果需要将所有输出变量全部指定为invariance,可以使用预编译命令:
#pragma STDGL invariant(all)
设置invariant会影响性能。
precise限制符:可以设置任何计算中的变量或者是函数的返回值。通常使用在细分着色器中,防止几何体形状裂缝。如果要保证某个表达式产生的结果是一致的,即使表达式中的数据发生了变化(但是在数学上不影响结果)也是如此。
Location = a * b + c * d
如果a与b,c与d之间互换数据理论上Location的结果不发生变化,那么Location的结果就应该是相同的。
priceise限制符可以修饰内置变量用户变量或者是函数返回值:
precise gl_Position;
precise out vec3 Location;
precise vec3 subdivide(vec3 p1, vec3 p2){ ... }
预编译指令
编译器优化:#pragma optimize(on/off)
编译器调试选项:#pragma debug(on/off)
着色器扩展处理
#extension extension_name : <directive>
extension_name与glGetString(GL_EXTENSIONS)获取到的相同,如果要使用所有扩展可以使用:
#extension all : <directive>
directive可以使用的值:
命令 | 描述 |
---|---|
require | 如果无法支持给定的扩展功能,或者是被设置为all,则提示错误 |
enable | 如果无法支持给定的扩展功能,则给出警告;如果被设置为all,则提示错误 |
warn | 如果无法支持给定的扩展功能,或者在编译过程中使用了任何扩展,则给出警告 |
diable | 禁止给定的扩展(即强制编译器不提供对扩展功能的支持),或者如果设置为all则禁止所有的扩展支持,之后的代码如果涉及到这个扩展,提示警告或者错误 |
uniform块
uniform块的写法:
uniform b{ // 限定符可以是uniform、in、out或则是buffer
vec4 v1;
bool v2;
}; // 访问时直接使用v1、v2
或者是
uniform b{
vec4 v1;
bool v2;
} name;
b是外部访问时的接口名称,name是着色器代码中访问的变量。
注意:uniform块中只能包含透明类型的变量(不透明类型包括采样器、图像和原子计数器),并且必须在全局作用域中声明。
uniform块的布局控制
布局限制符 | 描述 |
---|---|
shared | 设置uniform块是多个程序之间共享的(默认值) |
packed | 设置uniform块占有最小的内存空间,但是会禁止程序之间共享这个块 |
std140 | 使用标准布局方式来设置uniform块或者是着色器储存的buffer块 |
std430 | 使用标准布局方式来设置buffer块 |
row_major | 使用行主序的方式来储存uniform块中的矩阵 |
column_major | 使用列主序的方式来储存uniform块中的矩阵(默认值) |
多个限制符可以使用逗号分开:
layout (shared, row_major) uniform{...};
如果要对之后的所有uniform块使用相同的限制符,可以使用:
layout (packed, column_major) uniform;
uniform块不能作为uniform变量的命名空间,也就是说多个uniform块中也不能包含同名的uniform变量,访问uniform时,不一定非要使用块的名字。
查找uniform块在着色器中的索引
GLuint glGetUniformBlockIndex(GLuint program, const char* uniformBlockName);
返回名为uniformBlockName的uniform块的索引值。如果不是一个合法uniform块则返回GL_INVALID_INDEX。
关联缓存对象与uniform块
void glBindBufferRange(GLenum target, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
void glBindBufferBase(GLenum target, GLuint index, GLuint buffer);
将缓存对象buffer与索引为index的uniform块关联起来。target可以是GL_UNIFORM_BUFFER或者GL_TRANSFORM_FEEDBACK_BUFFER。
glBindBufferBase相当于将offset设置为0,size设置为缓存对象的大小来调用glBindBufferRange。
建立关联之后就可以通过缓存命令对uniform块内的数据进行初始化。
显示指定uniform块的索引
GLint glUniformBlockBinding(GLuint program, GLuint uniformBlockIndex, GLuint uniformBlockBinding);
需要在glLinkProgram之前调用。显示将uniformBlockIndex绑定到uniformBlockBinding上。也可以在layout中指定uniformBlockBinding的值。
http://www.voidcn.com/article/p-dposszej-kv.html
获取指定名称uniform变量的索引位置
void glGetUniformIndices(GLuint program, GLsizei uniformCount, const char** uniformNames, GLuint* uniformIndices);
in/out块
作为输入输出的块必须是匹配的
着色器的编译
编译流程:
对于每一个着色器对象需要:
- 创建一个着色器对象
- 将着色器源代码编译为对象
- 验证着色器编译是否成功
对于着色器程序:
- 创建一个着色器程序
- 将着色器对象关联到着色器程序
- 链接着色器程序
- 判断链接是否完成
- 使用着色器程序
创建着色器对象
GLuint glCreateShader(GLenum type);
关联着色器代码
void glShaderSource(GLuint shader, GLsizei count, const GLchar** string, const GLint* length);
编译着色器对象代码
void glCompileShader(GLuint shader);
编译结果查询可以使用glGetShaderiv(GL_COMPILE_STATUS)进行确认。如果不为true,可以使用glGetShaderInfoLog获取编译日志。
获取编译日志
void glGetShaderInfoLog(GLuint shader, GLsizei bufSize, GLsizei* length, char* infoLog);
创建着色器程序
GLuint glCreateProgram();
关联着色器对象
void glAttachShader(GLuint program, GLuint shader);
着色器对象可以在任何时候关联到着色器程序,但是他们的功能只有在程序链接之后才能使用。着色器对象可以关联到多个不同的着色器程序上。
解除着色器对象关联
void glDetachShader(GLuint program, GLuint shader);
移除着色器对象的关联,如果着色器对象被标记为删除的对象(调用glDeleteShader()),然后被解除关联则会立即被删除。
链接着色器程序
void glLinkProgram(GLuint program);
通过glGetProgramiv()并使用GL_LINK_STATUS来查看是否链接成功。
获取日志信息
void glGetProgramInfoLog(GLuint program, GLsizei bufSize, GLsizei* length, char* infoLog);
使用着色器程序
void glUseProgram(GLuint program);
如果已经启用了一个程序,而它需要关联新的着色器对象,或者解除之前关联的对象,那么需要对它重新链接。如果链接成功,那么新的程序会直接替代之前启动的程序。如果链接失败,那么当前绑定的着色器程序依然是可用的,不会被替代,知道链接成功或者使用glUseProgram指定了新的程序为止。
删除着色器对象
void glDeleteShader(GLuint shader);
删除着色器对象shader。如果shader已经链接到一个或多个激活的程序上,那么将其标记为可删除,当它不再使用时就会自动删除。
删除着色器程序
void glDeleteProgram(GLuint program);
立即删除一个没有被使用的着色器程序,如果程序正被使用,那么等到它空闲时再删除。
着色器对象是否存在
GLboolean glIsShader(GLuint shader);
着色器程序是否存在
GLboolean glIsProgram(GLuint program);
着色器子程序
设置子程序池
-
通过关键字subroutine来定义子程序的类型:
subroutine returnType subroutineType(type param, ...);
-
定义子程序集合的内容:
subroutine (subroutineType) returnType functionName(...);
-
指定一个子程序uniform变量,保存子程序的选择信息:
subroutine uniform subroutineType variableName;
子程序并不只能属于一个子程序类型,可以为同一子程序函数指定多个子程序类型。
获取子程序uniform位置
GLint glGetSubroutineUniformLocation(GLuint program, GLenum shadertype, const char* name);
如果name不是一个激活的子程序则返回-1, 如果program不是一个可用的着色器程序,则生成一个GL_INVALID_OPERATION错误。
获取子程序的索引号
GLuint glGetSubroutineIndex(GLuint program, GLenum shaderType, const char* name);
如果name不是shaderType的一个活动子程序,返回GL_INVALID_INDEX。
设置当前使用的子程序执行函数
GLuint glUniformSubroutinesuiv(GLenum shaderType, GLsizei count, const GLuint* incides);
使用示例:
GLint matShaderLoc;
GLuint ambientIndex;
GLuint diffIndex;
glUseProgram(program);
matShaderLoc = glGetSubroutineUniformLocation(program, GL_VERTEX_SHADER, "matShader");
if (matShaderLoc < 0){
// error
}
ambientIndex = glGetSubroutineIndex(program, GL_VERTEX_SHADER, "ambient");
diffIndex = glGetSubroutineIndex(program, GL_VERTEX_SHADER, "diffuse");
if (ambientIndex == GL_INVALID_INDEX || diffIndex = GL_INVALID_INDEX)
{
//error
}
else
{
GLsizei n;
glGetIntegerv(GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS, &n);
GLuint *indices = new GLuint[n];
indices[matShaderLoc] = ambientIndex;
glUniformSubroutinesuiv(GL_VERTEX_SHADER, indices);
delete [] indices;
}
独立的着色器对象
使用glUseProgram只能指定一个当前使用的着色器程序,因此不能够在链接之后动态指定不同阶段使用的着色器对象。
第一步 创建用于着色器管线的着色器程序
有两种方式:
-
通过glCreateProgram创建一个着色器程序,之后设定GL_PROGRAM_SEPARABLE为true
glProgramParameter(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
-
使用glCreateShaderProgramv创建独立的着色器程序
GLuint glCreateShaderProgramv(GLenum type, GLsizei count, const char** strings);
注意4.4之前,transform feedback不能够作为pre-link直接设置到shader当中
https://www.khronos.org/opengl/wiki/Shader_Compilation#Separate_programs
第二步 使用独立的着色器程序
通过着色器管线来关联着色器程序,管线对象的创建、绑定、删除函数分别是glGenProgramPipelines(),glBindProgramPipeline(),glDeleteProgramPipeline().
注意:glUseProgram()会覆盖管线对象,所以要保证调用glUseProgram(0)
绑定一个管线对象之后使用glUseProgramStages()将之前标记为独立的程序对象关联到管线上。
void glUseProgramStages(GLuint pipeline, GLbitfield stages, GLuint program);
设置着色器uniform的方式有两种,第一种是利用glActiveShaderProgram()选择一个活动的着色器程序,之后利用glUniform*()进行设置,这种方式不推荐。第二种方式是调用glProgramUniform*()进行设置:
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);
其中program可以不是当前绑定的程序。
示例1:
// Create two separable program objects from a
// single source string respectively (vertSrc and fragSrc)
GLuint vertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertSrc);
GLuint fragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragSrc);
// CHECK FOR ERRORS HERE!.
// Generate a program pipeline and attach the programs to their respective stages
glGenProgramPipelines(1, &pipeline);
glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vertProg);
glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fragProg);
// Query and set any uniforms
GLint colorLoc = glGetUniformLocation(fragProg, "Color");
glProgramUniform4f(fragProg, colorLoc, 1.f, 0.f, 0.f, 1.f);
示例2:
// Create two programs. One with just the vertex shader, and
// one with both geometry and fragment stages.
GLuint vertexProgram = glCreateProgram();
GLuint geomFragProgram = glCreateProgram();
// Declare that programs are separable - this is crucial!
glProgramParameteri(vertexProgram, GL_PROGRAM_SEPARABLE, GL_TRUE);
glProgramParameteri(geomFragProgram, GL_PROGRAM_SEPARABLE, GL_TRUE);
// Generate and compile shader objects, as normal.
GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
GLuint geomShader = glCreateShader(GL_GEOMETRY_SHADER);
GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(vertShader, 1, &vertSrc, NULL);
glShaderSource(geomShader, 1, &geomSrc, NULL);
glShaderSource(fragShader, 1, &fragSrc, NULL);
glCompileShader(vertShader);
glCompileShader(geomShader);
glCompileShader(fragShader);
// Attach the shaders to their respective programs
glAttachShader(vertexProgram, vertShader);
glAttachShader(geomFragProgram, geomShader);
glAttachShader(geomFragProgram, fragShader);
// Perform any pre-linking steps.
glBindAttribLocation(vertexProgram, 0, "Position");
glBindFragDataLocation(geomFragProgram, 0, "FragColor");
// Link the programs
glLinkProgram(vertexProgram);
glLinkProgram(geomFragProgram);
// Detach and delete the shader objects
glDetachShader(vertexProgram, vertShader);
glDeleteShader(vertShader);
glDetachShader(geomFragProgram, geomShader);
glDetachShader(geomFragProgram, fragShader);
glDeleteShader(geomShader);
glDeleteShader(fragShader);
// Generate a program pipeline
glGenProgramPipelines(1, &pipeline);
// Attach the first program to the vertex stage, and the second program
// to the geometry and fragment stages
glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vertexProgram);
glUseProgramStages(pipeline, GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, geomFragProgram);