GLSL-运算符和表达式

GLSL基本的运算符和表达式使用方法。

运算符

优先级 运算符说明 运算符 结合性
1 括号 ( )
2 数组下标
函数调用、构造函数
变量选择器
后置++、后置--
[] () . ++ -- 从左往右
3 前置++、前置--
一元运算符
++ -- + - ~ ! 从右往左
4 乘除法 * / % 从左往右
5 加减法 + - 从左往右
6 位操作 << >> 从左往右
7 大小关系 > >= < <= 从左往右
8 相等性 = != 从左往右
9 位操作 与 & 从左往右
10 位操作异或 ^ 从左往右
11 位操作 或 | 从左往右
12 逻辑与 && 从左往右
13 逻辑异或 ^^ 从左往右
14 逻辑或 |
15 选择 ?: 从右往左
16 赋值 = += -=
*= /= %=
<<= >>=
&= ^= |=
从右往左
17 序列 , 从左往右

GLSL中没有取地址运算符,也没有类转换符。

数组下标

GLSL中只能通过数组的下标来操作其中的元素。例如:

diffuseColor += lightIntensity[3] * NdotL;

构造函数

GLSL中的构造函数和C++没有什么不同,唯一的区别就是构造函数传递的参数必须要全部使用,未用到的参数不要写到构造函数的参数列表中。

标量的构造函数和类型转换

int(bool) // converts a Boolean value to an int
int(float) // converts a float value to an int
float(bool) // converts a Boolean value to a float
float(int) // converts an integer value to a float
bool(float) // converts a float value to a Boolean
bool(int) // converts an integer value to a Boolean

我们可以通过标量的构造函数来实现类型的转换。比如*int(bool)*把bool类型转成int类型。

这里简单说下转换后数值的变化:
float-->int : 只取float的整数部分,丢弃分数部分。
int、float-->bool : 非零转成true,零转成false。
bool-->int、float : true转成1或1.0,false转成0或0.0。

类似下面这种也可以,不过没啥卵用。

float(float)

标量的构造函数也可以传入非标量,这种会取非标量的第一个元素赋值。比如:

float(vec3) // 这里会取vec3的第一个值进行赋值

向量和矩阵的构造函数

非标量的构造函数可以根据一些标量进行初始化。

对于一个向量来说,如果只传一个标量给构造函数,则向量的每个元素都会被赋值成这个标量。

对于一个矩阵来说,如果只传一个标量给构造函数,则矩阵的对角线的元素会被赋值成这个标量,其他值会被赋成0。

向量的构造函数可以接受多个标量、向量,或者他们的混合。该向量的元素会以此被赋值,从参数列表从左到右依次被使用,并且每个非标量参数的每个元素都会被挨个依次使用。

对于矩阵的构造函数来说,也可以接收多个标量、向量、矩阵,或者他们的混合。矩阵构造或被使用时按照列优先的方式。

不管是向量还是矩阵,参数包含的元素的数量一定要大于等于向量的元素个数。但是当向量或矩阵的元素已经被全部赋值后,如果还有未被使用的参数,就会报错。

矩阵的构造函数也可以传入一个矩阵,这时参数的每一个元素(第i行,第j列)会赋值到矩阵对应的元素(第i行,第j列)上,矩阵其余未赋值的元素被赋值成单位矩阵。如果矩阵的构造函数的参数包含了矩阵,则不能同时有其他参数。

向量构造函数举例:

vec3(float) // initializes each component of with the float
vec4(ivec4) // makes a vec4 with component-wise conversion
vec2(float, float) // initializes a vec2 with 2 floats
ivec3(int, int, int) // initializes an ivec3 with 3 ints
bvec4(int, int, float, float) // uses 4 Boolean conversions
vec2(vec3) // drops the third component of a vec3
vec3(vec4) // drops the fourth component of a vec4
vec3(vec2, float) // vec3.x = vec2.x, vec3.y = vec2.y, vec3.z = float
vec3(float, vec2) // vec3.x = float, vec3.y = vec2.x, vec3.z = vec2.y
vec4(vec3, float)
vec4(float, vec3)
vec4(vec2, vec2)
vec4 color = vec4(0.0, 1.0, 0.0, 1.0);
vec4 rgba = vec4(1.0); // sets each component to 1.0
vec3 rgb = vec3(color); // drop the 4th component

