深入理解法线贴图原理
本文算是对此文(写给笨人的法线贴图原理)的提炼,主要是原文较长,且由大量文字构成,未必大家都有耐心认真读完,如果有耐心,建议先去读原文,然后再来这里验证。
1.为什么用法线贴图?法线贴图是为了存法线信息,让低模拥有类似高模的光照交互效果:凹凸不平,表面精致。
2.怎么存法线向量?纹理每个点都有rgb分量,可以用来存法线向量的3个分量(实际上是存2个分量,因为是单位法线向量,第三个分量可求出)。
3.那这个法线向量怎么来的?这个需要大谈特谈了。
1)3个空间坐标系:世界坐标系,模型坐标系和切线坐标系
我们需要解决法线向量是基于什么坐标系的。如果是世界坐标系,那模型只能放一个位置,要是换个位置,就错了。显然不行;
如果是模型坐标系,假如模型不动,但某个三角网格变形了,上面的法线向量变了,但因为模型坐标没变,所以算出来的法线向量木有变,变与不变矛盾了,并且,和模型绑定在一起,法贴就只能用于此模型了,毕竟不同模型的模型坐标系基是不一样的;
因为法线向量是依赖面的,就需要一个坐标矩阵:面变的时候,其也变,并且保证面的法线向量经此矩阵变换后,得到的向量(算的)和其在模型空间的实际向量是相同的。这个坐标系就是切线坐标系,所以使用切线坐标系的原因是,让法线向量在模型各种变换时,保持固定值,以便存到纹理中。
2)构建切线坐标系得到O-TBN。对模型的一个三角图元,我们如此构建其局部坐标系(切线坐标系):z轴是垂直该面的法线向量,x轴是该图元的一条边,y轴则由z叉乘x可得
模型空间到切线空间的变换矩阵:O-TBN = ,一般地,应用程序会提供T和N给顶点shader,我们就能得到此变换矩阵了。
3)在继续下去之前我们先谈一下如何用低模+法线贴图完成高模类似的效果
在vertex shader中得到的是低模的顶点,低模顶点的法线向量,低模的切线向量,2者构建低模顶点模型空间到切线空间的变换矩阵,先把光线、视线从模型空间变换到切线空间;
在fragment shader中,拿到的是高模和低模生成的法线贴图,对当前低模光栅化得到的密集片元,采样得到切线空间对应的法线向量,和vertex shader得到的数据计算光照。
这里要说的是,顶点shader拿到的是低模的顶点数据,但我们要用这个数据去和片段shader中的高模产生的法贴一起计算光照,看下图:
蓝色是低模一个三角图元,蓝色坐标系是顶点shader拿到的该顶点域的切线坐标系,红色是该低模三角图元对应的高模多个图元。
因为低模一个三角面需要对应多个高模的三角面,即对应多个法线向量,所以法线贴图的数据是这样产生的:高模法线向量从模型坐标系转到低模的切线坐标系的向量(高低模的模型坐标系一样)。这样地,多个法线向量就可以共享一个低模切线坐标系了。
4)生成法线贴图。有了第3点即从使用的角度分析,的铺垫,我们就可以得到法线贴图的生成步骤了:
a.构建低模所有图元的切线变换矩阵,把N和T存到低模的数据里,即让切线变换矩阵跟随模型,无论是下面生成法贴还是渲染时传入shader都需要使用;
b.对高模的每个图元,找到其对应的低模图元;
c.得到低模图元的切线变换矩阵,把高模此图元的法线向量变换到低模图元的切线空间,归一化,存到纹理对应的位置。
ps:因为法线向量各个分量取值区间是[-1,1],而rgb颜色分量范围是0-1,所以需要映射一下,即所谓的压缩:rgb = (rgb + 1) / 2,使用是也要反映射即解压一下。
4.为什么tangent space法线贴图是偏蓝色?因为存在贴图里的向量不是模型空间的,而是切线空间的,即在低模基础上的“扰动”,自然这个扰动是很小的,除非低模的图元非常少,所以贴图里的向量基本是z=1,即(0,0,1),,即rgb的b向量=1,所以偏蓝。
5.在我的第一个法线贴图中,我提到也可以把法线贴图采样出来的法线向量转到世界坐标系,和光照方向、视线方向计算光照效果,但这样需要在片段着色器中进行转换,而片元的数目要远远大于顶点数目,所以这种方法不宜采用。
6.法线贴图可以看做是对低模表面的“微小扰动”,所以许多情况下可以“通用”,比如墙模型和地面模型,柱子模型等,当然,像那种“专属扰动”就不能通用了,比如主角和怪物之间。
7.额外加《cg教程》里提的“大范围表面”.
普通做法:diffuse = max(dot(normal, light), 0);
大范围表面投射自己阴影做法:diffuse = light.z > 0 ? max(dot(normal, light), 0) : 0;
高亮的计算类似。分析一下可知,普通做法就是直接根据“虚拟的高模图元法线”计算光照;而大范围表面投射自己阴影是指,如果低模的那个图元没有接受光照,那么其对应的高模图元都不接受光照。后者可能使得某些本应该接受光照的片元木有光照,而前者的效果不能提供清晰的入射光方向。