《Unity Shader 入门精要读书笔记--初级篇--第七章--凹凸映射》
预备知识
什么是凹凸映射?
在普通纹理作用下,我们实现了给模型“穿衣服”的效果,实现了真实感渲染的第一步。但是,普通的单张纹理的能力很有限。比如,当我们模拟一个砖墙的材质时,单张纹理在远处看来,效果还算可以;但是,当我们靠近这个材质时,我们发现,原本应该粗糙的砖墙表面,竟然比“磨皮”之后的效果还要平滑,就像是贴了一个壁纸。这是为什么呢?
单张纹理只是调制了材质的漫反射颜色,让它看起来是那么回事。但是我们知道,实际上几乎所有材质的表面都是凹凸不平的。那么,这些凹凸不平的效果又是怎么实现的呢?
当然,为了实现凹凸不平的效果,最简单的思路是在建模的时候将模型建成凹凸不平的表面,抛开精度不谈,这种建模方法势必会导致三角形面片数量成几何增长(类似于迭代细分);那么换个角度,我们所看到的材质效果,实际上大部分是由着色模型决定的,对于凹凸表面而言,最终效果就是由于表面细小的凹凸片元的法向量不一致导致的。因此,我们只要在光照计算的时候更改模型表面法线,就可以达到凹凸不平的效果。
如何实现凹凸映射?
- 高度纹理(Height Map)
高度纹理存储了每个位置的相对高度,很直观,但不实用。我们可以通过高度图看到模型表面的凹凸情况,但是对于shader而言,需要额外计算每个位置的法线,效率极低。
- 法线纹理(Noraml Map)
法线纹理是实现凹凸映射的关键,法线纹理是一张“特殊”的纹理,存储了每个位置切线空间的法向量。每个顶点都有自己的切线空间,这个空间的z轴是顶点法线方向,x轴是顶点切线方向,y轴是z轴与x轴的叉乘结果,称为副切线(Bitangent)或副法线(Binormal)。对于法向量而言,每个分量的范围是[-1,1];而对于颜色值而言,每个分量为[0,1]。所以需要一个合适的映射:
p i x e l = n o r m a l + 1 2 pixel=\frac{normal+1}{2} pixel=2normal+1
同样地,当我们从法线纹理采样时,我们需要进行如下逆映射:
n o r m a l = p i x e l × 2 − 1 normal=pixel\times2-1 normal=pixel×2−1
那么,我们为什么要把***切线空间***的法向量存入法线纹理中呢?模型空间不行吗?世界空间不行吗?
对于着色计算而言,一般都是在世界空间进行的。考虑到直观性,模型空间却更好。但是对于凹凸映射而言,选择切线空间有如下好处:- 自由度高。对于模型空间或其他空间而言,法线信息是绝对的,而切线空间是相对的,取决于该顶点的法线和切线方向。这就意味着,绝对法线仅仅适用于创建它的初始模型,对于其他模型网格,就会有失真的情况。但是对于切线空间的法线纹理信息而言,其值其实是一个“扰动值”,也就是说并不是该点真实的法向量,而是用于着色计算时使用的法线值,是对该顶点法线的计算层面的扰动。这就意味着,当我们将这张贴图用于其他模型上时,也可以保证一定的效果。
- 保证UV动画正常进行。当我们移动UV坐标实现UV动画时,相当于给新模型贴图,如上所述,绝对的法线信息就无法达到正确效果。
- 可以复用法线纹理,通过扰动达到不通效果。
- 可以压缩,由于切线空间法线永远是外法线,z分量永远为正,可由xy推导出来。所以我们可以将法线贴图中某个通道空出,达到压缩的目的,当然,也可以存储其他模型信息。
对于法线纹理而言,由于大部分位置的法线与模型原法线一致,为(0,0,1),经过映射之后,变为了(0.5,0.5,1)是浅蓝色,所以法线纹理的视觉特征就是大片大片的蓝色。
高度图转化为法线贴图的数学方法
基本方法:利用高度图每个相邻像素平直表面的高度差,计算 s s s和 t t t方向上的切向量,用 H ( i , j ) H(i,j) H(i,j)表示尺寸为 w × h w\times h