Android OpenGL ES 2.0(九)---法线矩阵

本文从下面链接翻译过来:

第一篇 Normal Transformation
第二篇 The Normal Matrix

关于法线矩阵的计算方式个人觉得上面两篇文章讲得比较好,下面依次对两篇文章进行翻译,先来翻译第一篇文章。

第一篇 法线变换

在计算光照时,关于入射角的余弦值计算有一定的要求。它涉及到两个向量,光照的方向向量以及顶点的法线向量,并且这两个向量都是单位向量。

我们来看这样一个例子,2D圆。 我们可以将非均匀比例缩放(不同轴上的缩放比例不一样)应用于此圆上的顶点座标,将其转换为椭圆:

图1.圆形缩放

这一切看起来都很好,但考虑到法线转换的情况:

中间椭圆的顶点法线计算方式如果你使用跟圆顶点变换相同的矩阵来计算的话,椭圆的法线可能会是单位长度的向量,但是它不会再反射出椭圆的形状。右边椭圆反映出实际形状的法线。

事实证明,你真正想做的是用与位置相同的旋转矩阵来转换法线,但是缩放需要使用缩放矩阵的逆矩阵来转换法线。也就是说,沿X轴的0.5的比例将使该轴上的位置缩小一半。对于顶点的法线,您希望将法线的X值加倍,然后对结果法向量单位化处理。

如果你的应用程序只有一个简单的缩放矩阵,这很容易。但是,如果你的应用程序由多个连续旋转,缩放和其他操作组成的复杂的矩阵并不那么容易计算。

我们可以使用矩阵的逆转置来计算法线的变换矩阵。这意味着我们首先计算逆矩阵,然后计算该逆矩阵的转置。矩阵的转置就是沿对角线翻转的相同矩阵。原始矩阵的列是其转置后的行。

图2.矩阵的转置

那么这个逆转置矩阵如何帮助我们呢?

记住:我们想要的是复合矩阵中缩放矩阵的逆矩阵而不影响旋转矩阵部分。给定一个仅由旋转和缩放变换组成的3x3矩阵M,我们可以表达这个矩阵,如下所示:

这个矩阵表达了一个复合矩阵的变换,先进行一个旋转变换R1,然后在进行缩放变换S,最后在进行旋转变换R2。无论使用多少个缩放和旋转矩阵来构建M,我们都可以做到这一点。

回想一下,我们想要的是缩放矩阵的逆矩阵。如果我们在原始缩放矩阵的缩放为0.4,则我们希望在逆矩阵缩放为2.5。缩放矩阵的逆矩阵其实是该矩阵每个轴上的缩放比例的倒数。 因此,我们可以表达我们真正想要的法线变换矩阵:

关于旋转矩阵的一个有趣的事实:任何旋转矩阵的逆是它的转置。 此外,如果对一个矩阵进行连续两次逆变换的结果会得到原始矩阵。因此,您可以将任何旋转矩阵表示为其自身的反转置,而不会影响矩阵。 由于逆是它的转置,并且在矩阵上进行两次转置不会改变其值,因此旋转矩阵的逆转置是无操作。

此外,由于缩放矩阵的值是在对角线上的,因此对缩放矩阵进行转置操作不起作用,还是缩放矩阵本身。有了这两个事实,我们可以重新表达我们想要计算的矩阵:

使用矩阵代数的知识,我们可以将转置因子排除在外,但这样做需要颠倒矩阵乘法的顺序:

类似地,我们可以分解逆运算,但这需要再次颠倒顺序:

因此,逆转置矩阵解决了我们的问题。GLSL有内置的功能,可以为我们做这些操作。 虽然你能避免在GLSL中进行反转置,但强烈建议你这样做; 这不是一个微不足道的计算。

还有一点需要注意。只有在使用非均匀的缩放比例时才需要进行反转置。 实际上,使用这种缩放比例因子实际上很少见。均匀的缩放比例更常用。 因此,即使您没有使用反转置,您仍然需要在使用模型视图矩阵到相机矩阵进行变换后对法线进行标准化。

 

第二篇 法线矩阵

许多计算都是在眼睛空间完成的。这与通常在该空间中执行光照的计算有关,相反的由于需要依赖于眼睛位置,否则镜面光很难实现。

因此,我们需要一种方法将法线转换为眼睛空间。 要将顶点转换为眼睛空间,我们可以编写:

