三角形表面的顶点法线插值使得表面光滑。Normal Mapping,又称为Bump Mapping是一种技术,使用从特殊的纹理中采样得到的法线替代顶点法线插值,实现三角形表面的凹凸感。这称特殊的纹理称为normal map。
因为一张normal map支持对应多个朝向不同方向的表面,所以它无法存储局部空间的法线。因此,OpenGL使用tangent space(切线空间),切线空间是定义在表面上的空间,会随表面的朝向改变空间的朝向。
切线空间定义有四种:
第一种切线空间,定义为以三角形表面法线为z轴,纹理空间的u、v轴分别为x和y轴的空间,其中x、y、z轴的单位向称分别称为tangent(切线)、bitangent(副切线)、normal(法线)。
使用纹理空间的u、v轴分别为x和y轴,可以这样理解:虽然随着纹理在一个模型上展开,u、v轴在局部空间中可能是一条曲线或折线,但在组成模型的任意一个三角形中,u、v轴必然是直线,虽然它们之间可能不再垂直。
三角形三个顶点在局部空间中的坐标是已知的,假设为E0、E1、E2。顶点在纹理空间中的坐标是已知的假设为U0、U1、U2。假设局部空间中的tangent和bitangent为T和B,因为T和B分别为u、v轴上的单位向量,可得:
E1−E0=(U1−U0)T+(V1−V0)BE2−E0=(U2−U0)T+(V2−V0)B
假设:
△E1=E1−E0△E2=E2−E0△U1=U1−U0△U2=U2−U0△V1=V1−V0△V2=V2−V0
代入上式可得:
△E1=△U1T+△V1B△E2=△U2T+△V2B
上式可以转换为矩阵形式:
[E1E2]=[△U1△U2△V1△V2][TB]
将两边都左乘[△U1△U2△V1△V2]−1,并交换位置可得:
[TB]=[△U1△U2△V1△V2]−1[E1E2]
矩阵的逆如果存在,可以通过伴随矩阵求得,所以:
[TB]=△U1△V2−△U2△V11[△V2−△U2−△V1△U1][E1E2]
在介绍第二种切换空间之间,先介绍gram-schmidt process(施密特正交化):假设一个N维的坐标系,基向量分别为N1、N2、N3...。
- 将N1归一化。
- (N1N˙2)N1得到N2在N1上投影,N2−(N1N˙2)N1得到垂直N1的向量替换原来的N2,将N2归一化。
- N3再分别与N1、N2点乘得到N3在这两个向量上的投影,N3再减去这两个投影得到垂直于N1、N2的向量替换N3,将N3归一化。
- 以此类推,对更新每一个基向量,最终得到一组标准正交基。
第二种切线空间,是第一种切线空间通过施密特正交化得到的空间。其中基向量计算的顺序是固定的:
- 首先,如果表面法线N未归一化,将N归一化。
- 然后,tangent向量T向N作投影,并从N中减去,归一化,用于更新T。
- 最后,因为是三维空间,bitangent向量B=N×T。
第三种切线空间的z轴为顶点法线,tangent向量为共享这个顶点的所有三角形的tangent向量的平均值。bitangent向量通常不被需要,如果存在的话,与tangent向量类似。三角形内非顶点的像素,对应的此空间都是插值的结果。这个切线空间的作用是使得三角形之间过渡平滑。
第四种切线空间是第三种切线空间通过施密特正交化得到的空间。normal map中采样得到正是这个空间的法线,这个空间的基向量组成将法线变换到局部空间的TBN矩阵。
因为法线的xyz分量的取值区间为[-1, 1],而normal map中的通道的取值区间为[0, 1],两者之间转换需要进行映射。
因为大多数法线通常都不会偏离切线空间的Z轴太远,所以xy分量通常在0附近,z分量通常在1附近,映射到normal map中对应颜色在(0.5,0.5,1)附近,所以法线贴图看起来都偏蓝。
如果一张法线贴图的颜色全为(0.5,0.5,1),那意味着它的法线与切线空间的Z轴重合,它的效果等同于不使用法线贴图。
Assimp库,通过Importer.ReadFile加载模型时,使用aiProcess_CalcTangentSpace标记,可以为我们计算tangent向量,保存在aiMesh对象的mTangents数组中。