切线空间

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

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

du1=u1u0
du2=u2u0
dv1=v1v0
dv2=v2v0

Q1=P1P0
Q2=P2P0

由此可见:

Q1=du1t+dv1b
Q2=du2t+dv2b

将上式写为矩阵形式:

[Q1xQ1yQ1zQ2xQ2yQ2z]=[du1dv1du2dv2][TxTyTzBxByBz]

[du1dv1du2dv2]求逆,可得

[TxTyTzBxByBz]=1du1dv2dv1du2[dv2dv1du2du1][Q1xQ1yQ1zQ2xQ2yQ2z]

通过对TB规范化后得到切线方向t和副切线方向b

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

采样计算

存储与采样

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

法线非统一缩放问题

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

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

nt=(Gn)(Mt)=0

(Gn)(Mt)展开可得

[GixGjxGkxGiyGjyGkyGizGjzGkz][nxnynz][MixMjxMkxMiyMjyMkyMizMjzMkz][txtytz]=[nxGix+nyGjx+nzGkxnxGiy+nyGjy+nzGkynxGiz+nyGjz+nzGkz][txMix+tyMjx+tzMkxtxMiy+tyMjy+tzMkytxMiz+tyMjz+tzMkz]

由此可见

(Gn)(Mt)=(Gn)T(Mt)=nTGTMT

由于nTt=nt=0,如果GTM=I,则nTGTMT=0,由此可知G=(M1)T

M为正交矩阵,则M1=MT,可知(M1)T=M,此时可避免求逆和转置的操作。

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

在顶点着色器中计算世界空间下的法线向量 normalWS 、切线向量 tangentWS 和副切线向量 bitangentWS ,在计算 normalWS 时,变换顶点到世界空间的矩阵Mobjectworld其逆矩阵Mobjectworld1为 unity_WorldToObject ,使用模型空间的法线向量 normalOS 左乘Mobjectworld1,相当于右乘其转置矩阵(Mobjectworld1)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 和法线向量构成变换矩阵Gtangentworld,也即构成新的坐标空间,这里注意 half3x3(v1,v2,v3) 是以行向量构建矩阵,故将切线空间的法线向量 normalTS 变换至世界空间时,需要使用 normalTS 左乘矩阵Gtangentworld

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   WoBok  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示