跳转到内容

Cg 编程/向量和矩阵操作

来自维基教科书,开放的书籍,开放的世界

Cg 的语法与 C(因此 C++ 和 Java)非常相似;但是,它包含用于浮点向量和矩阵的内置数据类型和函数,这些函数是 Cg 特有的。这些将在本文中讨论。Cg 的完整描述可以在 Nvidia 的 Cg 教程Nvidia 的 Cg 语言规范 中找到。

具有全精度的浮点数据类型

[编辑 | 编辑源代码]

在 Cg 中,类型 float2float3float4 代表 2D、3D 和 4D 浮点向量。向量变量的定义与您预期的一样,就像 C、C++ 或 Java 具有这些类型一样

float2 a2DVector;
float3 three_dimensional_vector;
float4 vector4;

浮点 2×2、3×3 和 4×4 矩阵的数据类型分别为:float2x2float3x3float4x4

float2x2 m2x2;
float3x3 linear_mapping;
float4x4 trafo;

还存在用于具有不同行数和列数的矩阵的类型,例如 3 行 4 列:float3x4

具有有限精度的的数据类型

[编辑 | 编辑源代码]

除了 float 数据类型之外,还有额外的 half 类型(halfhalf2half3half4half2x2half3x3half4x4 等)和 fixed 类型(fixedfixed2fixed3fixed4 等),它们代表有限精度和范围的数字(即标量)、向量和矩阵。它们通常提供更好的性能;因此,为了获得最佳性能,您应该使用 half 类型之一代替相应的 float 类型(特别是如果数据表示几何位置或纹理坐标)甚至 fixed 类型之一(通常如果数据表示颜色)如果有限精度和范围不会导致渲染伪影。

在这里,代码将始终使用 float 类型以避免任何与有限精度相关的错误并保持尽可能简单。还存在用于整数和布尔向量的类型,这些类型这里将不讨论。

构造函数

[编辑 | 编辑源代码]

向量可以通过与数据类型同名的构造函数进行初始化和转换

float2 a = float2(1.0, 2.0);
float3 b = float3(-1.0, 0.0, 0.0);
float4 c = float4(0.0, 0.0, 0.0, 1.0);

请注意,某些 Cg 编译器可能会在使用整数初始化浮点向量时报错;因此,最好始终包含小数点。

将低维向量转换为高维向量是通过向这些构造函数提供正确数量的组件来实现的

float2 a = float2(0.1, 0.2);
float3 b = float3(0.0, a); // = float3(0.0, 0.1, 0.2)
float4 c = float4(b, 1.0); // = float4(0.0, 0.1, 0.2, 1.0)

同样,矩阵可以被初始化和构造。请注意,在矩阵构造函数中指定的这些值将被消耗以填充第一行,然后是第二行,依此类推。

float3x3 m = float3x3(
   1.1, 1.2, 1.3, // first row (not column as in GLSL!)
   2.1, 2.2, 2.3, // second row
   3.1, 3.2, 3.3  // third row
);
float3 row0 = float3(0.0, 1.0, 0.0);
float3 row1 = float3(1.0, 0.0, 0.0);
float3 row2 = float3(0.0, 0.0, 1.0);
float3x3 n = float3x3(row0, row1, row2); // sets rows of matrix n

Cg(但不是所有版本的 HLSL)也接受 float4 构造函数中的一个浮点数,以将所有组件设置为相同的值

float4 a = float4(0.0); // = float4(0.0, 0.0, 0.0, 0.0)

此外,Cg(但不是所有版本的 HLSL)可以使用向量构造函数将高维向量转换为低维向量

float4 a = float4(-1.0, 2.5, 4.0, 1.0);
float3 b = float3(a); // = float3(-1.0, 2.5, 4.0)
float2 c = float2(b); // = float2(-1.0, 2.5)

如果从较大的矩阵构造较小的矩阵,则将选择较大矩阵的顶部左子矩阵

float3x3 m = float3x3(
   1.1, 1.2, 1.3,
   2.1, 2.2, 2.3,
   3.1, 3.2, 3.3 
);
float2x2 n = float2x2(m); // = float2x2(1.1, 1.2, 2.1, 2.2)

向量的组件可以通过使用 [] 运算符进行数组索引(索引从 0 开始)或使用 . 运算符和元素名称 x, y, z, wr, g, b, a(或 s, t, p, q)进行访问

