Shader小常识之——法线纹理在切线空间下的存储

上一篇文章简单的介绍了法线的作用原理,如果能完全理解,说明已经入门,今天继续深入。

首先,来讲一下上一篇中的一个问题:一个面有两个与之垂直的向量,一个朝上一个朝下,那么哪一个才是它的法线?答案为四个字:“左手定律”。在回答这个问题前,我们有必要了解一下游戏中的模型生成。一个模型是由众多的三角形组成,而一个三角形有三条边,每条边由两个顶点控制。所以你看到的无论是男主人公还是女主人公,其实他都只是游戏空间中无数个顶点连接在一起组成的。再往深了说,其实模型是无数个纪录在游戏世界中的坐标数据,在GPU里画出来的。
这里写图片描述

OK,那我们就从最简单的一个三角形来说,一个三角形有三个顶点,GPU在绘制三角形的时候会有一个疑惑,那就是我究竟是以顺时针方向绘制还是已逆时针方向来绘制?联系我们的问题,法线究竟是朝上还是朝下?是不是感觉冥冥中有什么联系。是的,没错,那就是绘制顺序决定了法线朝向。在Unity里遵循的是左手定律:
左手定律

伸出你的小手,握拳方向表示顶点的绘制顺序,那么大拇指指向的就是法线的方向。这样就有了正面背面之说,单面双面之说。
那法线信息存储在哪呢?这个问题的答案似乎呼之欲出,那不在顶点上存储,难不成还存在面上吗,这样一说,好像突然豁然开朗,一个三角形对应三个顶点,每个顶点都存储了一条与面垂直的法线,如此,无数个这样的三角形组合在一起不就好了吗。真的是这样吗?多了不说,就说两个不同面的三角形组合在一起的情况:
这里写图片描述

如上图所示,这两个三角形不在同一个平面上,但是有两个顶点重叠,那重叠的这两个顶点的法线应该指向哪里?这就涉及到了如何计算法线的问题。有一定了解的人一定听说过顶点法线面法线的概念,一般来说,顶点法线和面法线是相同的,但是当一个顶点所在的面不同面时,法线就有了两种算法,一种是依旧用垂直于面的法线,另一种是共面的几条法线取平均,出来的效果是,前者会“有棱有角”,所以就有了Lowpoly:
Lowpoly

后者会“圆滑有度”,所以就有了max里的光滑组:
光滑组

这里要补充解释下,法线信息计算在建模的时候就已经是算好了的,不然模型显示就不正确了,在导入Unity的时候,引擎也可以对法线进行重新计算,有几个选项供选择:
这里写图片描述
当然,法线信息说白了就是一个数据,你也可以在脚本里写代码对其进行计算修改。而我们在shader里取出来的法线是已经计算好的法线数据了。

法线计算方法有了,那么法线应该在哪个坐标系下计算呢?一般来说,在模型空间坐标系(Object Space)下计算并存储是最方便不过了,而事实上Unity的vertex.normal取出来值也确实是模型空间下的法线坐标。众所周知,法线是一条单位向量,对应每个轴向上的分量范围是[-1, 1],而图片的颜色值是[0, 1],那如果想把物体的法线信息写入到一张图片上,就要做一个变换:
color = normal * 0.5 + 0.5
这样,我们就把法线信息存储在了一张贴图上,下次用的时候,直接对其进行采样
normal = color * 2 - 1
这样就得到了法线信息。看起来是如此简单,但其中还藏着一些“猫腻”。
猫腻一:假设法线向量为(x,y,z),它可以由(x,y,0)和(0,0,z)两条边组成,既然法线向量是一条单位向量,这两条边构成的三角形又是直角三角形,那么其实只要知道一条边长,就可以用勾股定理计算出另一条边长。实际上法线纹理只存储了xy信息,对应shader代码:

fixed3 bump = UnpackNormal(tex2D(_BumpTex, i.uv.xy)).xyz;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));

猫腻二:实际游戏中使用的法线贴图存储的并不是模型空间下的法线,而是切线空间(Tangent Space)。切向空间是以该顶点为原点,切线为x轴,法线为z轴,副切线(切线和法线叉乘)为y轴而得的空间。
切线空间
在切线空间下,(0,0,1)表示我们之前所说的垂直于平面的法线,套用color = normal * 0.5 + 0.5将其转换为color值,那么就成了(0.5,0.5,1),打开Unity,把颜色调成(0.5,0.5,1),你会发现这个颜色正是蓝色,这也就解决了我们昨天问的问题:“为什么法线贴图大部分都是蓝色?
OK,了解了今天的知识,以后是不是要以另一个思维去看待游戏世界了/偷笑。今天就到这里,每天一个小知识,如果你也有这么多的问题,欢迎留言讨论,我会考虑把它写出来,大家一起分享。

猜你喜欢

转载自blog.csdn.net/u013917120/article/details/78817736