1.为什么法线要保存在切空间中?
我们知道光照计算,只要保证各个向量在同一个空间坐标系下就可以算出正确的光照强度,那为什么我们在写shader的时候会在切空间进行光照计算呢?
在回答这个问题之前,我们先来看看为什么法线要存储在切空间中。我们用排除法来说明这个问题,如果法线存储在世界坐标系中,只要物体在世界空间中发生了位置变化,这张法线贴图就失效了。为了解决这个问题我们可以把法线存储在模型空间中,就和模型的顶点一样,当模型发生旋转,平移的时候,法线只需要乘上相应的变换矩阵就可以转换到世界坐标系下。这看起来很完美,但是我们忽略了一个问题。假设我们的模型都是刚体的话,这个方案没有问题。但是如果模型做了变形(想象一下皮球被按出一个坑)那每个顶点的法线该乘以一个什么样的矩阵才能转化到世界空间呢?
顶点从模型空间转换到世界空间,其实就是通过平移,旋转,缩放,三个因素确定一个变换矩阵,然后顶点乘以这个矩阵就转换到世界空间中了。但是皮球变形,这个即不是平移,也不是旋转,也不是缩放,之前的变换矩阵失效了。我们能不能找到一个变换矩阵来模拟变形的过程呢?答案是能,但不是一个变换矩阵,而是针对每个顶点的变换矩阵。这块有点抽象,我们回想一下,法线贴图中的每一个点都是一个向量,之前我们把法线存储在模型空间中,每个像素点就对应了模型空间中的一个向量。当模型发生移动时,每个顶点都会发生相同的移动,所以用一个变换矩阵就可以变换贴图中所有的点。但是模型做了变形,每个顶点的移动是不同的,这个时候就不能用一个变换矩阵去处理所有法线了。理所当然,我们要为每一个顶点寻找一个新的坐标系,也就是说法线贴图上的每一个像素点的坐标系都是不一样的。这个坐标系就是顶点的切空间。如果要把法线转换到世界空间中,它的变换过程就是切空间->模型空间->世界空间。而之前我们的转换过程是模型空间->世界空间。总结一下使用切空间法线的优缺点:
优点:
1.支持模型变形动画
2.可压缩,由于Tangent-Space Normal Map中法线的Z方向总是正方向的,因此我们可以仅存储XY方向,而推导得到Z方向。
3.可复用,比如,一个砖块,我们可以使用一张Normal Map,应用到不同的面上。
缺点:
1.空间变换需要额外计算TBN旋转矩阵
回答了法线为什么要存储到切空间,就可以回答为什么我们做光照计算要放到切空间中了。因为如果放到世界空间每个顶点的法线都需要转换到世界空间然后再做计算,而在切空间计算只需要把光源向量和视角向量转换到切空间就可以了。
2.法线的切空间是怎么计算出来的?
我们知道法线是垂直于三角面的一个向量,我们用N来表示法线,用T来表示切线,B来表示副切线。有了三个正交向量就能组成一个新的空间,TBN这个空间就是法线的切空间。
现在已知N这个向量,能和N组成正交向量的T,B,在空间上有无数种组合,我们应该使用哪一种组合呢?其实无论哪种组合都是可以的。我们之所以选出一种组合,是为了达成一种约定,无论是什么软件大家在计算切空间的时候都按照这种约定去计算,这样大家计算出来的结果是一致的。这样即便顶点中没有附带T,B信息,游戏引擎依然可以计算出对应的切空间。让我们看看都有哪些约定。
1.已知三角形的三个顶点在模型空间的坐标A(x0,y0,z0), B(x1,y1,z1), C(x2,y2,z2)
2.已知三个顶点在纹理UV空间中的坐标<s0, t0>, <s1, t1>, <s2, t2>
3.已知法线向量N(模型空间)
4.已知B,T和三角形共面
我们需要通过以上条件计算出一种“约定”的唯一解B,T,N。为什么叫约定的唯一解呢?因为上面的四组数据如果不约定的话是不能求出唯一解。而这个约定就是下面的代数公式。查看了很多文章,所有文章的公式推导都是基于下面两个代数方程。但是这两个方程是怎么来的,没有人回答,如果有大神知道的话还请赐教。我粗超的理解是,下面两个公式很像共面向量定理,也就是说E1,E2,T,B这四个向量共面。根据定理p=xa+yb,我们已知了x,y求a,b。这里的x,y的取值就是一种约定。
E1 = ΔU1T + ΔV1B
E2 = ΔU2T + ΔV2B
接下来的推导过程网上有很多,我就转载了一份附上转载链接:
https://blog.csdn.net/huangzhipeng/article/details/52915724
令ABC内的某个点为Q,其uv坐标为<s, t>, 让Q用如下代数式表示:
Q - A = (s - s0) * T + (t - t0) * B
已经知道了B, C两点,所以有方程组:
B - A = (s1 - s0) * T + (t1 - t0) * B (1)
C - A = (s2 - s0) * T + (t2 - t0) * B (2)
如果T, B是可以求出来的,那么就说明是可以推导出的。
先求T,由(1)&(2)得:
==>(B - A) * (t2 - t0) = (t2 - t0) * (s1 - s0) *T+ (t2 - t0) * (t1 - t0) * B (3)
(C - A)* (t1 - t0) = (t1 - t0) * (s2 - s0) *T+ (t2 - t0) * (t1 - t0) * B (4)
==>(B - A) * (t2 - t0) - (C- A) *(t1 - t0) = [ (t2 - t0) * (s1 - s0) - (t1 - t0) * (s2 - s0) ] *T
==>T = ( (B - A) * (t2 - t0) - (C-A) *(t1 - t0) ) / ( (t2 - t0) * (s1 - s0) - (t1 - t0) * (s2 - s0)) (5)
再求B, 由(1)&(2)得:
==>(B - A) * (s2 - s0) = (s1 - s0) * (s2 - s0) *T + (t1 - t0) * (s2 - s0) * B (6)
(C - A) * (s1 - s0) = (s1 - s0) * (s2 - s0) *T + (t2 - t0) * (s1 - s0) * B (7)
==>(B - A) * (s2 - s0) - (C -A) * (s1 - s0) = [ (t1 - t0) * (s2 - s0) - (t2 - t0) * (s1 - s0) ] *B
B = ( (B - A) * (s2 - s0) - (C -A) * (s1 - s0) ) / ((t1 - t0) * (s2 - s0) - (t2 - t0) * (s1 - s0) ) (8)
对(8)调整一下分母的符号,保持与(5)的分母一致,可得:
B = - ( (B - A) * (s2 - s0) - (C -A) * (s1 - s0) ) / ( (t2 - t0) * (s1 - s0) - (t1 - t0) * (s2 - s0) ) (9)
通过上述的方程组,求得唯一的B, T. 那么B, T 是互相垂直的吗? 答案是:不一定!
为了得出一个正交的坐标系,我们需要对B, T, N 进行施密特正交化。
新的切向量T' B'(尚未normalize):
T'=T-(N*T)N
B'=B-(N*B)N - (T'*B)T'
这样我们就得到了N, B', T' 作为我们的新坐标系基本轴向量。
这里有两个有两个细节问题。
1.为什么求出的B,T不是互相垂直的?
没有为什么,谁说这个公式推导出的结果就是正交向量了,不要被网上T就是U轴,B就是V轴搞混。
2.B,T,N为什么可以进行施密特正交化。
因为BTN这三个向量线性无关,TB和三角形共面,N是三角形的法线,所以TBN三个向量不共面,这三个向量线性无关,可以进行进行施密特正交化。