本文为L_Ares个人写作,如需转载请表明原文出处。
GLSL语法和C语言的相似,但是GLSL中是没有指针的。
另外,GLSL一般情况下都是没有语法提示的,所以最好不要写复杂的GLSL语言,除非你有业务需求,不然的话,很容易写错,而且找错也是很难的事情。
一、变量和变量类型
1. 整型 : int
。代表至少包含16位的有符号的整数。可以是8进制,10进制,16进制。
uint
。无符号整型。例如 : uint uValue = 188u;
2. 浮点型 : float
。OpenGL3.0以上的版本只有单精度浮点型,没有双精度。
例如 :float a = 3.14159f;
3. 布尔型 : bool
。只有true
和false
。例如 : bool a = true;
4. 向量 : 如图1.1
构造方法 : 拿float
类型的向量举例,其他的类型一致。
例如 : vec4 v1 = vec4(0,1,2,3);
也就是说,向量的初始化不是直接给个括号就行,要加上vec
,具体是vec
几,就看你用几分量的向量了。
在向量中的元素可以通过(x,y,z,w)或(r,g,b,a)或(s,t,p,q)
来取值,也可以利用它赋值,并且可以多个一起赋值,例如:
vec4 v1 = vec4(1.0,2.0,3.0,4.0);
vec4 v2 = vec4(5.0,6.0,7.0,8.0);
v2.r = v1.s;
v2.g = v1.x;
//这两个是可以的,但是没意义。
v2.rg = v1.xy;
v2.rg = v1.st;
//但是不能搞乱顺序,比如下面是不行的。
v2.rg = v1.xt;
//或者直接赋值
v1.rgb = vec3(1.0,2.0,3.0);
//也可以一次性对所有的分量进行操作
v1.xyzw = v2.xyzw + vec4(1.0,1.0,1.0,1.0);
向量也支持2个或者2个以上的元素进行交换操作(swizzle)。例如:
v1.rgba = v2.bgra;
v2.bgra = v1.rgba;
5. 矩阵 : 矩阵只有浮点类型元素。其他的都不支持。
OpenGL中的矩阵是列主顺序的,如果初始化的时候只给矩阵初始化一个值,则会构造成对角矩阵,其余的元素全部为0。
如图1.2
构造方法 : 拿mat4
来看,其他类型一致。
例如 :
//构造一个单元矩阵
mat4 m1 = mat4(1.0,0.0,0.0,0.0,
0.0,1.0,0.0,0.0,
0.0,0.0,1.0,0.0,
0.0,0.0,0.0,1.0);
//也可以这么构造单元矩阵
mat4 m2 = mat4(1.0);
和向量一样,想要初始化矩阵,要使用矩阵的构造函数:matx
,其中x是图1.2中的类型,根据需求自行选取。
6. 数组 : GLSL中只可以使用一维数组,数组的类型可以是所有基本类型或者结构体。
声明数组的方式 :
(1). 假设已经定义了结构体myStruct
myStruct myStructArray[];
(2). 向量数组
vec4 myVecArray[6];
(3). 数组赋值
vec4 tempVecArray[] = myVecArray;
(4). 基本数据类型数组,拿float
类型举例
float floatArray[8];
构造函数 : float a[4] = float[](1.0,2.0,3.0,4.0);
其他的类型类似。
上述方式和类似的方式都是可以声明一个数组的,但是不可以声明一个数组的数组。另外,GLSL中有可以获得数组长度的内建函数函数length()
,比如:floatArray.length()
可以获得数组的长度为8。
访问数组中的元素可以 : floatArray[0]
,这样,范围是[0,size - 1],如果越界,那么会发生编译错误,和C语言是一样的。
7. 结构体 : GLSL的结构体可以组合基本类型和数组来形成用户自定义的类型。在定义结构体的时候,你可以直接定义一个结构体实例,或者在后面定义结构体实例,这都是可以的。
定义一个结构体myTemp
并声明一个它的变量tempOne
: 这里注意,固定大小的数组是合法的。
struct myTemp
{
float pointSize;
vec4 colors;
vec3 position[3];
}tempOne;
结构体可以使用=
进行赋值,可以使用==
或!=
分别表示结构体相等,结构体不相等,只有两个结构体的所有元素都一致的时候,两个结构体才是相等的s,例如:
myTemp tempTwo;
tempOne = tempTwo;
tempOne == tempTwo;
访问结构体的变量使用点语法,比如:tempOne.colors
。
GLSL的结构体不支持嵌套定义,只有预先声明的结构体才可以嵌套其中。例如,上面我们定义了结构体myTemp
,下面我们再定义一个结构体theTemp
struct theTemp
{
float pointSize;
myTemp temp;
//注意,这里是不合法的,不可以这么写。结构体只有预先定义过的才可以嵌套。
struct cantUse{
float a;
float b;
}test;
//这也是个结构体,但是我们是在这个结构体的下面定义的,所以也是不合法的。
subTemp sub;
}
struct subTemp
{
float c;
float d;
}tex;
8. 变量存储限定符 :
如图1.3所示
二、函数
1. 函数的修饰符
in :
没有指定时,默认限定修饰符。复制进函数中,函数内部可以改变内部的它,但是函数不能对其本身进行修改。
inout :
复制进函数,并且可以在函数中进行修改,并且返回的时候也是复制出去。
out :
只在返回的时候复制,可以进行修改。
函数的修饰符不一定都要用到,看需求使用。
2. 主函数
在GLSL的每一个shader中,都必须有一个main
函数,main
函数的void
参数是可选的,但是返回值是void
是必须的。例如:
//括号里面的参数void是可选的,可写可不写,但是最前面的返回值void是必须要有的。
void main (void)
{
...
}
3. 普通函数
在GLSL中,函数必须是全局声明的,不可以在函数中声明或者定义函数。
函数必须有返回类型,参数是可选的。
参数的修饰符,也就是1中所说的几个,都是可选的。
结构体和数组都可以作为函数的参数传递,但是必须限制其大小,在调用传参的时候,只需要传入它们的名字就可以。
全部用上,一起举例:
vec4 myFunc(inout float num, out vec4 v1,mat4 m2,vec4 arr[10])
{
return v1;
}
比较正常的用法:
vec4 diffuse(vec3 normal , vec3 light , vec4 baseColor)
{
return baseColor * dot(normal,light);
}
另外,GLSL中,是没有递归函数的。
GLSL的函数是可以重载的,函数可以同名,其参数类型或者参数的数量不同即可。
三、控制流
GLSL提供了类似C语言的while
、do...while
的循环方式,没有for
循环。continue
会跳到下一次循环,break
是结束循环。用法几乎一致,直接参考C语言就行。
GLSL也提供了控制语句if/else
,比如:
if(color.a < 0.2){
color *= color.a
}else{
color = vec4(1.0,1.0,1.0,1.0);
}
但是!!!尽量少在GLSL中写这种逻辑代码,减少逻辑判断在GLSL完成的次数,减少在GLSL中使用循环的情况。
另外在片元着色器(fragmentShader)中,有一种特殊的控制流 : discard
。
使用discard
会退出片元着色器,不执行后面的片元操作,片元也不会写入帧缓冲区。
if (color.a < 0.2) discard;