切线空间

法线纹理用来呈现物体表面的凹凸细节,模型顶点自身的法线定义于模型空间(Object Space)中,模型的法线纹理一般存储在模型顶点的切线空间(Tangent Space)中,一般的,顶点本身为切线空间原点,选择顶点法线方向\(n\)为切线空间的正方向\(z\),法线贴图的\(u\)方向为切线方向\(t\),法线贴图的\(v\)方向为副切线方向\(b\)

可以通过三角形顶点的位置和其UV坐标推导出切线方向\(t\)和副切线方向\(b\),如下图,\(P_i\)为三角形的顶点,\((u_i,v_i)\)为UV坐标,其中

\(du_1=u_1-u_0\)
\(du_2=u_2-u_0\)
\(dv_1=v_1-v_0\)
\(dv_2=v_2-v_0\)

\(Q_1=P_1-P_0\)
\(Q_2=P_2-P_0\)

由此可见:

\(Q_1=du_1t+dv_1b\)
\(Q_2=du_2t+dv_2b\)

将上式写为矩阵形式:

\(\begin{bmatrix} Q{_1}_x&Q{_1}_y&Q{_1}_z\\ Q{_2}_x&Q{_2}_y&Q{_2}_z \end{bmatrix}= \begin{bmatrix} du_1&dv_1\\ du_2&dv_2 \end{bmatrix} \begin{bmatrix} T_x&T_y&T_z\\ B_x&B_y&B_z \end{bmatrix}\)

\(\begin{bmatrix} du_1&dv_1\\ du_2&dv_2 \end{bmatrix}\)求逆,可得

\(\begin{bmatrix} T_x&T_y&T_z\\ B_x&B_y&B_z \end{bmatrix}= \frac{1}{du_1dv_2-dv_1du_2} \begin{bmatrix} dv_2&-dv_1\\ -du_2&du_1 \end{bmatrix} \begin{bmatrix} Q{_1}_x&Q{_1}_y&Q{_1}_z\\ Q{_2}_x&Q{_2}_y&Q{_2}_z \end{bmatrix}\)

通过对\(T\)\(B\)规范化后得到切线方向\(t\)和副切线方向\(b\)

因为三角面是一个平面,只需要计算一个三角面的切线和副切线即可,它们对于三角形的顶点来说都是一样的,被多个三角面共享的顶点只需要将多个结果平均化即可。

采样计算

存储与采样

纹理颜色\(c\)各分量的范围为\([0,1]\),而法线向量\(n\)各分量的范围为\([-1,1]\),故在存储时需要做\(c=\frac{n+1}{2}\)的映射,在纹理采样时做\(n=2c-1\)的映射。

法线非统一缩放问题

在模型变换时,跟顶点相关的信息也需要一并变换,其中包括法线与切线等,由切线空间部分可知顶点的切线通过顶点之间的差值计算得来,根据线性变换的性质,用于顶点变换的矩阵\(M\)(矩阵\(M\)不包含平移)同样可以用于变换切线向量,但如果\(M\)不是正交矩阵,对变换后的空间可能在某些方向上产生“挤压”或“拉伸”,所以无法保证其变换的法线向量与原来的表面垂直。

因为切线向量\(t\)与法线向量\(n\)始终保持平行,所以对于同一个顶点来说满足\(n\cdot t=0\),变换后的\(t'\)\(n'\)也应满足该等式,假设顶点的变换矩阵为\(M\),法线向量的变换矩阵为\(G\),则

\(n'\cdot t'=(Gn)\cdot (Mt)=0\)

\((Gn)\cdot (Mt)\)展开可得

