dx12 龙书第十九章学习笔记 -- 法线贴图

本文详细介绍了法线贴图的概念及其应用,包括法线贴图的生成方式、TBN坐标系的理解与构建、切线空间到世界空间的转换等关键技术,并提供了具体的着色器代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本章需要掌握的内容:①法线贴图②TBN坐标系

1.使用法线贴图的动机

左图是使用法线贴图后的结果,右图是没有使用法线贴图(使用法线插值)的结果。可以明显感知到,高光在右图中是分布在一条直线上的,并没有符合实际,而左图光照效果完美契合了纹理。

2.什么是法线贴图

法线贴图也是一种纹理,只是每一个纹素存储的并非RGB数据,红绿蓝三种分量依次存储的是压缩后的x,y,z坐标,这里的坐标实则定义的是法向量。每一个像素存储的是一个法向量。

这里的x,y,z坐标是相对于什么坐标轴而言的呢?下图👇:根据左手螺旋定律:TBN--XYZ

如图19.3,其中的向量大多都近似平行于z轴,换言之,这些向量的z轴取得了3种坐标分量中的最大值。如果将法线图看作正常RGB图的话,整体会呈现蓝色,因为z坐标存于蓝色通道之中。

压缩操作:如何将单位向量(每个坐标范围都被限定在[-1,1]),如果以平移及缩放的手段将其区间变换至[0,1],再乘以255,再截断(truncate)小数部分,最终得到[0,255]的某个整数。变换函数为:

f(x)=(0.5x+0.5)*255

压缩处理的逆操作:f^{-1}(x)=\frac{2x}{255}-1

我们不需要亲自动手参与压缩处理,因为借助PS插件就能将图片轻松转换为法线图,所以只是在PS中对法线图进行采样时,还是要实现解压缩变换过程中的一些步骤。在shader中对法线图进行采样:

float3 normalT = gNormalMap.Sample(gTriLinearSam, pin.Tex);

此时normalT将获取归一化分量构成的坐标(r,g,b),其中0≤r,g,b≤1。

也就是说,坐标的解压工作已经自动实现了一部分(从[0,255]的整数->[0,1]的浮点数),接下来,我们需要在PS中手动实现[0,1]->[-1,1]的转换,转换函数:

g(x)=2x-1

pixel shader代码中:

normalT = 2.f*normalT-1.f;
// 标量1.f会被扩充为向量(1,1,1),来与向量进行减法

3.纹理空间/切线空间 TBN

将纹理映射到3D三角形上时,观察纹理坐标系与三角形所处平面的关系。纹理uv坐标与三角形平面重合,再结合三角形的平面法线N,我们便得到了一个位于三角形所在平面内的3D TBN基(TBN-basis),它常被称为纹理空间(texture space)或切线空间(tangent space,正切空间、切空间)。注意:切线空间会随不同的三角形而发生改变👇。

区分:"纹理坐标系"和"纹理空间/切线空间"

  • 纹理坐标系是2D坐标系,uv空间
  • 纹理空间/切线空间是3D坐标系

这里的法线是基于TBN空间(纹理空间)所定义的,而为了计算光照效果,法向量需要转换到世界空间中。所以现在的目标是实现切线空间->局部空间(->世界空间)。

纹理空间中,假设顶点v_0对应的纹理坐标(0,0),那么顶点v_1=e_0=(\Delta u_0,\Delta v_0),如果知道TBN三个坐标轴分别相对于局部空间的坐标(假设:T、B、N),那么我们可以将顶点坐标转换到局部空间:

\\ e_0=\Delta u_0T+\Delta v_0 B \\ e_1=\Delta u_1T+\Delta v_1 B

使用矩阵表示👆:

\begin{bmatrix} e_0\\ e_1 \end{bmatrix}=\begin{bmatrix} \Delta u_0 & \Delta v_0 \\ \Delta u_1& \Delta v_1 \end{bmatrix}\begin{bmatrix} T\\ B \end{bmatrix}  