float4 v = float4(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

也可以通过扩展 . 符号来构造新的向量

float4 v = float4(1.1, 2.2, 3.3, 4.4);
float3 a = v.xyz; // = float3(1.1, 2.2, 3.3) 
float3 b = v.bgr; // = float3(3.3, 2.2, 1.1)

矩阵被认为由行向量组成,这些行向量可以通过使用 [] 运算符进行数组索引进行访问。得到的(行)向量的元素可以通过上面讨论的方式进行访问

float3x3 m = float3x3(
   1.1, 1.2, 1.3, // first row 
   2.1, 2.2, 2.3, // second row
   3.1, 3.2, 3.3  // third row
);
float3 row2 = m[2]; // = float3(3.1, 3.2, 3.3)
float m20 = m[2][0]; // = 3.1
float m21 = m[2].y; // = 3.2

运算符

[编辑 | 编辑源代码]

如果二元运算符 *, /, +, -, =, *=, /=, +=, -= 用于相同类型的向量之间,它们将按组件执行

float3 a = float3(1.0, 2.0, 3.0);
float3 b = float3(0.1, 0.2, 0.3);
float3 c = a + b; // = float3(1.1, 2.2, 3.3)
float3 d = a * b; // = float3(0.1, 0.4, 0.9)

请特别注意,a * b 代表两个向量的按组件乘积,这在线性代数中并不常见。对于矩阵,这些运算符也按组件执行。同样,两个矩阵的按组件乘积在线性代数中并不常见。

对于通常的矩阵-向量积或矩阵-矩阵积,请参见下面的内置 mul 函数。对于向量之间的点积和叉积,请参见下面的内置函数 dotcross

* 运算符也可以用于将浮点数(即标量)乘以向量或矩阵的所有组件(从左或右)

float3 a = float3(1.0, 2.0, 3.0);
float2x2 m = float2x2(1.0, 0.0, 0.0, 1.0);
float s = 10.0;
float3 b = s * a; // float3(10.0, 20.0, 30.0)
float3 c = a * s; // float3(10.0, 20.0, 30.0)
float2x2 m2 = s * m; // = float2x2(10.0, 0.0, 0.0, 10.0)
float2x2 m3 = m * s; // = float2x2(10.0, 0.0, 0.0, 10.0)

内置向量和矩阵函数

[编辑 | 编辑源代码]

按组件函数

[编辑 | 编辑源代码]

以下函数按组件对类型 floatfloat2float3float4halfhalf2half3half4fixedfixed2fixed3fixed4 的变量执行操作,这些变量表示为 TYPE

TYPE min(TYPE a, TYPE b) // returns a if a < b, b otherwise
TYPE max(TYPE a, TYPE b) // returns a if a > b, b otherwise
TYPE clamp(TYPE a, TYPE minVal, TYPE maxVal) 
   // = min(max(a, minVal), maxVal) 
TYPE lerp(TYPE a, TYPE b, TYPE wb) // = a * (TYPE(1.0, 1.0, ...) - wb) + b * wb
TYPE lerp(TYPE a, TYPE b, float wb) // = a * TYPE(1.0 - wb, 1.0 - wb, ...) + b * TYPE(wb, wb, ...)

还有更多内置函数,这些函数也按组件执行,但对向量不太有用,例如 abssignfloorceilroundfracfmodstepsmoothstepsqrtpowexpexp2loglog2radians(将度数转换为弧度)、degrees(将弧度转换为度数)、sincostanasinacosatanatan2

矩阵函数

[编辑 | 编辑源代码]

对于线性代数中通常的矩阵-矩阵积,应使用 mul 函数。它通过将一行(第一个矩阵)乘以一列(第二个矩阵)来计算结果矩阵的组件,例如

  

在Cg中

float2x2 a = float2x2(1., 2.,  3., 4.);
float2x2 b = float2x2(10., 20.,  30., 40.);
float2x2 c = mul(a, b); // = float2x2(
   // 1. * 10. + 2. * 30., 1. * 20. + 2. * 40., 
   // 3. * 10. + 4. * 30., 3. * 20. + 4. * 40.)

此外,mul 函数可以用于对应维度的矩阵-向量乘积,例如:

  

在Cg中

float2 v = float2(10., 20.);
float2x2 m = float2x2(1., 2.,  3., 4.);
float2 w = mul(m, v); // = float2x2(1. * 10. + 2. * 20., 3. * 10. + 4. * 20.)

请注意,向量必须作为第二个参数,才能从右侧乘以矩阵。

如果向量被指定为第一个参数,它将从 **左侧** 乘以矩阵。

float2 v = float2(10., 20.);
float2x2 m = float2x2(1., 2.,  3., 4.);
float2 w = mul(v, m); // = float(10. * 1. + 20. * 3., 10. * 2. + 20. * 4.)

从左侧乘以行向量到矩阵相当于从右侧乘以列向量到 **转置** 矩阵。

用分量表示

        

因此,将一个向量从左侧乘以矩阵,相当于将它从右侧乘以转置矩阵,而无需显式计算转置矩阵。

还有一个函数 transpose 用于计算转置矩阵。

float2x2 m = float2x2(1., 2.,  3., 4.);
float2x2 n = transpose(m); // = float2x2(1., 3., 2., 4)

几何函数

[edit | edit source]

以下函数对于向量运算特别有用。TYPE 可以是以下任意值:float, float2, float3, float4, half, half2, half3, half4, fixed, fixed2, fixed3fixed4(每行只允许一个)。

float3 cross(float3 a, float3 b) 
   // = float3(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 所来自的一侧,即 ni 的点积应该为负。浮点数 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, 0.0, ...); // total internal reflection
return r * i - (r * dot(n, i) + sqrt(d)) * n;

如代码所示,如果发生全内反射 (参见维基百科条目),即光线没有穿过两种材料之间的界面,则该函数将返回一个长度为 0 的向量。

进一步阅读

[edit | edit source]

关于 Cg 的完整描述可以在 Nvidia 的 Cg 教程(包括 附录 E 中的所有标准库函数)和 Nvidia 的 Cg 语言规范 中找到。

< Cg 编程

除非另有说明,本页上的所有示例源代码均为公共领域。
华夏公益教科书