法线贴图

法线贴图

引入法线贴图

  • 对于是一个平面的物体,比如一块桌面或者一块地砖的表面,只使用纹理就可以表现出很好的效果;但对于凹凸不平的物体表面,比如由很多砖块砌成的墙面,只使用纹理就不能表现出凹凸不平的细节,原因是:在片段着色器中计算光照时,每个片段使用的法线是插值的顶点法线,而这些法线几乎相同,所以表现不出凹凸不平的视觉
  • 如果我们能通过一种方式获取物体表面不同的朝向,再根据不同的朝向来计算光照,那么就可以表现出良好的凹凸不平的视觉效果
  • 物体的表面朝向是通过法向量来表达的,如果我们提前把每个片段的法向量都计算出来,并记录到某个文件中,在计算光照的时候从这个文件中读取法向,就可以得出好的效果了。这个文件就叫做法线贴图

法线贴图

  • 法线贴图是一个2D纹理
  • 要把一个法向量记录到一个2D纹理中,需要做一些变换。因为法向量的xyz都是在[-1.0, 1.0]之间,而纹理中的rgb是在[0.0, 1.0]之间
  • 法线贴图一般都是一张偏蓝色的纹理,因为大部分片段的法向都是(0,0,1)
  • 法线贴图,一般由制作模型的人,在导出模型文件时,一并生成法线贴图;也可以通过ps等工具,将一张普通纹理生成对应的法线贴图

引入切线空间

  • 当法线贴图中记录的法向量与场景中物体的实际法线方向一致时,效果很好;当他们不一致时,就又没有了凹凸不平的细节
  • 原因是,物体的实际法线方向变了,而从法线贴图中读取的数据却没有变,导致光照计算错误
  • 假设有这样一个的坐标空间:法线贴图中的法向量在这个坐标空间中总是指向正Z方向。在计算光照时,所有的光照向量都相对这个正Z方向进行变换,然后再计算光照,最后得到正确的计算结果;或者将这个正Z方向变换到与真实物体表面法线对齐,然后再计算光照,最后得到正确结果。这个坐标空间就叫做切线空间(tangent space)。

切线空间

  • 法线贴图中的法线向量定义在切线空间中
  • 在切线空间中,法线永远指着正z方向
  • 切线空间是位于三角形表面之上的空间
  • 我们可以计算出一个矩阵,把切线空间的z方向和表面的法线方向对齐。这就是切线空间的好处
  • 这种矩阵叫做TBN矩阵,tangent(切线)、bitangent(副切线)和normal(法线)向量