下面几个矩阵初始化的例子,对角线会设置成参数的值,其他元素设成0:

mat2(float)
mat3(float)
mat4(float)

矩阵的初始化的例子:

mat2(vec2, vec2);
mat3(vec3, vec3, vec3);
mat4(vec4, vec4, vec4, vec4);
mat2(float, float,
float, float);
mat3(float, float, float,
float, float, float,
float, float, float);
mat4(float, float, float, float,
float, float, float, float,
float, float, float, float,
float, float, float, float);

当然,初始化的例子多种多样,只要符合原则即可。

结构体的构造函数

结构体的构造函数比较简单,举例:

struct light {
float intensity;
vec3 position;
};
light lightVar = light(3.0, vec3(1.0, 2.0, 3.0));

如上,只需要传入的参数的顺序和结构体中变量声明的顺序完全一致即可。

向量的元素

我们知道一个向量由多个元素组成,比如vec3里有三个元素,我们可以通过三种方式来操作这些元素。

向量在OpenGL中可以代表一个坐标系中的坐标,也可以代表一个颜色,也可以代表texture的坐标。所以GLSL提供了三种方式来操作向量中的元素:

操作方式 场景
{x, y, z, w} 向量代表着坐标或法线的时候用这个
{r, g, b, a} 向量代表着一个颜色值的时候用这个
{s, t, p, q} 向量代表着texture的坐标的时候用这个

其实这三种方式是完全一样的,只是GLSL为了让代码可阅读性更高,根据向量的不同应用场景,提供了三种不同的操作方式。比如vec3.xvec3.rvec3.s是完全一样的,都是指代向量的第一个元素。只是在不同场景选用不同的方式会使代码可阅读性更好而已。

除了获取向量中的某一个元素的值以外,GLSL还提供了更方便的方法同时获取多个值:

vec4 v4;
v4.rgba; // is a vec4 and the same as just using v4,
v4.rgb; // is a vec3,
v4.b; // is a float,
v4.xy; // is a vec2,
v4.xgba; // is illegal - the component names do not come from
// the same set.

但是最多只能获取四个值,举例:

vec4 v4;
v4.xyzw; // is a vec4
v4.xyzwxy; // is illegal since it has 6 components
(v4.xyzwxy).xy; // is illegal since the intermediate value has 6 components
vec2 v2;
v2.xyxy; // is legal. It evaluates to a vec4.

获取元素的值的时候可以任意组合,比如:

vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
vec4 swiz= pos.wzyx; // swiz = (4.0, 3.0, 2.0, 1.0)
vec4 dup = pos.xxyy; // dup = (1.0, 1.0, 2.0, 2.0)

除了读取向量的元素值意外,也可以通过这种方式进行写操作,例如:

vec4 pos = vec4(1.0, 2.0, 3.0, 4.0);
pos.xw = vec2(5.0, 6.0); // pos = (5.0, 2.0, 3.0, 6.0)
pos.wx = vec2(7.0, 8.0); // pos = (8.0, 2.0, 3.0, 7.0)
pos.xx = vec2(3.0, 4.0); // illegal - 'x' used twice
pos.xy = vec3(1.0, 2.0, 3.0); // illegal - mismatch between vec2 and vec3

除了上面这三种方式以外,GLSL还提供了类似访问数组下标的形式来访问向量的元素。

vec4 pos;
pos[0] = 1; // 将向量的第一个元素设为0

矩阵的元素

操作矩阵的元素也很简单,可以把矩阵当成一个列优先的二维数组。

