跳到内容

Cg 编程/应用矩阵变换

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

在着色器中应用传统的顶点变换(参见 “顶点变换”部分)或任何其他由矩阵表示的变换,通常通过在着色器的统一变量中指定相应的矩阵,然后将矩阵与向量相乘来完成。但是,在细节上有一些差异。在这里,我们讨论点的变换(即具有第 4 个坐标等于 1 的 4D 向量)、方向的变换(即严格意义上的向量:3D 向量或具有第 4 个坐标等于 0 的 4D 向量)以及表面法向量的变换(即指定与平面正交的方向的向量)。

本节假定您对 Cg 语法有一定的了解,如 “向量和矩阵操作”部分 中所述。

变换点

[编辑 | 编辑源代码]

对于点,变换通常用 4×4 矩阵表示,因为它们可能包括由 3D 向量 t 在第 4 列进行平移

     

(投影矩阵还将在最后一行包含其他不等于 0 的值。)

三维点用具有第 4 个坐标等于 1 的四维向量表示

为了应用变换,矩阵 乘以向量

  

用 4×4 矩阵对由 4D 向量表示的点进行变换的 Cg 代码非常简单

float4x4 mymatrix;
float4 point;
float4 transformed_point = mul(mymatrix, point);

如果给定一个转置矩阵,则可以使用 transpose 函数计算所需的矩阵。但是,更常见的是,如 “向量和矩阵运算”部分 所述,从左侧对转置矩阵进行向量乘法。

float4x4 matrix_transpose;
float4 point;
float4 transformed_point = mul(point, matrix_transpose);

变换方向

[edit | edit source]

三维空间中的方向用 3D 向量或第四个坐标为 0 的 4D 向量表示。(可以将它们视为无穷远处的点;类似于地平线上的一个点,我们无法确定其在空间中的位置,只能确定找到它的方向。)

在 3D 向量的情况下,我们可以通过将其与 3×3 矩阵相乘来进行变换

float3x3 mymatrix;
float3 direction; 
float3 transformed_direction = mul(mymatrix, direction);

或者,如果我们将 3D 向量转换为第四个坐标等于 0 的 4D 向量,则可以使用 4×4 矩阵进行变换

float4x4 mymatrix;
float3 direction;
float4 transformed_direction = mul(mymatrix, float4(direction, 0.0));

或者,也可以将 4×4 矩阵转换为 3×3 矩阵。

另一方面,可以将 4D 向量直接与 4×4 矩阵相乘。也可以将其转换为 3D 向量,以便将其与 3×3 矩阵相乘

float3x3 mymatrix;
float4 direction; // 4th component is 0
float4 transformed_direction = float4(mul(mymatrix, direction.xyz), 0.0);

变换法线向量

[edit | edit source]

与方向类似,表面法线向量(简称“法线向量”)用 3D 向量或第四个分量为 0 的 4D 向量表示。但是,它们的变换方式不同。(数学原因是它们表示一个称为协向量、协变向量、一形式或线性泛函的东西。)

要理解法线向量的变换,请考虑表面法线向量的主要特征:它与表面正交。当然,此特征在变换下仍然成立,即变换后的法线向量应该与变换后的表面正交。如果表面在局部用切线向量表示,则此特征要求变换后的法线向量与变换后的方向向量正交,如果原始法线向量与原始方向向量正交。

数学上来说,法线向量 n 与方向向量 v 正交,如果它们的点积为 0。事实证明,如果 v 通过 3×3 矩阵 进行变换,则法线向量必须通过 的 **转置逆矩阵** 进行变换:。我们可以通过检查变换后的法线向量 n 与变换后的方向向量 v 的点积来轻松测试这一点

                 

第一步中我们用到了 = ,然后 = ,然后 ,然后 (即单位矩阵)。

计算表明,变换后的向量的点积实际上与原始向量的点积相同;因此,变换后的向量是正交的当且仅当原始向量是正交的。这正是它应该做的。

因此,为了在 Cg 中变换法向量,转置逆矩阵通常被指定为一个统一变量(连同用于变换方向和点的原始矩阵),并像任何其他变换一样应用。

float3x3 matrix_inverse_transpose;
float3 normal;
float3 transformed_normal = mul(matrix_inverse_transpose, normal);

对于 4×4 矩阵,可以通过添加 0 将法向量转换为 4D 向量。

float4x4 matrix_inverse_transpose;
float3 normal;
float3 transformed_normal = mul(matrix_inverse_transpose, float4(normal, 0.0)).xyz;

或者,可以将矩阵转换为 3×3 矩阵。

如果已知逆矩阵,则可以从左侧乘以法向量来应用转置逆矩阵,如 “向量和矩阵运算”部分 所述。因此,为了将法向量乘以转置逆矩阵,我们可以从左侧将其乘以逆矩阵。

float3x3 matrix_inverse;
float3 normal;
float3 transformed_normal = mul(normal, matrix_inverse);

