Shader与OpenGL
现代OpenGL渲染管线严重依赖着色器(shader)来处理传入的数据。可以这么说,如果不使用shader,用OpenGL可以做到的事情只有清除窗口了。从3.1版本开始,固定管线从core模式中去除,因此我们必须使用shader来完成工作。
OpenGL渲染管线可以被划分为几个阶段,每个阶段会把前一阶段的输出作为输入,每个阶段都是有各自的分工。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,方能做到加速计算出图的效果。
下图(转自 Learn OpenGL CN),是一个图形渲染管线的每个阶段的抽象展示。
Shader如此重要,学会编写shader也成为很重要的一项技能。我们首先谈一谈编写shader需要的语言。
GLSL与OpenGL
GLSL(OpenGL Shader Language)释义OpenGL着色器语言,它是类C的语言,如果有C或C++语言基础的小伙伴看GLSL代码应该不会陌生,每一阶段的shader都是从main函数开始执行的。大致结构如下代码所示 。类C,这里的 // 是注释符号,多行注释符合同是——/* 和 */ 。此外,着色器的每一行结尾必须有一个分号。
#version 330 core
void main()
{
//执行代码
}
GLSL语法
变量类型
GLSL语言是一种强类型语言,这就意味着所有变量在使用之前必须先声明,并且给出变量的类型。变量名称的命名规范和C语言相同。表中给出GLSL基本的数据类型。
类型 | 描述 |
float | IEEE32位浮点值 |
double | IEEE64位浮点值 |
int | 有符号32位整数 |
uint | 无符号32位整数 |
bool | 布尔值 |
变量作用域类C++,初始化如下
int i, number = 50;
float fMax, fMin = -10.2;
bool bResult = true;
double dValue = 3.1415926
聚合类型:GLSL的基本类型可以进行合并,从而参与转换的运算。
bvec2 | 包含2个布尔成分的向量 |
bvec3 | 包含3个布尔成分的向量 |
bvec4 | 包含4个布尔成分的向量 |
ivec2 | 包含2个整型成分的向量 |
ivec3 | 包含3个整型成分的向量 |
ivec4 | 包含4个整型成分的向量 |
mat2 或者 mat2x2 | 2×2的浮点数矩阵类型 |
mat3或者mat3x3 | 3×3的浮点数矩阵类型 |
mat4x4 | 4×4的浮点矩阵 |
mat2x3 | 2列3行的浮点矩阵(OpenGL的矩阵是列主顺序的) |
mat2x4 | 2列4行的浮点矩阵 |
mat3x2 | 3列2行的浮点矩阵 |
mat3x4 | 3列4行的浮点矩阵 |
mat4x2 | 4列2行的浮点矩阵 |
mat4x3 | 4列3行的浮点矩阵 |
表格中mat2x3,第一个值2表示列数,第二值表示行数(体现列优先)。初始化
vec3 vecNormal = vec3(0.0, 2.0, 3.0);
类型之间等价转化和拷贝构造
ivec3 = ivec3(vecNormal);
向量的截断和加长
vec4 Color;
vec3 RGB = vec3(Color);
vec4 White = vec4(RGB , 1.0);
访问和赋值向量中的元素
// 1)通过名称
float r = color.r;
color.r = 1.0f;
// 2)通过下标
float r = color[0];
color[0] = 1.0f;
// 3)搅拌式
vec3 luminance = color.rrr; //基于红色分量设置亮度值
vec4 color2 = color.abgr; //反转颜色分量
向量访问符 | 符号描述 |
(x, y, z, w) | 位置相关分量 |
(r, g, b, a) | 颜色相关分量 |
(s, t, p, q) | 纹理坐标相关分量 |
矩阵的使用
初始化:可以用向量初始化,或单个值指定,值得注意的是矩阵的初始化遵循列优先原则,所以先填充的是第一列。
mat3 m =mat3 (1.0, 0.0, 0.0,
0.0, 1.0, 2.0,
0.0, 0.0, 1.0);
vec3 colum1 = (1.0, 0.0, 0.0);
vec3 colum2 = (1.0, 0.0, 0.0);
vec3 colum3 = (1.0, 0.0, 0.0);
mat3 m = mat3(colum1, colum2, colum3);
mat4 m = mat4(colum1, 1.0,
colum2, 2.0,
colum3, 1.0);
矩阵的访问和赋值:
通过下标形式访问和赋值矩阵的一个向量或者一个元素:
mat4 m = mat4(1.0);
vec4 = m[0]; //矩阵的第一列
float yScale = m[1][1];// 矩阵的第二列,第二行
结构体 类C
struct Particle
{
float fLifeTime;
vec3 vecPosition;
vec3 vecNormal;
}
数组 声明和初始化
float coeff[3];// 等价于float[3] coeff;
int coeff[]; //未定义维度
//定义时候的初始化:
float coeff[3] = float[3](2.3, 1.0, 3.0);
//数组长度可以用默认内建.length()得到n大小;
for(int i = 0; i < coeff.length(); i++)
{
coeff[i] *= 1.0f;
}
存储限制符
修饰符 | 描述 |
const | 常量值必须在声明是初始化。它是只读的不可修改的。 |
attribute | 表示只读的顶点数据,只用在顶点着色器中。数据来自当前的顶点状态或者顶点数组。它必须是全局范围声明的,不能再函数内部。一个attribute可以是浮点数类型的标量,向量,或者矩阵。不可以是数组或则结构体 |
uniform | 一致变量。在着色器执行期间一致变量的值是不变的。与const常量不同的是,这个值在编译时期是未知的是由着色器外部初始化的。一致变量在顶点着色器和片段着色器之间是共享的。它也只能在全局范围进行声明。 |
varying | 顶点着色器的输出。例如颜色或者纹理坐标,(插值后的数据)作为片段着色器的只读输入数据。必须是全局范围声明的全局变量。可以是浮点数类型的标量,向量,矩阵。不能是数组或者结构体。 |
centorid varying | 在没有多重采样的情况下,与varying是一样的意思。在多重采样时,centorid varying在光栅化的图形内部进行求值而不是在片段中心的固定位置求值。 |
invariant | (不变量)用于表示顶点着色器的输出和任何匹配片段着色器的输入,在不同的着色器中计算产生的值必须是一致的。所有的数据流和控制流,写入一个invariant变量的是一致的。编译器为了保证结果是完全一致的,需要放弃那些可能会导致不一致值的潜在的优化。除非必要,不要使用这个修饰符。在多通道渲染中避免z-fighting可能会使用到。 |
in | 用在函数的参数中,表示这个参数是输入的,在函数中改变这个值,并不会影响对调用的函数产生副作用。(相当于C语言的传值),这个是函数参数默认的修饰符 |
out | 用在函数的参数中,表示该参数是输出参数,值是会改变的。 |
inout | 用在函数的参数,表示这个参数即是输入参数也是输出参数。 |
GLSL中数据类型也可以通过上述修饰符来指定自己的行为,这些是不同于C或C++的部分,也是shader编程过程中很重要的一部分,单单给出如上表格,很难让初学者理解其发挥的作用,后面的博客 会用具体的例子来体现这些修饰符的作用。
语句
操作符
GLSL语言的操作符与C语言相似。如下表(操作符的优先级从高到低排列)
操作符 | 描述 |
() | 用于表达式组合,函数调用,构造 |
[] | 数组下标,向量或矩阵的选择器 |
. | 结构体和向量的成员选择 |
++ – | 前缀或后缀的自增自减操作符 |
+ – ! | 一元操作符,表示正 负 逻辑非 |
* / | 乘 除操作符 |
+ - | 二元操作符 表示加 减操作 |
<> <= >= == != | 小于,大于,小于等于, 大于等于,等于,不等于 判断符 |
&& || ^^ | 逻辑与 ,或, 异或 |
?: | 条件判断符 |
= += –= *= /= | 赋值操作符 |
, | 表示序列 |
流控制
GLSL的逻辑控制方式用的也是流行的if-else和switch语句。与C不同的,GLSL有一特殊的关键字discard,discard语句只适用于片元着色器中。片元着色器的运行会在discard语句的位置上立即终止。
函数
GLSL支持用户自定义函数,同样也有一些内置函数。
函数声明语法和C类似,只是变量名需要添加访问修饰符:
计算的不变性
GLSL无法保证在不同的着色器中,两个完全相同的计算式会得到完全一样的结果。这一情形与CPU段应用程序进行计算时的问题相同,即不同优化可能会导致结果非常细微的差异。GLSL中有两种方法来确保着色器之间的计算不变性,即invariant或precise关键字。
Finally
个人认为 Learn OpenGL 是很好的适合初学者学习的网站,但其中对shader的编写没有深入的介绍;此系列博客整理shader学习中的知识和思考,很少地介绍OpenGL CPU代码编写部分。欢迎一起学习进步。
参考资料:
OpenGL编程指南红宝书。
Learn OpenGL CN https://learnopengl-cn.github.io/