计算TBN矩阵

  • 要建构这样一个把切线空间转变为不同空间的矩阵,我们需要三个相互垂直的向量,它们沿一个表面的法线贴图对齐于:右、前、上;已知上是物体表面的法向量,求右和前的T、B
    在这里插入图片描述

  • 由于TBN相互垂直,且沿表面的法线贴图对齐于右、前、上,因此T、B对齐与纹理坐标的U、V方向
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MdQRtpPR-1626864381528)(en-resource://database/1411:1)]

  • 顶点P1、P2、P3是三角形的三个顶点,UV分别是他们的纹理坐标
    Δ U 1 = U 2 − U 1 , Δ V 1 = V 2 − V 1 ΔU_1 = U_2 - U_1, ΔV_1 = V_2 - V_1 ΔU1=U2U1,ΔV1=V2V1

Δ U 2 = U 3 − U 2 , Δ V 2 = V 3 − V 2 ΔU_2 = U_3 - U_2, ΔV_2 = V_3 - V_2 ΔU2=U3U2,ΔV2=V3V2

E 1 = Δ U 1 T + Δ V 1 B E_1 = ΔU_1T + ΔV_1B E1=ΔU1T+ΔV1B

E 2 = Δ U 2 T + Δ V 2 B E_2 = ΔU_2T + ΔV_2B E2=ΔU2T+ΔV2B

( E 1 X , E 1 Y , E 1 Z ) = Δ U 1 ( T X , T Y , T Z ) + Δ V 1 ( B X , B Y , B Z ) (E_{1X},E_{1Y},E_{1Z}) = ΔU_1(T_X,T_Y,T_Z) + ΔV_1(B_X,B_Y,B_Z) (E1X,E1Y,E1Z)=ΔU1(TX,TY,TZ)+ΔV1(BX,BY,BZ)

( E 2 X , E 2 Y , E 2 Z ) = Δ U 2 ( T X , T Y , T Z ) + Δ V 2 ( B X , B Y , B Z ) (E_{2X},E_{2Y},E_{2Z}) = ΔU_2(T_X,T_Y,T_Z) + ΔV_2(B_X,B_Y,B_Z) (E2X,E2Y,E2Z)=ΔU2(TX,TY,TZ)+ΔV2(BX,BY,BZ)

[ E 1 X E 1 Y E 1 Z E 2 X E 2 Y E 2 Z ] = [ Δ U 1 Δ V 1 Δ U 2 Δ V 2 ] ∗ [ T X T Y T Z B X B Y B Z ] \left[ \begin{matrix} E_{1X} & E_{1Y} & E_{1Z} \\ E_{2X} & E_{2Y} & E_{2Z} \end{matrix} \right] = \left[ \begin{matrix} ΔU_1 & ΔV_1 \\ ΔU_2 & ΔV_2 \end{matrix} \right] * \left[ \begin{matrix} T_X & T_Y & T_Z \\ B_X & B_Y & B_Z \end{matrix} \right] [E1XE2XE1YE2YE1ZE2Z]=[ΔU1ΔU2ΔV1ΔV2][TXBXTYBYTZBZ]

[ Δ U 1 Δ V 1 Δ U 2 Δ V 2 ] − 1 ∗ [ E 1 X E 1 Y E 1 Z E 2 X E 2 Y E 2 Z ] = [ T X T Y T Z B X B Y B Z ] \left[ \begin{matrix} ΔU_1 & ΔV_1 \\ ΔU_2 & ΔV_2 \end{matrix} \right]^{-1} * \left[ \begin{matrix} E_{1X} & E_{1Y} & E_{1Z} \\ E_{2X} & E_{2Y} & E_{2Z} \end{matrix} \right] = \left[ \begin{matrix} T_X & T_Y & T_Z \\ B_X & B_Y & B_Z \end{matrix} \right] [ΔU1ΔU2ΔV1ΔV2]1[E1XE2XE1YE2YE1ZE2Z]=[TXBXTYBYTZBZ]

[ T X T Y T Z B X B Y B Z ] = 1 Δ U 1 Δ V 2 − Δ U 2 Δ V 1 ∗ [ Δ V 2 − Δ V 1 − Δ U 2 Δ U 1 ] ∗ [ E 1 X E 1 Y E 1 Z E 2 X E 2 Y E 2 Z ] \left[ \begin{matrix} T_X & T_Y & T_Z \\ B_X & B_Y & B_Z \end{matrix} \right] = \frac{1}{ΔU_1ΔV_2 - ΔU_2ΔV_1} * \left[ \begin{matrix} ΔV_2 & -ΔV_1 \\ -ΔU_2 & ΔU_1 \end{matrix} \right] * \left[ \begin{matrix} E_{1X} & E_{1Y} & E_{1Z} \\ E_{2X} & E_{2Y} & E_{2Z} \end{matrix} \right] [TXBXTYBYTZBZ]=ΔU1ΔV2ΔU2ΔV11[ΔV2ΔU2ΔV1ΔU1][E1XE2XE1YE2YE1ZE2Z]

  • 由上面的推导可知,通过三角形的两条边、纹理坐标可以计算出切向量T、副切向量B
  • 因为一个三角形永远是平坦的形状,我们只需为每个三角形计算一个切线/副切线,它们对于每个三角形上的顶点都是一样的

使用法线贴图

  • 为了使用法线贴图,需要在顶点着色器中创建TBN矩阵,首先使用上面的方法计算出模型每个顶点的切线向量和副切线向量,并把他们和法向量一起传入顶点着色器,来创建TBN矩阵
  • 需要注意的是,现在创建出来的TBN矩阵是在法线空间中,我们需要把他变换到世界空间中,方法就是乘以模型矩阵逆矩阵的转置矩阵
  • 如果在将TBN矩阵变换到世界空间中时,只关心方向,不关心缩放和平移,可以直接乘以模型矩阵进行变换
  • 从理论上说,顶点着色器中不需要传入副切线向量,因为可以通过切线向量和法线向量叉乘得出

方法一

  • 直接使用TBN矩阵,把切线坐标空间的法向量转换到世界坐标空间

  • 把TBN矩阵传到片段着色器中,把采样得到的法线坐标左乘上TBN矩阵,转换到世界坐标空间中,这样所有法线和其他光照变量就在同一个坐标系中,之后按照Blinn-Phong模型计算光照就可以了

  • 顶点着色器

...
out VS_OUT 
{ 
    vec3 FragPos; 
    vec2 TexCoords; 
    mat3 TBN; 
} vs_out; 
uniform mat4 mormalMat;

void main() 
{ 
    vec3 T = normalize(vec3(modelMat * vec4(tangent, 0.0))); 
    vec3 N = normalize(vec3(modelMat * vec4(normal, 0.0))); 
    T = normalize(T - dot(T, N) * N);//修正
    vec3 B = cross(N, T);
    vs_out.TBN = mat3(T, B, N); 
    ...
}
  • 片段着色器
...
in VS_OUT
{ 
    vec3 FragPos; 
    vec2 TexCoords; 
    mat3 TBN; 
} fs_in;

void main()
{
    vec3 normal = texture(normalMap, fs_in.TexCoords).rgb; 
    normal = normalize(normal * 2.0 - 1.0); 
    normal = normalize(fs_in.TBN * normal);
    ...
}

方法二

  • 使用TBN矩阵的逆矩阵,把世界坐标空间的向量转换到切线坐标空间

  • 因为TBN矩阵是正交矩阵,在求逆矩阵时,可以使用转置矩阵代替

  • 把lightDir,viewDir都变换到切线空间,这样他们就和法线贴图中采样到的法线就在同一个坐标空间了,之后就可以放肆的计算光照

  • 顶点着色器

...
void main()
{
    ...
    mat3 TBN = transpose(mat3(T, B, N));    
    vs_out.TangentLightPos = TBN * lightPos;
    vs_out.TangentViewPos  = TBN * viewPos;
    vs_out.TangentFragPos  = TBN * vec3(modelMat * vec4(position, 0.0));
 }
  • 片段着色器
...
in VS_OUT 
{
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} fs_in;

void main()
{
    vec3 normal = texture(normalMap, fs_in.TexCoords).rgb; 
    normal = normalize(normal * 2.0 - 1.0); 
    ...
}

两种方法比较

  • 方法二可以把所有有关向量的变换控制在顶点着色器中进行
  • 方法二将切线空间的光源位置、观察位置以及顶点位置发送给片段着色器。这样我们就不用在片段着色器中进行矩阵乘法。这是一个极佳的优化,因为顶点着色器通常比片段着色器运行的次数少

使用法线贴图与不使用的对比

  • 不使用法线贴图
    请添加图片描述
  • 使用法线贴图
    请添加图片描述
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值