\(\begin{bmatrix} G{_i}_x&G{_j}_x&G{_k}_x\\ G{_i}_y&G{_j}_y&G{_k}_y\\ G{_i}_z&G{_j}_z&G{_k}_z \end{bmatrix} \begin{bmatrix} n_x\\n_y\\n_z \end{bmatrix} \cdot \begin{bmatrix} M{_i}_x&M{_j}_x&M{_k}_x\\ M{_i}_y&M{_j}_y&M{_k}_y\\ M{_i}_z&M{_j}_z&M{_k}_z \end{bmatrix} \begin{bmatrix} t_x\\t_y\\t_z \end{bmatrix}= \begin{bmatrix} n_xG{_i}_x+n_yG{_j}_x+n_zG{_k}_x\\ n_xG{_i}_y+n_yG{_j}_y+n_zG{_k}_y\\ n_xG{_i}_z+n_yG{_j}_z+n_zG{_k}_z \end{bmatrix} \cdot \begin{bmatrix} t_xM{_i}_x+t_yM{_j}_x+t_zM{_k}_x\\ t_xM{_i}_y+t_yM{_j}_y+t_zM{_k}_y\\ t_xM{_i}_z+t_yM{_j}_z+t_zM{_k}_z \end{bmatrix}\)

由此可见

\((Gn)\cdot (Mt)=(Gn)^T(Mt)=n^TG^TMT\)

由于\(n^Tt=n\cdot t=0\),如果\(G^TM=I\),则\(n^TG^TMT=0\),由此可知\(G=(M^{-1})^T\)

\(M\)为正交矩阵,则\(M^{-1}=M^T\),可知\((M^{-1})^T=M\),此时可避免求逆和转置的操作。

在世界空间中采样计算法线

在顶点着色器中计算世界空间下的法线向量 normalWS 、切线向量 tangentWS 和副切线向量 bitangentWS ,在计算 normalWS 时,变换顶点到世界空间的矩阵\(M_{object\rightarrow world }\)其逆矩阵\(M_{object\rightarrow world }^{-1}\)为 unity_WorldToObject ,使用模型空间的法线向量 normalOS 左乘\(M_{object\rightarrow world }^{-1}\),相当于右乘其转置矩阵\((M_{object\rightarrow world }^{-1})^T\)

half3 normalWS = normalize(mul(input.normalOS, (float3x3)unity_WorldToObject));
half3 tangentWS = normalize(mul((float3x3)UNITY_MATRIX_M, input.tangentOS.xyz));
half3 bitangentWS = cross(normalWS, tangentWS) * input.tangentOS.w;

在片元着色器中采样切线空间下的法线向量 normalTS ,由于纹理格式存在差异,使用Shader Lab中内置函数 UnpackNormal() 对存储的法线进行反映射,然后使用顶点着色器传入的切线向量 tangentWS 、副切线向量 bitangentWS 和法线向量构成变换矩阵\(G_{tangent\rightarrow world}\),也即构成新的坐标空间,这里注意 half3x3(v1,v2,v3) 是以行向量构建矩阵,故将切线空间的法线向量 normalTS 变换至世界空间时,需要使用 normalTS 左乘矩阵\(G_{tangent\rightarrow world}\)

half3 normalTS = UnpackNormal(tex2D(_NormalMap, input.uv));
half3x3 tangentToWorld = half3x3(input.tangentWS.xyz, input.bitangentWS.xyz, input.normalWS.xyz);
half3 normalWS = mul(normalTS.xyz, tangentToWorld);

Unity中的切线设置

Unity默认使用 MikkTSpace 计算切线,在Model Import Settings中可进行设置。

切线最常用于凹凸贴图着色器中。切线是单位长度的矢量,它顺着网格表面沿水平 (U) 纹理方向。Unity 中的切线表示为 Vector4, 其“x,y,z”分量定义矢量,而 w 用于在需要时翻转副法线。Unity 计算另一个表面矢量(副法线)的方法是获取法线与切线之间的叉积,然后将结果乘以切线的 w。因此,w 应始终为 1 或 -1。

参考

《3D游戏与计算机图形学中的数学方法》

《Unity Shader入门精要》

法线贴图

posted on 2024-09-07 12:55  WoBok  阅读(14)  评论(0编辑  收藏  举报