GLSL 编程/向量和矩阵运算
GLSL 的语法与 C 非常相似(因此也与 C++ 和 Java 相似);然而,它有内置的数据类型和用于浮点向量和矩阵的函数,这些函数是 GLSL 特有的。这里将讨论这些函数。有关 GLSL 的完整描述,请参阅““进一步阅读””部分的文献资料。
在 GLSL 中,vec2
、vec3
和 vec4
类型分别代表 2D、3D 和 4D 浮点向量。(还有用于整数和布尔向量的类型,这里不讨论。)向量变量的定义方式与您在 C、C++ 或 Java 中定义这些类型的方式相同。
vec2 a2DVector;
vec3 three_dimensional_vector;
vec4 vector4;
用于浮点 2×2、3×3 和 4×4 矩阵的数据类型是:mat2
、mat3
和 mat4
mat2 m2x2;
mat3 linear_mapping;
mat4 trafo;
在声明浮点变量(包括向量和矩阵变量)时,您可以使用精度限定符 lowp
、mediump
或 highp
来建议一个精度,例如
lowp vec4 color; // for colors, lowp is usually fine
mediump vec4 position; // for positions and texture coordinates, mediump is usually ok
highp vec4 astronomical_position; // for some positions and time, highp is necessary
//(precision of time measurement decreases over time
//and things get jumpy if they rely on absolute time since start of the application)
其思想是,lowp
变量需要的计算时间和存储空间(以及带宽)都比 mediump
变量少,而 mediump
变量需要的计算时间比 highp
变量少。因此,为了获得最佳性能,您应该使用最低的精度,该精度仍然可以提供满意的结果。(这里只有少数例外情况,例如,访问 lowp
向量的单个元素可能比访问 mediump
向量慢,因为这需要额外的解码。)
请注意,决定实际使用哪种精度的决定权在于编译器和驱动程序;精度限定符只是程序员的建议。还要注意,lowp
变量的范围可能非常有限(例如,从 -2.0 到 2.0),因此,lowp
对颜色最有用,但通常不适合位置。
为了定义所有浮点变量的默认精度,您应该使用 precision
命令,例如
precision mediump float;
这将在未为浮点变量指定显式精度限定符的情况下使用 mediump
。如果没有该命令,则浮点数的默认精度在 OpenGL 或 OpenGL ES 顶点着色器中为 highp
,在 OpenGL 片段着色器中为 highp
,在 OpenGL ES 片段着色器中为未定义。(然而,在 Unity 中,precision
命令不可用,顶点着色器的默认精度为 highp
,片段着色器的默认精度为 mediump
。)
向量可以通过与数据类型同名的构造函数进行初始化和转换
vec2 a = vec2(1.0, 2.0);
vec3 b = vec3(-1.0, 0.0, 0.0);
vec4 c = vec4(0.0, 0.0, 0.0, 1.0);
请注意,一些 GLSL 编译器会抱怨如果使用整数来初始化浮点向量;因此,最好始终包含小数点。
您也可以在构造函数中使用一个浮点数将所有分量设置为相同的值
vec4 a = vec4(0.0); // = vec4(0.0, 0.0, 0.0, 0.0)
将更高维向量转换为更低维向量也可以通过这些构造函数来实现
vec4 a = vec4(-1.0, 2.5, 4.0, 1.0);
vec3 b = vec3(a); // = vec3(-1.0, 2.5, 4.0)
vec2 c = vec2(b); // = vec2(-1.0, 2.5)
通过向这些构造函数提供正确数量的分量,可以将更低维向量转换为更高维向量
vec2 a = vec2(0.1, 0.2);
vec3 b = vec3(0.0, a); // = vec3(0.0, 0.1, 0.2)
vec4 c = vec4(b, 1.0); // = vec4(0.0, 0.1, 0.2, 1.0)
类似地,可以初始化和构造矩阵。请注意,在矩阵构造函数中指定的值将被消耗以填充第一列,然后是第二列,依此类推。
mat3 m = mat3(
1.1, 2.1, 3.1, // first column (not row!)
1.2, 2.2, 3.2, // second column
1.3, 2.3, 3.3 // third column
);
mat3 id = mat3(1.0); // puts 1.0 on the diagonal
// all other components are 0.0
vec3 column0 = vec3(0.0, 1.0, 0.0);
vec3 column1 = vec3(1.0, 0.0, 0.0);
vec3 column2 = vec3(0.0, 0.0, 1.0);
mat3 n = mat3(column0, column1, column2); // sets columns of matrix n
如果使用较小的矩阵来构造较大的矩阵,则额外的行和列将设置为它们在单位矩阵中应该具有的值
mat2 m2x2 = mat2(
1.1, 2.1,
1.2, 2.2
);
mat3 m3x3 = mat3(m2x2); // = mat3(
// 1.1, 2.1, 0.0,
// 1.2, 2.2, 0.0,
// 0.0, 0.0, 1.0)
mat2 mm2x2 = mat2(m3x3); // = m2x2
如果使用较大的矩阵来构造较小的矩阵,则将选择较大矩阵的顶部、左侧子矩阵,例如,在上一示例的最后一行。
向量的分量可以通过使用 []
运算符进行数组索引来访问(索引从 0 开始),或者通过使用 .
运算符和元素名称 x, y, z, w
或 r, g, b, a
或 s, t, p, q
来访问
vec4 v = vec4(1.1, 2.2, 3.3, 4.4);
float a = v[3]; // = 4.4
float b = v.w; // = 4.4
float c = v.a; // = 4.4
float d = v.q; // = 4.4
还可以通过扩展 .
符号(“swizzling”)来构造新的向量
vec4 v = vec4(1.1, 2.2, 3.3, 4.4);
vec3 a = v.xyz; // = vec3(1.1, 2.2, 3.3)
vec3 b = v.bgr; // = vec3(3.3, 2.2, 1.1)
vec2 c = v.tt; // = vec2(2.2, 2.2)
矩阵被认为是由列向量组成的,这些列向量可以通过使用 []
运算符进行数组索引来访问。可以使用上面讨论的方法来访问结果(列)向量的元素
mat3 m = mat3(
1.1, 2.1, 3.1, // first column
1.2, 2.2, 3.2, // second column
1.3, 2.3, 3.3 // third column
);
vec3 column3 = m[2]; // = vec3(1.3, 2.3, 3.3)
float m20 = m[2][0]; // = 1.3
float m21 = m[2].y; // = 2.3
如果二元运算符 *, /, +, -, =, *=, /=, +=, -=
用于相同类型的向量之间,它们将按分量进行操作
vec3 a = vec3(1.0, 2.0, 3.0);
vec3 b = vec3(0.1, 0.2, 0.3);
vec3 c = a + b; // = vec3(1.1, 2.2, 3.3)
vec3 d = a * b; // = vec3(0.1, 0.4, 0.9)
特别要注意,a * b
表示两个向量的按分量乘积,这在线性代数中并不常见。
对于矩阵,这些运算符也按分量进行操作,除了 *
运算符,它表示矩阵乘积,例如
在 GLSL 中
mat2 a = mat2(1., 2., 3., 4.);
mat2 b = mat2(10., 20., 30., 40.);
mat2 c = a * b; // = mat2(
// 1. * 10. + 3. * 20., 2. * 10. + 4. * 20.,
// 1. * 30. + 3. * 40., 2. * 30. + 4. * 40.)
对于逐元素矩阵乘法,提供了内置函数 matrixCompMult
。
*
运算符也可以用来将浮点数(即标量)乘以向量或矩阵的所有元素(从左或右)。
vec3 a = vec3(1.0, 2.0, 3.0);
mat3 m = mat3(1.0);
float s = 10.0;
vec3 b = s * a; // vec3(10.0, 20.0, 30.0)
vec3 c = a * s; // vec3(10.0, 20.0, 30.0)
mat3 m2 = s * m; // = mat3(10.0)
mat3 m3 = m * s; // = mat3(10.0)
此外,*
运算符可用于对应维度的矩阵向量乘法,例如
在 GLSL 中
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = m * v; // = vec2(1. * 10. + 3. * 20., 2. * 10. + 4. * 20.)
注意向量必须从右边乘以矩阵。
如果向量从 **左边** 乘以矩阵,则结果对应于从左边将行向量乘以矩阵。这相当于将列向量从右边乘以 **转置** 矩阵。
在组件中
因此,从左侧将向量乘以矩阵对应于从右侧将向量乘以转置矩阵。
vec2 v = vec2(10., 20.);
mat2 m = mat2(1., 2., 3., 4.);
vec2 w = v * m; // = vec2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)
由于没有内置函数来计算转置矩阵,因此此技术非常有用:只要需要将向量乘以转置矩阵,就可以将其从左侧乘以原始矩阵。此技术的几个应用在“应用矩阵变换”部分进行了描述。
内置向量和矩阵函数
[edit | edit source]逐元素函数
[edit | edit source]如前所述,函数
TYPE matrixCompMult(TYPE a, TYPE b) // component-wise matrix product
对矩阵类型 mat2
、mat3
和 mat4
(表示为 TYPE
)计算逐元素乘积。
以下函数对类型为 float, vec2, vec3
和 vec4
的变量(表示为 TYPE
)逐元素执行操作。
TYPE min(TYPE a, TYPE b) // returns a if a < b, b otherwise
TYPE min(TYPE a, float b) // returns a if a < b, b otherwise
TYPE max(TYPE a, TYPE b) // returns a if a > b, b otherwise
TYPE max(TYPE a, float b) // returns a if a > b, b otherwise
TYPE clamp(TYPE a, TYPE minVal, TYPE maxVal)
// = min(max(a, minVal), maxVal)
TYPE clamp(TYPE a, float minVal, float maxVal)
// = min(max(a, minVal), maxVal)
TYPE mix(TYPE a, TYPE b, TYPE wb) // = a * (TYPE(1.0) - wb) + b * wb
TYPE mix(TYPE a, TYPE b, float wb) // = a * TYPE(1.0 - wb) + b * TYPE(wb)
还有更多内置函数,这些函数也按逐元素方式工作,但对于向量来说不太有用,例如,abs
、sign
、floor
、ceil
、fract
、mod
、step
、smoothstep
、sqrt
、inversesqrt
、pow
、exp
、exp2
、log
、log2
、radians
(将度转换为弧度)、degrees
(将弧度转换为度)、sin
、cos
、tan
、asin
、acos
、atan
(带一个参数和带两个参数分别用于带符号分子和带符号分母)、lessThan
、lessThanEqual
、greaterThan
、greaterThanEqual
、equal
、notEqual
和 not
。
几何函数
[edit | edit source]以下函数对于向量运算特别有用。TYPE
可以是:float, vec2, vec3
和 vec4
(每行仅一项)。
vec3 cross(vec3 a, vec3 b) // = vec3(a[1] * b[2] - a[2] * b[1],
// a[2] * b[0] - a[0] * b[2],
// a[0] * b[1] - a[1] * b[0])
float dot(TYPE a, TYPE b) // = a[0] * b[0] + a[1] * b[1] + ...
float length(TYPE a) // = sqrt(dot(a, a))
float distance(TYPE a, TYPE b) // = length(a - b)
TYPE normalize(TYPE a) // = a / length(a)
TYPE faceforward(TYPE n, TYPE i, TYPE nRef)
// returns n if dot(nRef, i) < 0, -n otherwise
TYPE reflect(TYPE i, TYPE n) // = i - 2. * dot(n, i) * n
// this computes the reflection of vector 'i'
// at a plane of normalized(!) normal vector 'n'
物理函数
[edit | edit source]函数
TYPE refract(TYPE i, TYPE n, float r)
计算折射光线的方向,其中 i
指定入射光线的归一化(!) 方向,n
指定两种光学介质(例如空气和水)界面的归一化(!) 法向量。向量 n
应指向 i
所来的方向,即 n
和 i
的点积应为负数。浮点数 r
是光线所来介质的折射率与表面另一侧介质的折射率的比率。因此,如果光线从空气(折射率约为 1.0)射入水(折射率 1.33)表面,则比率 r
为 1.0 / 1.33 = 0.75。函数的计算方法为
float d = 1.0 - r * r * (1.0 - dot(n, i) * dot(n, i));
if (d < 0.0) return TYPE(0.0); // total internal reflection
return r * i - (r * dot(n, i) + sqrt(d)) * n;
如代码所示,如果发生全内反射(参见维基百科中的条目),即光线未穿过两种材料之间的界面,则函数将返回长度为 0 的向量。
进一步阅读
[edit | edit source]OpenGL 的所有 GLSL 详细信息都在 “OpenGL 着色语言 4.10.6 规范” 中,可在Khronos OpenGL 网站上获取。
关于 OpenGL 的 OpenGL 着色语言的更易理解的描述可以在许多关于 OpenGL 的书的最新版本中找到,例如,在 Dave Shreiner 于 2009 年由 Addison-Wesley 出版发行的 “OpenGL 编程指南:学习 OpenGL 版本 3.0 和 3.1 的官方指南”(第 7 版)第 15 章中。
OpenGL ES 2.0 的所有 GLSL 详细信息都在 “OpenGL ES 着色语言 1.0.17 规范” 中,可在“Khronos OpenGL ES API 注册表”上获取。
关于 OpenGL ES 着色语言的更易理解的描述可以在 Aaftab Munshi、Dan Ginsburg 和 Dave Shreiner 合著的 “OpenGL ES 2.0 编程指南” 第 5 章和附录 B 中找到(参见其网站)。
< GLSL 编程