GLSL 编程/应用矩阵变换
在着色器中应用传统的顶点变换(参见 “顶点变换”部分)或任何其他由矩阵表示的变换通常通过在着色器的统一变量中指定相应的矩阵,然后将该矩阵乘以一个向量来完成。然而,在细节上有一些差异。这里,我们讨论点的变换(即 4D 向量,其第 4 个坐标等于 1),方向的变换(即严格意义上的向量:3D 向量或第 4 个坐标等于 0 的 4D 向量),以及表面法向量的变换(即指定与平面正交的方向的向量)。
本节假设您对 “向量和矩阵运算”部分 中描述的 GLSL 语法有所了解。
对于点,变换通常用 4×4 矩阵表示,因为它们可能包括 3D 向量 t 在第 4 列的平移
(投影矩阵还将在最后一行包含其他不等于 0 的值。)
三维点用第 4 个坐标等于 1 的四维向量表示
为了应用变换,矩阵 乘以向量
使用 GLSL 代码将 4x4 矩阵应用于由 4D 向量表示的点非常简单。
mat4 matrix;
vec4 point;
vec4 transformed_point = matrix * point;
变换方向
[edit | edit source]三维空间中的方向可以用 3D 向量或第四个坐标为 0 的 4D 向量表示。(可以将它们视为无穷远处的点,类似于地平线上的一点,我们无法确定其空间位置,只能确定其方向。)
对于 3D 向量,我们可以使用 3x3 矩阵变换它
mat3 matrix;
vec3 direction;
vec3 transformed_direction = matrix * direction;
或者使用 4x4 矩阵,如果我们将 3D 向量转换为第四个坐标为 0 的 4D 向量。
mat4 matrix;
vec3 direction;
vec3 transformed_direction = vec3(matrix * vec4(direction, 0.0));
或者,也可以将 4x4 矩阵转换为 3x3 矩阵。
另一方面,4D 向量可以直接乘以 4x4 矩阵。也可以将其转换为 3D 向量,以便使用 3x3 矩阵进行乘法。
mat3 matrix;
vec4 direction; // 4th component is 0
vec4 transformed_direction = vec4(matrix * vec3(direction), 0.0);
变换法向量
[edit | edit source]与方向类似,表面法向量(或简称为“法向量”)由 3D 向量或第四个分量为 0 的 4D 向量表示。但是,它们变换方式不同。(数学原因是它们表示的是协向量、协变向量、一形式或线性泛函。)
要理解法向量的变换,请考虑表面法向量的主要特征:它与表面正交。当然,这种特征在变换后仍然应该成立,即变换后的法向量应该与变换后的表面正交。如果表面由切向量局部表示,则此特征要求变换后的法向量与变换后的方向向量正交,如果原始法向量与原始方向向量正交。
从数学角度讲,法向量 n 与方向向量 v 正交,如果它们的点积为 0。事实证明,如果 v 由 3x3 矩阵 变换,则法向量必须由 的 **转置逆矩阵** 变换: 。我们可以通过检查变换后的法向量 n 和变换后的方向向量 v 的点积来轻松测试这一点。
第一步,我们使用了 = ,然后 = ,然后 ,然后 (即单位矩阵)。
计算表明,变换后的向量的点积实际上与原始向量的点积相同;因此,变换后的向量是正交的,当且仅当原始向量是正交的。正如预期的那样。
因此,为了在 GLSL 中变换法向量,转置逆矩阵 通常被指定为一个 uniform 变量(以及用于变换方向和点的原始矩阵),并像任何其他变换一样应用。
mat3 matrix_inverse_transpose;
vec3 normal;
vec3 transformed_normal = matrix_inverse_transpose * normal;
对于一个 4x4 矩阵,法向量可以通过添加 0 来转换为一个 4D 向量。
mat4 matrix_inverse_transpose;
vec3 normal;
vec3 transformed_normal = vec3(matrix_inverse_transpose * vec4(normal, 0.0));
或者,矩阵可以转换为一个 3x3 矩阵。
如果逆矩阵已知,法向量可以从左侧乘以应用转置逆矩阵。一般来说,用向量乘以转置矩阵可以通过将向量放在矩阵左侧来轻松表示。原因是向量-矩阵乘积只对行向量(即转置列向量)有意义,并且对应于转置矩阵与相应列向量的矩阵-向量乘积。
由于 GLSL 不区分列向量和行向量,因此结果只是一个向量。
因此,为了用转置逆矩阵乘以法向量,我们可以从左侧乘以逆矩阵。
mat3 matrix_inverse;
vec3 normal;
vec3 transformed_normal = normal * matrix_inverse;
对于将 4x4 矩阵乘以 4D 法向量(从左侧或右侧),应确保结果向量的第 4 个分量为 0。事实上,在某些情况下,需要丢弃计算出的第 4 个分量(例如,通过将结果转换为 3D 向量)。
mat4 matrix_inverse;
vec4 normal;
vec4 transformed_normal = vec4(vec3(normal * matrix_inverse), 0.0);
请注意,对法向量进行任何单位化操作都不会保留这种变换。因此,法向量通常在变换后被单位化(例如,使用内置 GLSL 函数 normalize
)。
当变换矩阵 是正交的时候,就会出现一种特殊情况。在这种情况下, 的逆矩阵是转置矩阵;因此, 逆矩阵的转置是两次转置的矩阵,即原始矩阵,也就是说,对于一个正交矩阵
因此,在**正交**矩阵的情况下,法向量会与方向和点使用相同的矩阵进行变换。
mat3 matrix; // orthogonal matrix
vec3 normal;
vec3 transformed_normal = matrix * normal;
有时需要应用逆变换。在大多数情况下,最佳的解决方案是为逆矩阵定义另一个统一变量,并在主应用程序中设置逆矩阵。然后,着色器可以像任何其他矩阵一样应用逆矩阵。这比在着色器中计算逆矩阵效率高得多。
但是,有一个特殊情况:如果矩阵 具有上面显示的形式(即第 4 行为 (0,0,0,1))
其中 是一个 3×3 正交矩阵(即 的行向量(或列向量)是归一化且相互正交的;例如,这通常是视图变换的情况,请参阅“顶点变换”部分),则逆矩阵由下式给出(因为 对于正交矩阵 )
对于使用点 进行的乘法,该点由 4D 向量 表示,其中 3D 向量 p ,我们得到
请注意,向量t 只是矩阵 的第 4 列,它可以在 GLSL 中方便地访问。
mat4 matrix;
vec4 last_column = matrix[3]; // indices start with 0 in GLSL
如上所述,将转置矩阵与向量相乘可以很容易地用将向量放在矩阵左侧来表示,因为向量-矩阵乘积只有对行向量(即转置列向量)才有意义,并且对应于转置矩阵与相应列向量进行矩阵-向量乘积。
利用 GLSL 的这些特性,项 (p - t) 可以用以下方式轻松高效地实现(请注意,结果的第 4 个分量必须单独设置为 1)。
mat4 matrix; // upper, left 3x3 matrix is orthogonal;
// 4th row is (0,0,0,1)
vec4 point; // 4th component is 1
vec4 point_transformed_with_inverse = vec4(vec3((point - matrix[3]) * matrix), 1.0);
使用逆矩阵变换方向
[edit | edit source]与点的情况类似,使用逆矩阵变换方向的最佳方法通常是在主应用程序中计算逆矩阵,并通过另一个统一变量将其传递给着色器。
例外情况是正交 3×3 矩阵 (即所有行(或列)都已归一化并且彼此正交)或形式为 的 4×4 矩阵
其中 是正交 3×3 矩阵。在这些情况下,逆矩阵 等于转置矩阵 。
如上所述,在 GLSL 中,用向量乘以转置矩阵的最佳方法是从左侧乘以原始矩阵,因为这被解释为行向量与原始矩阵的乘积,它对应于转置矩阵与列向量的乘积。
因此,用转置矩阵(即正交矩阵的情况下的逆矩阵)进行变换写成
mat4 matrix; // upper, left 3x3 matrix is orthogonal
vec4 direction; // 4th component is 0
vec4 direction_transformed_with_inverse = vec4(vec3(direction * matrix), 0.0);
请注意,结果的第 4 个分量必须单独设置为 0,因为 direction * matrix
的第 4 个分量对于方向的变换没有意义。(然而,对于此处未讨论的平面方程的变换,它是有意义的。)
针对 3x3 矩阵和 3D 向量的版本只需要在 3D 和 4D 向量之间进行不同的强制转换操作。
使用逆变换变换法向量
[edit | edit source]假设逆矩阵 可用,但需要与 对应的变换。此外,我们想将这种变换应用于法向量。在这种情况下,我们只需通过从左边将法向量乘以逆矩阵来应用逆矩阵的转置(如上所述)
mat4 matrix_inverse;
vec3 normal;
vec3 transformed_normal = vec3(vec4(normal, 0.0) * matrix_inverse);
(或通过转换矩阵)。
内置矩阵变换
[edit | edit source]一些框架(特别是 OpenGL 兼容性配置文件,但既不是 OpenGL 核心配置文件也不是 OpenGL ES 2.x)在 GLSL 着色器中提供了一些内置的制服来访问某些顶点变换。它们不应该被声明,但这里有声明来指定它们的类型
uniform mat4 gl_ModelViewMatrix;
uniform mat4 gl_ProjectionMatrix;
uniform mat4 gl_ModelViewProjectionMatrix;
uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords];
uniform mat3 gl_NormalMatrix; // transpose of the inverse of the
// upper left 3x3 matrix of gl_ModelViewMatrix
uniform mat4 gl_ModelViewMatrixInverse;
uniform mat4 gl_ProjectionMatrixInverse;
uniform mat4 gl_ModelViewProjectionMatrixInverse;
uniform mat4 gl_TextureMatrixInverse[gl_MaxTextureCoords];
uniform mat4 gl_ModelViewMatrixTranspose;
uniform mat4 gl_ProjectionMatrixTranspose;
uniform mat4 gl_ModelViewProjectionMatrixTranspose;
uniform mat4 gl_TextureMatrixTranspose[gl_MaxTextureCoords];
uniform mat4 gl_ModelViewMatrixInverseTranspose;
uniform mat4 gl_ProjectionMatrixInverseTranspose;
uniform mat4 gl_ModelViewProjectionMatrixInverseTranspose;
uniform mat4 gl_TextureMatrixInverseTranspose[gl_MaxTextureCoords];
进一步阅读
[edit | edit source]法向量变换在“OpenGL 4.1 兼容性配置文件规范”的第 2.12.2 节中进行了描述,该规范可从 Khronos OpenGL 网站 获取。
对法向量变换更易于理解的描述在“OpenGL 编程指南”的免费 HTML 版本的附录 E 中给出,该版本可在 网上 获得。
矩阵变换的内置制服在“OpenGL 着色语言 4.10.6 规范”的第 7.4.1 节中进行了描述,该规范可从 Khronos OpenGL 网站 获取。
< GLSL 编程