参考书籍:UnityShader入门精要
参考文章:为什么要有切线空间(Tangent Space),它的作用是什么?
首先需要了解一个前提,在图形学中我们会用到很多的坐标空间,而切线空间只是其中一个,其它的诸如 模型空间,世界空间,观察空间,裁切空间,屏幕空间 有时间会在后续写博客来加深自己的理解。
切线空间的原点就是顶点本身,z轴是顶点的法线方向(n),x轴是顶点的切线方向(tangent, t)。Attention!!! 既然以及知道了两个轴,那么通过叉积运算,我们可以得到y轴,其被称为副切线(bitangent, b) 或者 副法线。
如图所示,
蓝轴为法线,红轴为切线,绿轴为副切线。
抛出问题?
切线定义:几何上,切线指的是一条刚好触碰到曲线上某一点的直线。更准确地说,当切线经过曲线上的某点(即切点)时,切线的方向与曲线上该点的方向是相同的。平面几何中,将和圆只有一个公共交点的直线叫做圆的切线。
对于三维空间中,更多是用切平面这个概念。
那么我们可以知道,一个平面存在无数条直线,如何来选择我们想要的切线呢?
有聪明的人想到了,要不干脆直接使用和纹理坐标方向相同的那条**tangent(T)**吧。只是我认为哈! 我们所使用到的法线贴图正好也是纹理,有个统一标准嘛。
该图片来自于为什么要有切线空间(Tangent Space),它的作用是什么? - Milo Yip的回答 - 知乎
左边是模型空间下的法线纹理,右边则是切线空间下的法线纹理,可以看到右边偏蓝,Why???
一个一个来理解~~~
模型空间:既然是模型空间,那么其法线方向是各种各样的,其 x , y , z x,y,z x,y,z 分量的范围主要是 [ − 1 , 1 ] [-1, 1] [−1,1] ,由于存储在纹理中,因此需要做一个映射,将区间映射到 [ 0 , 1 ] [0, 1] [0,1],即 f ( n ⃗ ) = n ⃗ 2 + [ 0.5 , 0.5 , 0.5 ] f(\vec{n}) = \frac{\vec{n}}{2} + [0.5, 0.5, 0.5] f(n)=2n+[0.5,0.5,0.5] ,这里有点偏题了。。回到重点,由于方向各异,导致其法线向量转存为纹理中的rgb值时颜色就比较花里胡哨了。
切线空间:映射和上面还是一样的,不过我们之前已经说到了切线空间下法线充当的是z轴,也就表明法线向量的主要分布是 n ⃗ = [ 0 , 0 , 1 ] \vec{n}=[0, 0, 1] n=[0,0,1],映射后的值为 [ 0.5 , 0.5 , 1.0 ] [0.5, 0.5, 1.0] [0.5,0.5,1.0],rgb中显示为浅蓝色,到这里其实已经可以清楚的知道为什么切线空间偏蓝了吧。当然也不全是浅蓝色,其他颜色即表示对其法线方向进行了扰动。你想哈,如果法线不变,那么其就是浅蓝色,稍微有点扰动,其颜色就会发生一点变化。
模型空间下存储法线的优点:
切线空间下存储法线的优点:
缺点就是两者之间没有的优点🐵
我个人觉得先了解坐标空间变换的概念,更有利于理解这个TBN矩阵。
直接点,想象一下我们的世界,我们怎么来定位一个物体的,都是用的相对位置,因此在图形学中也是这样,不管在什么坐标空间,其实都是相对的。
如何转换呢?
A
p
=
M
c
→
p
A
C
B
c
=
M
p
→
c
B
p
A_p=M_{c\to p}A_C \\ B_c=M_{p\to c}B_p
Ap=Mc→pACBc=Mp→cBp
下标带
p
p
p 的表示父坐标空间,
c
c
c 表示子坐标空间,当然并不存在父子关系,Remember,相对!!!
M
c
→
p
,
M
p
→
c
M_{c\to p}, M_{p\to c}
Mc→p,Mp→c 表示子空间到父空间,父空间到子空间的变换矩阵,两者互为逆矩阵。
设
C
C
C 坐标空间下三个坐标轴以及原点在
P
P
P 空间下的表示为
x
c
,
y
c
,
z
c
,
O
c
\mathbf{x}_c, \mathbf{y}_c, \mathbf{z}_c, \mathbf{O}_c
xc,yc,zc,Oc。
给定
C
C
C 坐标空间下一点
A
c
=
(
a
,
b
,
c
)
A_c=(a,b,c)
Ac=(a,b,c)。通过直白的方式获得
A
p
A_p
Ap。
A
p
=
O
c
+
a
x
c
+
b
y
c
+
c
z
c
A_p=\mathbf{O}_c + a\mathbf{x}_c+b\mathbf{y}_c+c\mathbf{z}_c
Ap=Oc+axc+byc+czc
转换成矩阵形式:
A
p
=
O
c
+
(
x
x
c
x
y
c
x
z
c
y
x
c
y
y
c
y
z
c
z
x
c
z
y
c
z
z
c
)
(
a
b
c
)
=
(
x
O
c
,
y
O
c
,
z
O
c
)
+
(
x
x
c
x
y
c
x
z
c
y
x
c
y
y
c
y
z
c
z
x
c
z
y
c
z
z
c
)
(
a
b
c
)
A_p=\mathbf{O}_c +
使用齐次坐标空间来表示:
A
p
=
(
x
O
c
,
y
O
c
,
z
O
c
,
1
)
+
(
x
x
c
x
y
c
x
z
c
0
y
x
c
y
y
c
y
z
c
0
z
x
c
z
y
c
z
z
c
0
0
0
0
1
)
(
a
b
c
)
=
(
∣
∣
∣
∣
x
c
y
c
z
c
O
c
∣
∣
∣
∣
0
0
0
1
)
(
a
b
c
1
)
A_p=(x_{\mathbf{O}_c} , y_{\mathbf{O}_c} , z_{\mathbf{O}_c}, 1) +
这里
M
c
→
p
M_{c\to p}
Mc→p 就出来了。
M
c
→
p
=
(
∣
∣
∣
∣
x
c
y
c
z
c
O
c
∣
∣
∣
∣
0
0
0
1
)
M_{c\to p} =
有了
M
c
→
p
M_{c\to p}
Mc→p ,
M
p
→
c
M_{p\to c}
Mp→c 就可以求到了,如果是正交矩阵,直接就是前者的转置矩阵。
Key point!!! 你看这个矩阵的形式,当我们得到这个矩阵的时候,其实可以直接把子坐标空间的原点和坐标轴方向,当然还需要归一化!!!
另一方面,如果只操作矢量,那么不需要平移操作,那么变换矩阵可以进一步简化为:
M
c
→
p
=
(
∣
∣
∣
x
c
y
c
z
c
∣
∣
∣
)
M_{c\to p} =
到这我们应该可以理解坐标变换其中用数学公式来怎么表达了。接下来需要去了解法线变换的一些特殊性,以及TBN矩阵。
对于点和大部分方向矢量,我们可以直接使用同一个 4 × 4 4\times 4 4×4 或者 3 × 3 3\times 3 3×3 的变换矩阵 M A → B M_{A\to B} MA→B ,使其从坐标空间 A A A 转换到坐标空间 B B B ,But! 这对于法线的变换就不适用了。
可以看到仅仅只是放缩了一下,其它顶点信息正确的变换,但是法线明显变换出现了问题,变换后法线不再垂直于平面了。
而切线是通过两个顶点之间的差值计算得到,我们可以直接用变换矩阵来变换。
T
B
=
M
A
→
B
T
A
\mathbf{T}_B=M_{A\to B}\mathbf{T}_A
TB=MA→BTA
这里的
T
\mathbf{T}
T 表示切线。
那么问题来了???如何得到变换后的法线向量呢???
已知条件:
T
A
⋅
N
A
=
0
\mathbf{T}_A \cdot \mathbf{N}_A=0
TA⋅NA=0
给定条件: 变换矩阵
M
A
→
B
M_{A\to B}
MA→B
设置:法线变换矩阵
G
G
G
首先:
T
B
⋅
N
B
=
(
M
A
→
B
T
A
)
⋅
(
G
N
A
)
=
(
M
A
→
B
T
A
)
⋅
(
G
N
A
)
=
(
M
A
→
B
T
A
)
⊤
(
G
N
A
)
=
T
A
⊤
M
A
→
B
⊤
G
N
A
=
T
A
⊤
(
M
A
→
B
⊤
G
)
N
A
=
0
这里开始我不是很明白公式的第二个等号后的式子为什么能直接转变成第三个等号后的式子,后来思考了一下,这是两个向量在点乘,所以转置一下其实变成了矩阵相乘,最终会得到
0
0
0 ,这里因为
T
A
⋅
N
A
=
0
\mathbf{T}_A \cdot \mathbf{N}_A = 0
TA⋅NA=0 ,所以只要
(
M
A
→
B
⊤
G
)
=
I
(M_{A\to B}^{\top}G) = \mathbf{I}
(MA→B⊤G)=I 就可以了,即相乘为一个单位阵。
那么结果呼之欲出, G = ( M A → B ⊤ ) − 1 G=(M_{A\to B}^{\top})^{-1} G=(MA→B⊤)−1 ,即变换矩阵的逆转置矩阵,如果变换矩阵正交,即只做旋转变换,那么 G = ( M A → B ⊤ ) − 1 = ( M A → B ⊤ ) ⊤ = M A → B G = (M_{A\to B}^{\top})^{-1} = (M_{A\to B}^{\top})^{\top}=M_{A\to B} G=(MA→B⊤)−1=(MA→B⊤)⊤=MA→B 。
如果还包含统一缩放,那么可以使用统一缩放系数 k k k 来变换,即 G = ( M A → B ⊤ ) − 1 = 1 k ( M A → B ⊤ ) ⊤ = 1 k M A → B G = (M_{A\to B}^{\top})^{-1} = \frac{1}{k}(M_{A\to B}^{\top})^{\top}=\frac{1}{k}M_{A\to B} G=(MA→B⊤)−1=k1(MA→B⊤)⊤=k1MA→B 。
在Shader中,我们计算光照模型通常还是会在世界空间下计算,所以在片元着色器中会将获得的切线空间坐标下的法线通过TBN矩阵转到世界空间中去。
首先获得模型世界空间坐标下法线的坐标 N \mathbf{N} N ,切线坐标 T \mathbf{T} T,注意! 这里的法线是通过模型的网格顶点自动计算出来的,与我们要使用的法线贴图的法线不是同一个概念,后者是加了一些扰动,使的模型不用改动网格就可以获得更真实的效果。
获得副切线 B = N × T \mathbf{B} = \mathbf{N} \times \mathbf{T} B=N×T ,通过叉乘得到。
那么
T
B
N
=
(
∣
∣
∣
T
B
N
∣
∣
∣
)
\mathbf{TBN}=
现在我们获得了切线空间下的三个坐标轴在世界空间下的表示,回想之前的坐标变换,我们已经获得了将切线空间坐标下的点变换到世界空间坐标下的矩阵!!!
那么到此为止,对于TBN矩阵的理解就告一段落了。