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放松了以下几点要求:

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏xcywt

《大话数据结构》一些基础知识

第一章 数据结构绪论 1.4 基本概念和术语 1.4.1 数据 数据:描述客观事物的符号,是计算机中可以操作的对象,是能被极端及识别,并输入给计算机处理的符号集...

3349
来自专栏深度学习之tensorflow实战篇

python中list、array、matrix之间的基本区别

python科学计算包的基础是numpy, 里面的array类型经常遇到. 一开始可能把这个array和python内建的列表(list)混淆, 这里简单总结一...

49012
来自专栏软件开发 -- 分享 互助 成长

二叉排序树和平衡二叉树

二叉排序树又称二叉查找树或二叉搜索树。 它一棵空树或者是具有下列性质: (1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值; (2)若右子树不空,...

19410
来自专栏杂七杂八

numpy科学计算包的使用2

利用数组进行数据处理 NumPy数组使你可以将许多种数据处理任务表述为简洁的数组表达式(否则需要编写循环)。用数组表达式代替循环的做法,通常被称为矢量化。 矢...

34812
来自专栏机器之心

搭建模型第一步:你需要预习的NumPy基础都在这了

NumPy 主要的运算对象为同质的多维数组,即由同一类型元素(一般是数字)组成的表格,且所有元素通过正整数元组进行索引。在 NumPy 中,维度 (dimens...

962
来自专栏青玉伏案

算法与数据结构(十一) 平衡二叉树(AVL树)(Swift版)

今天的博客是在上一篇博客的基础上进行的延伸。上一篇博客我们主要聊了二叉排序树,详情请戳《二叉排序树的查找、插入与删除》。本篇博客我们就在二叉排序树的基础上来聊聊...

2067
来自专栏HTML5学堂

提取数字——字符串、正则面试题

提取数字——字符串、正则面试题 HTML5学堂:正则、数组、字符串,是JavaScript语言中让人头痛的一些知识,今天这篇文章我们使用数组字符串、正则两种方法...

2776
来自专栏Python中文社区

NumPy二元运算的broadcasting机制

NumPy中有一个非常方便的特性:broadcasting。当我们对两个不同长度的numpy数组作二元计算(如相加,相乘)的时候,broadcasting就在背...

3328
来自专栏TensorFlow从0到N

讨厌算法的程序员 7 - 归并排序的时间复杂度分析

? 递归树 上一篇归并排序基于分治思想通过递归的调用自身完成了排序,本篇是关于归并排序的最后一部分——分析其时间复杂度。 这个过程中会解释清楚在各种时间复杂度...

2336
来自专栏编程理解

数据结构(四):平衡二叉树(AVL树)

。影响时间复杂度的因素即为二叉树的高,为了尽量避免树中每层上只有一个节点的情况,这里引入平衡二叉树。

753

扫码关注云+社区