天天看点

GLSL 其他说明

法线矩阵

在很多顶点shader中都用到了gl_NormalMatrix。这里将介绍这个矩阵是什么,以及它的作用。

大部分计算是在视图空间内完成的,主要原因是光照的运算要放在这个空间内,否则一些依赖观察点坐标的效果,比如镜面反射光就很难实现。

所以我们需要将法线变换到视图空间。变换一个顶点到视图空间的方法如下:

vertexEyeSpace = gl_ModelViewMatrix * gl_Vertex;

对法线也能如此操作吗?一个法线是3个浮点数组成的向量,而模型视图矩阵是一个4×4的矩阵。另外,因为法线是一个向量,我们只需要变换它的方向,而模型视图矩阵中左上方的3×3子矩阵正好包含了旋转变换。所以可不可以用法线来乘这个子矩阵呢?

下面的代码很简单地实现了这个要求:

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

这样的话,gl_NormalMatrix还有什么用呢?只是为了简化代码书写吗?实际当然不是这么简单,上面的代码在某些情况下是有效的,但不能应对所有情况。

让我们看看潜在的问题:

GLSL 其他说明

在上图中我们可以看到一个三角面以及它的法线和切线。下图显示了如果模型视图矩阵包含非一致缩放(non-uniform scale)的话会发生什么。

GLSL 其他说明

注意,如果缩放是一致的(uniform),那么法线方向保持不变,变的只是长度,而且可以通过归一化修正这个影响。

在上图中,模型视图矩阵应用到所有顶点以及法线上,最后的结果明显错误:法线不再与三角面垂直了。

现在我们知道模型视图矩阵在某些情况下,不能用来变换法线向量。下面的问题就是:那么该使用哪个矩阵?

考虑一个3×3矩阵G,让我们看看要正确变换法线,这个矩阵该是什么样子。

我们知道,变换前切线和法线是垂直的,即T•N = 0,在变换后切线和法线同样应该保持垂直,即T’•N’ = 0。现在假设G是正确变换法线的矩阵,同时模型视图矩阵的左上3×3子矩阵M可以正确变换切线T(T是一个向量,所以w成分为0)。因为T可以通过两个顶点的差来计算,所以变换顶点的矩阵同样可以用来变换T。由此可以得到如下等式:

GLSL 其他说明

向量的点乘相当于向量的内积,所以有:

GLSL 其他说明

我们知道相乘的转置等于分别转置再交换顺序相乘:

GLSL 其他说明

已知N和T点乘结果为0,所以如果下式成立就可以满足等式为0:

GLSL 其他说明

即有:

GLSL 其他说明

可见变换法线的正确矩阵是M的逆的转置。OpenGL计算出的这个矩阵就保存在gl_NormalMatrix里。

在本节开始讨论过,某些情况下使用模型视图矩阵也可以。当模型视图矩阵的左上3×3子矩阵M正交时,可以得到:

GLSL 其他说明

一个正交矩阵的所有行/列都为单位向量,并且互相正交。当两个向量乘上正交矩阵时,它们之间的夹角在变换前后不变。由于这种保角变换的关系,所以法线和切线依然保存垂直。此外,向量的长度也保持不变。

M在什么时候能确定为正交的呢?当我们把几何变换限制为旋转和平移时(在OpenGL应用程序中只使用glRotate和glTranslate,而不使用glScale),就可以保证M正交。注意:gluLookAt同样建立正交矩阵。

注意,如果顶点法线没有单位化,那么得到的插值法线的方向也将是错误的。所以,即使一个顶点没有在顶点shader用到,也可能要对它在顶点shader中进行归一化。

有一种情况,在片断shader中可以避免归一化操作,那就是每个顶点法线方向相同,而且顶点法线是经过归一化的。此时顶点法线插值得到的结果都相同。

以方向光为例,每个片断都需要考虑光线方向,如果光线向量已经在之前归一化了,在片断shader中就可以避免归一化这一步。

上一篇: PHP 查询脚本
下一篇: HTML其他概念