mat4 m;
m[1] = vec4(2.0); // 设置第二列的元素全部为2.0
m[0][0] = 1.0; // 设置左上角的元素为1.0
m[2][3] = 2.0; // 设置第三列的第四个元素为2.0

和C++中用二维数组表示矩阵时的操作相同。

结构体的元素

对结构体的基本操作有三种:

操作 符号
选择元素 .
相等性比较 == !=
赋值 =

对于选择元素来说,没啥好讲的。

对于相等性比较,GLSL中,判断两个结构体是否相等,首先要求这两个结构体的class是一样的。两个结构体相等当且仅当这两个结构体中的每一个元素都相等。

赋值和相等性判断不适用于包含数组或simpler的结构体。

Struct S {int x;};
S a;
{
struct S {int x;};
S b;
a = b; // error: type mismatch
}

赋值

GLSL中的赋值操作,表达式左右两边的类型要完全一致,因为GLSL中没有类型转换,如果想转换类型,必须通过在构造函数中传参的形式实现。

除了基本的赋值操作,还有一些操作,比如*+=-=、**= 、 */=*等等。

矩阵和向量操作

一般来说,矩阵以及向量的操作都是针对其中的元素进行操作。比如:

vec3 v, u;
float f;
v = u + f;

和下面的操作完全相等:

v.x = u.x + f;
v.y = u.y + f;
v.z = u.z + f;

再举个例子:

vec3 v, u, w;
w = v + u;

上面的操作和下面的操作完全相等:

w.x = v.x + u.x;
w.y = v.y + u.y;
w.z = v.z + u.z;

但是有例外,当矩阵和矩阵相乘、矩阵和向量相乘,则是按照线性代数的规则相乘。

例如:

vec3 v, u;
mat3 m;
u = v * m;

等同于

u.x = dot(v, m[0]); // m[0] is the left column of m
u.y = dot(v, m[1]); // dot(a,b) is the inner (dot) product of a and b
u.z = dot(v, m[2]);

再例如:

u = m * v;

等同于

u.x = m[0].x * v.x + m[1].x * v.y + m[2].x * v.z;
u.y = m[0].y * v.x + m[1].y * v.y + m[2].y * v.z;
u.z = m[0].z * v.x + m[1].z * v.y + m[2].z * v.z;

再例如:

mat m, n, r;
r = m * n;

等同于

r[0].x = m[0].x * n[0].x + m[1].x * n[0].y + m[2].x * n[0].z;
r[1].x = m[0].x * n[1].x + m[1].x * n[1].y + m[2].x * n[1].z;
r[2].x = m[0].x * n[2].x + m[1].x * n[2].y + m[2].x * n[2].z;
r[0].y = m[0].y * n[0].x + m[1].y * n[0].y + m[2].y * n[0].z;
r[1].y = m[0].y * n[1].x + m[1].y * n[1].y + m[2].y * n[1].z;
r[2].y = m[0].y * n[2].x + m[1].y * n[2].y + m[2].y * n[2].z;
r[0].z = m[0].z * n[0].x + m[1].z * n[0].y + m[2].z * n[0].z;
r[1].z = m[0].z * n[1].x + m[1].z * n[1].y + m[2].z * n[1].z;
r[2].z = m[0].z * n[2].x + m[1].z * n[2].y + m[2].z * n[2].z;

总的来说,矩阵和向量二者之间的操作都是线性代数的操作,矩阵向量和标量之间的操作都是标量依次和矩阵向量的每一个元素依次操作。

表达式求值

c++标准要求表达式必须按照优先级指定的顺序进行计算。如果结果相同或结果未定义,则只能重新组合操作。没有其他的转换可以应用于影响操作的结果。GLSL ES放松了以下几点要求:

  • 加法和乘法被认为是相联的。
  • 乘法可以被重复的加法取代。
  • 除法可以用倒数和乘法来代替。
  • 值可以用比指定的更高的精度来表示。的约束内不变性(在适用的情况下),使用的精度可能有所不同。
  • 整数值可以用浮点值表示。对这些值的操作可能是。由相应的浮点运算执行。