在将 4×4 矩阵乘以 4D 法向量(从左侧或右侧)的情况下,应确保所得向量的第 4 个分量为 0。实际上,在某些情况下,需要丢弃计算出的第 4 个分量(例如,通过将结果转换为 3D 向量)。

float4x4 matrix_inverse;
float4 normal;
float4 transformed_normal = float4(mul(normal, matrix_inverse).xyz, 0.0);

请注意,通过这种变换,法向量的单位长度归一化不会保留。因此,法向量通常在变换后归一化为单位长度(例如,使用内置 Cg 函数 normalize)。

用正交矩阵变换法向量

[edit | edit source]

当变换矩阵 是正交的时候,会出现一种特殊情况。在这种情况下, 的逆是转置矩阵;因此, 的逆的转置是两次转置的矩阵,即原始矩阵,即对于正交矩阵

  

因此,对于正交矩阵,法向量用与方向和点相同的矩阵进行变换。

float3x3 mymatrix; // orthogonal matrix
float3 normal;
float3 transformed_normal = mul(mymatrix, normal);

用逆矩阵变换点

[edit | edit source]

有时需要应用逆变换。在大多数情况下,最佳解决方案是为逆矩阵定义另一个统一变量,并在主应用程序中设置逆矩阵。然后,着色器可以像任何其他矩阵一样应用逆矩阵。这比在着色器中计算逆矩阵效率高得多。

但是,存在一个特殊情况:如果矩阵 具有上面所示的形式(即第 4 行为 (0,0,0,1))

具有正交 3×3 矩阵 (即 的行(或列)向量是归一化的并且彼此正交;例如,这通常是视图变换的情况,请参阅 “顶点变换”部分),那么逆矩阵由(因为对于正交矩阵

  

对于用 4D 向量 表示的点 乘以 3D 向量 p ,我们得到

     

注意向量 t 只是矩阵 的第 4 列,可以通过以下方式访问:

float4x4 m;
float4 last_column = float4(m[0][3], m[1][3], m[2][3], m[3][3]);

如上所述,用一个转置矩阵乘以一个向量可以通过将向量从左侧乘以矩阵来轻松表达,这对应于转置矩阵与对应列向量的矩阵向量积

利用 Cg 的这些特性,(p - t) 可以轻松高效地实现,如下所示(注意,结果的第 4 个分量必须单独设置为 1)

float4x4 m; // upper, left 3x3 matrix is orthogonal; 
   // 4th row is (0,0,0,1) 
float4 point; // 4th component is 1
float4 last_columm = float4(m[0][3], m[1][3], m[2][3], m[3][3]);
float4 point_transformed_with_inverse = float4(mul(point - last_column, m).xyz, 1.0);

使用逆矩阵变换方向

[edit | edit source]

与点的情况一样,用逆矩阵变换方向的最佳方法通常是在主应用程序中计算逆矩阵,并通过另一个统一变量将其传递给着色器。

例外情况是正交 3×3 矩阵 (即所有行(或列)都已归一化,并且彼此正交)或形式为 4×4 矩阵

其中 是正交 3×3 矩阵。在这种情况下,逆矩阵 等于转置矩阵

如上所述,在 Cg 中,用转置矩阵乘以向量的最佳方法是从左到右乘以原始矩阵,因为这被解释为行向量与原始矩阵的乘积,对应于转置矩阵与列向量的乘积。

因此,用转置矩阵(即在正交矩阵的情况下为逆矩阵)进行变换写为

float4x4 mymatrix; // upper, left 3x3 matrix is orthogonal
float4 direction; // 4th component is 0
float4 direction_transformed_with_inverse = float4(float3(mul(direction, mymatrix)), 0.0);

请注意,结果的第 4 个分量必须单独设置为 0,因为 mul(direction, mymatrix) 的第 4 个分量对于方向的变换没有意义。(但是,它对于平面方程的变换是有意义的,这里没有讨论。)

3x3 矩阵和 3D 向量的版本只需要在 3D 和 4D 向量之间进行不同的强制转换操作。

用逆变换变换法线向量

[edit | edit source]

假设逆矩阵 可用,但需要对应于 的变换。此外,我们希望将此变换应用于法线向量。在这种情况下,我们可以通过从左到右乘以法线向量到逆矩阵(如上所述)来应用逆矩阵的转置

float4x4 matrix_inverse;
float3 normal;
float3 transformed_normal = mul(float4(normal, 0.0), matrix_inverse).xyz;

(或通过强制转换矩阵)。

进一步阅读

[edit | edit source]

法线向量的变换也描述在“OpenGL 4.5 Compatibility Profile Specification”的第 12.1.2 节中,该规范可在 Khronos OpenGL 网站 上获得。

“OpenGL Programming Guide”的免费 HTML 版本的附录 F 中提供了更易于理解的法线向量变换描述,可在 网上 获得。

< Cg 编程

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