\begin{bmatrix} e_{0.x} & e_{0.y} & e_{0.z} \\ e_{1.x} & e_{1.y} & e_{1.z} \end{bmatrix} =\begin{bmatrix} \Delta u_0 & \Delta v_0\\ \Delta u_1 & \Delta v_1 \end{bmatrix} \begin{bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{bmatrix}

e_0,e_1是局部空间中顶点坐标,(\Delta u_0,\Delta v_0),(\Delta u_1,\Delta v_1)是两个顶点对应的纹理坐标,所以根据这两个我们能算出T、B向量在局部空间中的坐标。

\begin{bmatrix} T_x & T_y & T_z \\ B_x & B_y & B_z \end{bmatrix} = \begin{bmatrix} \Delta u_0 & \Delta v_0\\ \Delta u_1 & \Delta v_1 \end{bmatrix}^{-1}\begin{bmatrix} e_{0.x} & e_{0.y} & e_{0.z} \\ e_{1.x} & e_{1.y} & e_{1.z} \end{bmatrix}

逆矩阵性质:假设矩阵A=\begin{bmatrix} a & b \\ c & d \end{bmatrix},则有:

A^{-1}=\frac{1}{ad-bc}\begin{bmatrix} d & -b \\ -c & a \end{bmatrix}

切向量T和B在物体空间中一般均不是单位长度,而且,若纹理发生了扭曲形变,那么这两个向量也将不再互为正交规范化向量。

按照惯例,T、B、N三个向量通常分别被称为切线(tangent,或主切线)、副法线(bionormal,或bitangent副切线)以及法线(normal)。

4.顶点切线空间

如果使用上述纹理空间进行采样时,会出现问题,得到的效果会呈现明显的三角形划分痕迹,这是因为切线空间位于三角形所在平面,以致使贴图面不够圆滑,直来直去全是棱角。所以我们需要在每个顶点处指定切向量,并使用求平均值的技术,使得顶点法线更趋于平滑的表面。

求共享顶点的平均法线,是在创建模型时求得,参考本书8.2.1节。

比如每处理一个三角形时,就对其中包含的顶点的法线进行累加,这样就能最后得到平均法线。

步骤:①我们通过计算网格中共用顶点v的每个三角形的切向量平均值,作为顶点v处的切向量②副切向量的计算同理③计算完上述平均值之后,往往还需要对TBN基进行正交规范化处理,使这3个向量相互正交且具有单位长度[格拉姆-施密特过程]。

我们不会把副切向量B直接存于内存中,要用到B时直接计算N×T即可,公式中的N即顶点法线的平均值(平均顶点法线)。此时,所定义的顶点结构体如下:

struct Vertex
{
    XMFLOAT3 Pos;
    XMFLOAT3 Normal; // N
    XMFLOAT2 TexC;
    XMFLOAT3 TangentU; // T
};

5.在切线空间与物体空间之间进行转换

切线空间=>物体空间的变换矩阵:

M_{object}=\begin{bmatrix} T\\ B\\ N \end{bmatrix}=\begin{bmatrix} T_x & T_y & T_z\\ B_x& B_y & B_z\\ N_x & N_y & N_z \end{bmatrix}

该矩阵是正交矩阵,所以其逆矩阵就是它的转置矩阵。

n_{world}=n_{tangent}(M_{object}M_{world})

所以要将切线转换到世界空间,也可以使用TBN在世界空间中的坐标作为变换矩阵的行向量。

6.法线贴图的着色器代码

注意:①插值得到的法向量(顶点法向量)是用来构建TBN基;②法线贴图采集的法向量作为像素点处的法向量。

// 法线图样本转换到世界空间
float3 NormalSampleToWorldSpace(float3 normalMapSample,
    float3 unitNormalW,
    float3 tangentW)
{
    float3 normalT = 2.f*normalMapSample-1.f;
    
    float3 N = unitNormalW; // 这里假设了unitNormalW是规范化向量
    float3 T = normalize(tangentW-dot(tangentW,N)*N);
    float3 B = cross(N,T);
   
    float3x3 TBN = float3x3(T,B,N);

    float3 bumpedNormalW = mul(normalT,TBN);

    return bumpedNormalW;
}
// PS:
float3 normalMapSample = gNormalMaps.Sample(samLinear,pin.Tex).rgb;
float3 bumpedNormalW = NormalSampleToWorldSpace(normalMapSample,pin.NormalW,pin.TangentW);

插值完成后,切向量与法向量可能变成非正交规范向量。以上两行代码通过T减去其N方向上的分量(投影),再对结果进行规范化处理,从而使T称为规范化向量且正交于N。

T·N=||T||·||N||·cosθ=||T||·cosθ 就是T在N边上的投影大小,再乘上方向向量N。

struct VertexIn
{
    ...
    float3 TangentU : TANGENT; // (局部空间)切线
};

struct VertexOut
{
    ...
    float3 TangentW : TANGENT; // (世界空间)切线
};

另外,我们在法线图的alpha通道存储的是光泽度掩码(shiness mask),它控制着像素级别上物体表面的光泽度[1~0:白色~黑色]。

// PS:
const float shiness = (1.f-roughness)*normalMapSample.a;

课后练习题:

1.2.生成法线图的工具:①PhotoShop中NVIDIA公司制作的法线图插件②CrazyBump程序,自动生成法线图和位移图。 

3.如果对纹理进行旋转,那么我们需要旋转相应的TBN,我们需要在世界空间中使切线T绕着法线N旋转,这涉及大量三角函数运算开销[绕任意轴旋转的变换矩阵]。所以我们可以将T先变换到TBN空间中,旋转,再转换回世界空间。

5.位移贴图(displacement mapping)的思路是引入一种名为高度图(heightmap)的新品贴图来描述物体表面的凹凸不平。此图常与硬件曲面细分配合使用,它指示新增加顶点在法向量上的偏移量,来为网格增添几何细节。

我们可以用位移贴图实现海平面效果:设置多张位移贴图,在平坦的顶点栅格上,以不同的速度和方向"滚动"这些高度图。分别对这些高度图采样,高度之和就是顶点的高度(y坐标)。

问题:位移贴图会修改xyz分量?位移贴图是相对于物体局部空间吗(应该是)?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值