切线空间解决的问题
在使用法线贴图的时候,如果法线贴图中保存的值是在模型空间中的,在渲染过程中,模型往往会产生旋转,平移,缩放等变换,这就导致法线贴图在贴到模型表面时,只能在某个朝向上生效,其他朝向会得到错误的结果。为了解决这个问题,就引入了切线空间,以模型顶点法线为正Z轴,法线贴图中保存的法线值改为相对于顶点的偏移方向。这样一来,法线贴图中只存了一个偏移量。适当地计算切线空间相关的变换矩阵即可将法线变换到模型空间,或者将模型空间的方向,变换到切线空间中。在同一个空间下的量,就可以用来进行着色计算了。
切线空间定义
切线空间是位于三角形表面之上的空间。
这句话应该这么理解,在模型表面网格上,每一个三角面都可以定义自己的切线空间,对于单个三角面来说,可以计算一个面法线N,那么为了定义一个空间,还需要在三角面所在平面上取相互垂直的T轴和B轴。很显然,这样的解是无穷多个的,其实任意选择一组都可以,但是为了计算简单,就利用纹理坐标进行计算,使T轴与U轴对齐,B轴与V轴对齐。
切线空间的TBN矩阵的计算方法
初始输入数据
- 三角形三个顶点坐标(模型空间)
- 三角形三个顶点的UV坐标
计算步骤
对于单个三角面的切线空间的计算是比较简单的。因为单个三角面的所有顶点的法线都是这个面法线。那么只需要通过下面这个公式,计算出于UV轴对齐的切线和副切线,基于施密特正交化方法,结合面法线可以获得最终的切线和副切线。而对于多个三角面共享的同一个顶点,就需要引入一些加权平均算法来构造切线空间了。
中间向量TB的推导
计算公式和图直接搬自《Learn OpenGL》,包括图和公式。如下图所示,输入顶点和分别是、、,UV坐标分别是、、。边和的计算如下:
以为例,下图中,纹理坐标的差值
在T轴上,在B轴上。可以列出下面的等式:
把数值精细到值。如下:
两个未知数,两个方程,即可求解T轴和B轴。写成矩阵的表示形式如下:
变换一下,得到下面的形式:
逆矩阵的计算,可以用1除以矩阵的行列式,再乘以它的伴随矩阵计算出来。如下:
这样就可以计算出切线向量和副切线向量
让我们也针对这个公式来做一些实验,只对单个三角面做计算处理,选取其中某些点的计算结果进行可视化。得到下面这张图,蓝色的是切线,绿色是福切线,黄色是顶点法线。红色标注的应该是对应的三角面。这里是用的一个立方体网格来做的试验。如果选择其他复杂的网格,那么得到的切线和副切线并不总是与顶点法线垂直,甚至切线和副切线也不是垂直的,但是和面法线是垂直的。和顶点法线不垂直的原因是Unity中Mesh里的顶点法线是经过加权平均了的,和面法线垂直的原因是切线和副切线就在单个三角面内。

我们把Unity的计算结果和我们的计算结果可视化出来比对,发现确实是有偏差的。下图中,红色是Unity计算的结果,其他颜色是我们自己计算的结果。还是由于只是计算了单个面的切线和副切线,所以存在偏差很正常,而且Unity的顶点法线是经过了某种加权平均算法得到的结果,和单个面法线之间肯定存在偏差。更进一步地,我以Unity计算的法线和单个三角面计算得到的切线和副切线做一次施密特正交化,得到的结果仍然是有差的。因此,我猜在计算平均法线的时候,对切线也可以做相应地平均操作,最后再进行施密特正交化步骤。得到的结果应该会差不多。

那么最后讨论一个问题,至此,我们根据上面的计算公式,对单个三角面计算得到与UV轴对齐的切线和副切线,但是并不互相垂直。如果我们要为单个顶点计算平均的顶点法线以及切线和副切线,就需要计算出所有共享了这个顶点的三角面的法线、切线和副切线。然后再分别进行加权平均,最后施密特正交化构造最终的TBN矩阵。到这一步的时候,我们会发现上面的计算公式只是计算了一个中间结果,而最终的切线和副切线并不与UV对齐。所以应该有一个结论,最终的切线空间,并不与UV轴对齐,对齐只是计算了一个中间结果,这一点应该是可以肯定的了。
对于法线加权平均算法,我只做了简单的了解,大概是计算权值时,考虑三角面的面积。使得最终平均的法线不会被较小面积的三角面影响。关于这方面的内容,只简单提及,不做深入的探讨。
施密特正交化
施密特正交化的计算方法,需要输入两个向量,它使得输出向量和输入的N向量,能够保持垂直。我们从公式开始介绍,然后再介绍公式的几何意义。假设施密特正交化的输入为两个方向向量,和,是空间中任意的方向,归一化是不必要的。根据下面的公式,可以计算得到\vec{T^'},使\vec{T^'}与垂直。
最初看这个公式的时候,理解起来比较费劲。如果只看公式,公式的右边,是两个向量减的结果,而我希望把这个公式化繁为下面的形式。因为向量的减法在几何表示上不是特别直观,而向量的加法,是只需要经过简单思考就可以想象出来的一个过程。所以下面的公式把减法,变成了加负的形式。
针对这个公式,首先先点乘,点乘的结果是向量在向量上的投影长度,然后乘以向量,加号的右边,就相当于计算了一个偏移,然后对向量做一次偏移操作,得到的向量\vec{T^{'}和就是垂直的了。它的几何意义,在2D平面上,如下图所示:

推广到三维空间中,该公式就是在和所构成的平面上,将向量偏移到和向量垂直的位置。而且最终计算出的向量的方向,与是在同一侧。
应用到切线空间的计算中,我们首先会基于某种平均算法,计算出顶点法线和顶点切线。然后以顶点法线为基准,对顶点切线进行修正。然后取二者叉乘的结果作为副切线,至此,计算得到的顶点法线、顶点切线和副切线,就构成了切线空间的正交基。这是定义在模型空间中的。
TBN矩阵的计算方法
TBN矩阵是一个能够将模型空间中的点变换到切线空间中的变换矩阵。它由三个正交基构成,即通过上面的方法计算得到的正交向量。使用切线空间的应用场景,要么是将切线空间的法线计算到模型空间,要么是将模型空间的方向变换到切线空间中。那么,所谓TBN矩阵可以实现第一种变换,即切线空间到模型空间的转换。而将模型空间中的方向变换到切线空间中,就需要计算TBN矩阵的逆矩阵即可,根据线性代数的知识,正交矩阵的逆矩阵,即为它的转置矩阵。
当有了切线向量,副切线向量和顶点法线方向,对于单个顶点的TBN矩阵就很容易计算,在一个3x3的矩阵中,把这三个向量逐列摆放即可。有的坐标系可能是按行摆放,这需要一些推到过程,可以参考《Unity Shader入门精要》中第4.6.2节介绍的内容。
参考资料
[法线贴图 - LearnOpenGL CN (learnopengl-cn.github.io)](learnopengl-cn.github.io/05 Advanced Lighting/04 Normal Mapping/)
切线空间详解 - Alphuae - 博客园 (cnblogs.com)
切线空间(Tangent Space)完全解析 - 哔哩哔哩 (bilibili.com)
游戏编程:切线空间(Tangent Space)-王其光博客 (wangqiguang.work)
切线空间(Tangent Space) 的计算与应用_sork的博客-CSDN博客_切线空间
为什么要有切线空间(Tangent Space),它的作用是什么? - 知乎 (zhihu.com)
《Unity Shader入门精要》