vertexEyeSpace = gl_ModelViewMatrix * gl_Vertex;

那么为什么我们不能用法线量做同样的事情呢? 首先法线是3个浮点数的向量,模型视图矩阵是4×4。 其次,由于法线是一个向量,我们只想转换它的方向。包含方向的模型视图矩阵的区域是左上3×3子矩阵。 那么为什么不通过这个子矩阵乘以法线呢?

使用以下代码可以轻松实现这一点:

normalEyeSpace = vec3(gl_ModelViewMatrix * vec4(gl_Normal,0.0));

那么,GLSL的内置变量gl_NormalMatrix只是简化代码编写吗? 不,不是真的。 上面的代码行在某些情况下会起作用,但不是全部。

让我们看一下潜在的问题:

在上图中,我们看到一个三角形,带有法线和切线向量。下图显示了当modelview矩阵包含非均匀比例的缩放矩阵时会发生什么。

注意:如果缩放矩阵在所有轴上的缩放是均匀的,那么法线的方向将不会改变,只是法线的长度会受到影响,但这可以通过将法线单位化来进行修正。

在上图中,Modelview矩阵应用于所有顶点以及法线,结果显然是错误的,变换后的法线不再垂直于表面。

我们知道向量可以表示为两点之间的差。 考虑切线向量,可以将其计算为三角形边缘的两个顶点之间的差。 如果P1和P2是定义边的顶点,我们知道:

T = P2 - P1

考虑到向量可以写成四元组,最后一个设置为零,我们可以将等式的两边乘以Modelview矩阵

T * Modelview = (P2-P1)* Modelview

这导致了:

T * Modelview = P2 * Modelview - P1 * Modelview
T' = P2' - P1'

由于P1'和P2'是变换之后三角形的顶点,因此T'保持与三角形边缘相切。 因此,Modelview保留切线,但它不保留法线。

考虑到用于向量T的相同方法,我们可以找到两个点Q1和Q2

N = Q2 - Q1 

主要问题是通过转换之后点Q2' -  Q1'定义的向量不一定保持正确的法线,如上图所示。

所以现在我们知道我们不能在所有情况下应用Modelview来转换法向量。那么问题是,我们应该应用什么矩阵?

考虑一个3×3矩阵G,让我们看看如何计算这个矩阵来正确地转换法向量。

我们知道,根据向量垂直的定义,在矩阵变换之前T.N = 0,那么在变换之后N'.T'必须也保持等于零,因为它们必须保持彼此垂直。 T可以通过乘以模型视图的左上3×3子矩阵得到(T是向量,因此w分量为零),让我们称之为子矩阵M.

假设矩阵G是用于变换法向量的正确矩阵。因此,以下等式:

N' . T' = (GN) . (MT) = 0

向量的点积可以转换为矩阵的乘积,因此:

(GN).(MT) = (GN)^T * (MT)

注意,必须考虑第一个向量的转置,因为这需要乘以向量。 根据矩阵转置乘法的相关知识,上面等式与可以转为下面等式

(GN)^T  (MT) = N^TG^TMT

因为

N.T = N^T * T 

所以结合前面等式可以推到出

G^TM = I

其中I是单位矩阵,所以矩阵G最终结果如下:

G^TM = I -> G = (M^{-1})^T

因此,转换法线的正确矩阵是M矩阵的逆的转置。 OpenGL在gl_NormalMatrix中为我们计算。

在本节的开头,说道有时候使用Modelview矩阵在某些情况下会起作用。 每当Modelview的3×3左上子矩阵正交时,我们都有:

M^{-1} = M^T -> G = M 

这是因为对于正交矩阵,转置与逆矩阵相同。 什么是正交矩阵? 正交矩阵是所有列/行是单位长度,并且是相互垂直的。 这意味着当两个向量乘以这样的矩阵时,在通过正交矩阵变换之后它们之间的角度与该变换之前的角度相同。 简单地说,变换保留了向量之间的角度关系,因此变换后的法线保持垂直于切线! 此外,它还保留了向量的长度。

那么我们何时可以确定M是正交的? 当我们将几何操作限制为旋转和平移时,即在OpenGL应用程序中,我们只使用glRotate和glTranslate而不是glScale。 这些操作保证M是正交的。 注意:gluLookAt还会创建一个正交矩阵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章