OpenGL学习(三)——GLSL
参考资料:
- 【双语】【TheCherno】OpenGL_哔哩哔哩_bilibili
- [LearnOpenGL CN (learnopengl-cn.github.io)](https://learnopengl-cn.github.io/01 Getting started/02 Creating a window/)
- LearnOpenGL 示例环境搭建 - 知乎 (zhihu.com)
GLSL
着色器(Shader)是运行在GPU上的小程序,用来完成渲染管线的一个环节。从基本意义上来说,着色器只是一种把输入转化为输出的程序。
着色器也是一种非常独立的程序,因为它们之间不能相互通信;它们之间唯一的沟通只有通过输入和输出。
在VSCode中可以创建后缀为.glsl的文件编写着色器代码,并通过ShaderToy插件进行实时预览调试。
数据类型
基础数据类型:int
、float
、double
、uint
和bool
。
3种容器类型:向量(Vector)、矩阵(Matrix)和纹理采样器(Sampler)
结构体类型:和C语言一样,只是不支持嵌套定义
数组类型:和C语言一样
内建变量
向量
向量是一个可以包含有2、3或者4个分量的容器,分量的类型是基础类型中的一种。
类型 | 含义 |
---|---|
vecn |
包含n 个float分量的向量(即默认是float类型) |
bvecn |
包含n 个bool分量的向量 |
ivecn |
包含n 个int分量的向量 |
uvecn |
包含n 个unsigned int分量的向量 |
dvecn |
包含n 个double分量的向量 |
其中
n
代表分量的数量
获取分量:通过.x
、.y
、.z
和.w
来获取它们的第1、2、3、4个分量。也允许你对颜色使用rgba
,或是对纹理坐标使用stpq
访问相同的分量。
向量重组
vec2 a(1.0, 2.0);
vec4 b = a.xyxx;
vec3 c = (a, 3.0)
vec4 d = a.xxxx + c.yxzy;
矩阵
类型 | 含义 |
---|---|
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行的浮点矩阵 |
纹理采样器
类型 | 含义 |
---|---|
sampler1D | 用于内建的纹理函数中引用指定的1D纹理的句柄。只可以作为一致变量或者函数参数使用 |
sampler2D | 二维纹理句柄 |
sampler3D | 三维纹理句柄 |
samplerCube | cube map纹理句柄 |
sampler1DShadow | 一维深度纹理句柄 |
sampler2DShadow | 二维深度纹理句柄 |
内建变量
定点着色器可用的内置变量如下表:
名称 | 类型 | 描述 |
---|---|---|
gl_Color | vec4 | 输入属性-表示顶点的主颜色 |
gl_SecondaryColor | vec4 | 输入属性-表示顶点的辅助颜色 |
gl_Normal | vec3 | 输入属性-表示顶点的法线值 |
gl_Vertex | vec4 | 输入属性-表示物体空间的顶点位置 |
gl_MultiTexCoordn | vec4 | 输入属性-表示顶点的第n个纹理的坐标 |
gl_FogCoord | float | 输入属性-表示顶点的雾坐标 |
gl_Position | vec4 | 输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作。所有的顶点着色器都必须写这个值。 |
gl_ClipVertex | vec4 | 输出坐标,用于用户裁剪平面的裁剪 |
gl_PointSize | float | 点的大小 |
gl_FrontColor | vec4 | 正面的主颜色的varying输出 |
gl_BackColor | vec4 | 背面主颜色的varying输出 |
gl_FrontSecondaryColor | vec4 | 正面的辅助颜色的varying输出 |
gl_BackSecondaryColor | vec4 | 背面的辅助颜色的varying输出 |
gl_TexCoord[] | vec4 | 纹理坐标的数组varying输出 |
gl_FogFragCoord | float | 雾坐标的varying输出 |
片段着色器的内置变量如下表:
名称 | 类型 | 描述 |
---|---|---|
gl_Color | vec4 | 包含主颜色的插值只读输入 |
gl_SecondaryColor | vec4 | 包含辅助颜色的插值只读输入 |
gl_TexCoord[] | vec4 | 包含纹理坐标数组的插值只读输入 |
gl_FogFragCoord | float | 包含雾坐标的插值只读输入 |
gl_FragCoord | vec4 | 只读输入,窗口的x,y,z和1/w |
gl_FrontFacing | bool | 只读输入,如果是窗口正面图元的一部分,则这个值为true |
gl_PointCoord | vec2 | 点精灵的二维空间坐标范围在(0.0, 0.0)到(1.0, 1.0)之间,仅用于点图元和点精灵开启的情况下。 |
gl_FragData[] | vec4 | 使用glDrawBuffers输出的数据数组。不能与gl_FragColor结合使用。 |
gl_FragColor | vec4 | 输出的颜色用于随后的像素操作 |
gl_FragDepth | float | 输出的深度用于随后的像素操作,如果这个值没有被写,则使用固定功能管线的深度值代替 |
修饰符
变量的声明可以使用如下的修饰符:
修饰符 | 描述 |
---|---|
const | 常量值必须在声明时初始化。它是只读的不可修改的。 |
attribute | 表示只读的顶点数据,只用在顶点着色器中。数据来自当前的顶点状态或者顶点数组。它必须是全局范围声明的,不能在函数内部。一个attribute可以是浮点数类型的标量,向量,或者矩阵。不可以是数组或者结构体 |
uniform | 一致变量。在着色器执行期间一致变量的值是不变的。与const常量不同的是,这个值在编译时期是未知的是由着色器外部初始化的。一致变量在顶点着色器和片段着色器之间是共享的。它也只能在全局范围进行声明。 |
varying | 顶点着色器的输出。例如颜色或者纹理坐标,(插值后的数据)作为片段着色器的只读输入数据。必须是全局范围声明的全局变量。可以是浮点数类型的标量,向量,矩阵。不能是数组或者结构体。 |
centorid varying | 在没有多重采样的情况下,与varying是一样的意思。在多重采样时,centorid varying在光栅化的图形内部进行求值而不是在片段中心的固定位置求值。 |
invariant | (不变量)用于表示顶点着色器的输出和任何匹配片段着色器的输入,在不同的着色器中计算产生的值必须是一致的。所有的数据流和控制流,写入一个invariant变量的是一致的。编译器为了保证结果是完全一致的,需要放弃那些可能会导致不一致值的潜在的优化。除非必要,不要使用这个修饰符。在多通道渲染中避免z-fighting可能会使用到。 |
in | 用在函数的参数中,表示这个参数是输入的,在函数中改变这个值,并不会影响对调用的函数产生副作用。(相当于C语言的传值),这个是函数参数默认的修饰符 |
out | 用在函数的参数中,表示该参数是输出参数,值是会改变的。 |
inout | 用在函数的参数,表示这个参数即是输入参数也是输出参数。 |
函数
每个shader中必须有一个main函数,其他和C语言一样。
内置函数
角和三角函数
语法 | 说明 |
---|---|
genType radians (genType degrees) | 角度转弧度(degrees to radians) |
genType degrees (genType radians) | 弧度转角度(radians to degrees) |
genType sin (genType angle) | 三角函数-正弦sine |
genType cos (genType angle) | 三角函数-余弦cosine |
genType tan (genType angle) | 三角函数-正切tangent |
genType asin (genType x) | 反三角函数-反正弦arc sine |
genType atan (genType y, genType x) | 反三角函数-反余弦arc cosine |
genType atan (genType y_over_x) | 反三角函数-反正切arc tangent |
指数函数
语法 | 说明 |
---|---|
genType pow (genType x, genType y) | x的y次方,(x^y)。如果x<0,则结果是undefined;如果x=0并且y<=0,则结果是undefined |
genType exp (genType x) | x的自然指数,(e^x) |
genType log (genType x) | x的自然对数,(\log_ex),即(\ln{x})x<=0时结果是undefined |
genType exp2 (genType x) | 2的x次方,(2^x) |
genType log2 (genType x) | 以2为底,x的自然对数,(log_2x),x<=0时结果是undefined |
genType sqrt (genType x) | 对x进行开根号,(\sqrt{x}),x<0时结果是undefined |
genType inversesqrt (genType x) | sqrt的倒数,(\frac{1}{\sqrt x}),x<=0时结果是undefined |
常用函数
语法 | 说明 |
---|---|
genType abs (genType x) | x的绝对值 |
genType sign (genType x) | 判断x是正数、负数,还是零 |
genType floor (genType x) | 返回不大于x的最大整数 |
genType ceil (genType x) | 返回不小于x的最小整数 |
genType fract (genType x) | 返回x的小数部分,即x-floor(x) |
genType mod (genType x, genType y) | 返回x – y * floor (x/y) |
genType min (genType x, genType y) | 返回x和y的较小值 |
genType max (genType x, genType y) | 返回x和y的较大值 |
genType clamp (genType x, genType minVal, genType maxVal) | min (max (x, minVal), maxVal),如果minVal > maxVal,则返回undefined |
genType mix (genType x, genType y, genType a) | 返回x * (1−a) + y * a |
几何函数
语法 | 说明 |
---|---|
genType length (genType x) | 计算向量的长度, (\sqrt{x12+x22+...}) |
genType distance (genType p0, genType p1) | p0和p1之间的距离,即length(p0-p1) |
genType dot (genType x, genType y) | 向量x与向量y的点积 |
genType cross (vec3 x, vec3 y) | 向量x与向量y的叉积 |
genType normalize (genType x) | 返回向量x对应的单位向量,即方向相同,长度为1 |
genType faceforward(genType N, genType I, genType Nref) | 如果dot(Nref, I) < 0,则返回N,否则返回-N |
genType reflect (genType I, genType N) | I是入射光的方向,N是反射平面的法线,返回值是反射光的方向。I – 2 * dot(N, I) * N。N必须是单位向量。 |
genType refract(genType I, genType N, float eta) | I是入射光的方向,N是反射平面的法线,折射率是eta,返回值是折射后的光线的向量。I和N必须是单位向量。 |
矩阵函数
语法 | 说明 |
---|---|
mat matrixCompMult (mat x, mat y) | 返回矩阵x乘以矩阵y的结果。例如result[i][j] 等于 x[i][j] * y[i][j]。注意:如果想进行线性代数里的乘法,请使用符号“*”。 |
向量关系函数
语法 | 说明 |
---|---|
bvec lessThan(vec x, vec y) bvec lessThan(ivec x, ivec y) | 判断x<y |
bvec lessThanEqual(vec x, vec y) bvec lessThanEqual(ivec x, ivec y) | 判断x<=y |
bvec greaterThan(vec x, vec y) bvec greaterThan(ivec x, ivec y) | 判断x>y |
bvec greaterThanEqual(vec x, vec y) bvec greaterThanEqual(ivec x, ivec y) | 判断x>=y |
bvec equal(vec x, vec y) bvec equal(ivec x, ivec y) bvec equal(bvec x, bvec y) | 判断x==y |
bvec notEqual(vec x, vec y) bvec notEqual(ivec x, ivec y) bvec notEqual(bvec x, bvec y) | 判断x!=y |
bool any(bvec x) | 判断x的元素是否有true |
bool all(bvec x) | 判断x的元素是否全部为true |
bool not(bvec x) |
Texture查找函数
语法 | 说明 |
---|---|
vec4 texture2D (sampler2D sampler, vec2 coord) vec4 texture2D (sampler2D sampler, vec2 coord, float bias) vec4 texture2DProj (sampler2D sampler, vec3 coord) vec4 texture2DProj (sampler2D sampler, vec3 coord, float bias) vec4 texture2DProj (sampler2D sampler, vec4 coord) vec4 texture2DProj (sampler2D sampler, vec4 coord, float bias) vec4 texture2DLod (sampler2D sampler, vec2 coord, float lod) vec4 texture2DProjLod (sampler2D sampler, vec3 coord, float lod) vec4 texture2DProjLod (sampler2D sampler, vec4 coord, float lod) | 使用texture坐标coord来查找当前绑定到采样器的texture。对于函数名中含有Proj的函数来说,texture的坐标(coord.s,coord.t)会先除以coord的最后一个坐标。对于vec4这种变种来说,坐标的第三个元素直接被忽略。 |
vec4 textureCube (samplerCube sampler, vec3 coord)vec4 textureCube (samplerCube sampler, vec3 coord, float bias)vec4 textureCubeLod (samplerCube sampler, vec3 coord, float lod) | 使用coord这个坐标去查找当前绑定到采样器的cube map。coord的方向用来表示去查找cube map的哪一个二维平面。OpenGL说明书的2.0版本的3.8.6小节详细描述了这一点。 |
基本程序结构
#版本声明
对于定点着色器用layout关键字
输入变量声明;
输出变量声明;
void main() {
执行代码逻辑
为内置变量、输入变量和输出变量赋值
}
输入与输出
每个着色器都有输入和输出,这样才能进行数据交流和传递。
in
关键字:指定输入变量out
关键字:指定输出变量
只要指定的输出变量与下一个着色器的输入变量匹配,就能实现流水线式的传递。匹配的要求是变量类型和变量名都一样
对于顶点着色器:需要加一个 layout
标识,并且指定 location = 0
。不指定的话就要通过在OpenGL代码中使用glGetAttribLocation查询属性位置值(Location)
对于片段着色器:需要一个 vec4
的颜色输出变量。
CPU与GPU通信——CPU更改着色器中的变量
使用Uniform来实现CPU中的程序向GPU中的着色器发送数据。Uniform是全局的,因此不需要通过流水线式的 in/out
来传递,而是可以直接在任何一个着色器中直接访问。
通过在变量定义前加上 uniform
来将变量声明为Uniform。然后在CPU程序中先需要找到着色器中uniform属性的索引/位置值。当我们得到uniform的索引/位置值后,我们就可以更新它的值了。
如果你声明了一个uniform却在GLSL代码中没用过,编译器会静默移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误
while(!glfwWindowShouldClose(window))
{
// 输入
processInput(window);
// 渲染
// 清除颜色缓冲
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 记得激活着色器
glUseProgram(shaderProgram);
// 更新uniform颜色
float timeValue = glfwGetTime();
float rgbGreenValue = sin(timeValue) / 2.0f + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
glUniform4f(vertexColorLocation, 0.0f, rgbGreenValue, 0.0f, 1.0f); // 设置vec4的4个分量的值
// 绘制三角形
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
// 交换缓冲并查询IO事件
glfwSwapBuffers(window);
glfwPollEvents();
}
glUniform
函数的后缀选择:
后缀 | 含义 |
---|---|
f |
函数需要一个float作为它的值 |
i |
函数需要一个int作为它的值 |
ui |
函数需要一个unsigned int作为它的值 |
3f |
函数需要3个float作为它的值,其他个数以此类推 |
fv |
函数需要一个float向量作为它的值 |
注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个uniform之前你必须先启用程序(调用glUseProgram
),因为它是在当前激活的着色器程序中设置uniform的。