纹理的另一种常见的应用就是凹凸映射(bump mapping)。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是“凹凸不平”的,但可以从模型的轮廓处看出“破绽”。
有两种主要的方法可以用来进行凹凸映射:一种方法是使用一张高度纹理(height map)来模拟表面位移(displacement),然后得到一个修改后的法线值,这种方法也被称为高度映射(height mapping);另一种方法则是使用一张法线纹理(normal map)来直接存储表面法线,这种方法又被称为法线映射(normal mapping)。尽管我们常常将凹凸映射和法线映射当成是相同的技术,但要知道它们之间的不同。
高度纹理
高度图中存储的是强度值(intensity),它用于表示模型表面局部的海拔高度。因此,颜色越浅表明该位置的表面越向外凸起,而颜色越深表明该位置越向里凹。这种方法的好处是非常直观,我们可以从高度图中明确地知道一个模型表面的凹凸情况,但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,而是需要由像素的灰度值计算而得,因此需要消耗更多的恨不能。
高度图通常会和法线映射一起使用,用于给出表面凹凸的额外信息。也就是说,我们通常会使用法线映射来修改光照。
法线纹理
法线纹理存储的是表面的法线方向。由于法线方向的分量范围在[-1, 1],而像素的分量范围为[0, 1],因此我们需要做一个映射,通常使用的映射就是:
pixel=(normal + 1) / 2
这就要求,我们在Shader中对法线纹理进行纹理采样后,还需要对结果进行一次反映射的过程,以得到原先的法线方向。反映射的过程实际就是使用上面映射函数的逆函数:
normal=pixel × 2 - 1
然而,由于方向是相对于坐标空间来说的,那么法线纹理中存储的法线方向在哪个坐标空间中呢?对于模型顶点自带的法线,它们是定义在模型空间中的,因此一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为模型空间的法线纹理(object-space normal map)。然而,在实际制作中,我们往往会采用另一种坐标空间,即模型顶点的切线空间(tangent space)来存储法线。对于模型的每一个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也被称为副切线(bitangent, b)或副法线。这种纹理被称为是切线空间的法线纹理(tangent-space normal map)。
图片来源(http://www.surlybird.com/tutorials/TangentSpace/)
上图 左边: 模型空间下的法线纹理 右边: 切线空间下的法线纹理
从图中可以看出,模型空间下的法线纹理看起来是"五颜六色"的。这是因为所有法线所在的坐标空间是同一个坐标空间,即模型空间,而每个点存储的法线方向是各异的,有的是(0,1,0),经过映射后存储到纹理中就对应了RGB(0.5,1,0.5)浅绿色,有的是(0,-1,0),经过映射后存储到纹理中就对应了(0.5,0,0.5)紫色。而切线空间下的法线纹理看起来几乎全部是蓝色的。这是因为,每个法线方向所在的坐标空间是不一样的,即是表面每个顶点各自的切线空间。这种法线纹理其实就是存储了每个点在各自的切线空间中的法线扰动方向。也就是说,如果一个点的法线方向不变,那么在它的切线空间中,新的法线方向就是z轴方向,即值为(0,0,1),经过映射后存储在纹理中就对应了RGB(0.5,0.5,1)浅蓝色。而这个颜色就是法线纹理中大片的蓝色。这些蓝色实际上说明顶点的大部分法线是和模型本身法线一样的,不需要改变。
总体来说,模型空间下的法线纹理更符合人类的直观认识,而且法线纹理本身也很直观,容易调整,因为不同的法线方向就代表了不同的颜色。但美术人员往往更喜欢使用切线空间下的法线纹理。那么,为什么他们更偏好使用这个看起来“很蹩脚”的切线空间呢?
实际上,法线本身存储在哪个坐标系中都是可以的,我们甚至可以选择存储在世界空间下。但问题是,我们并不是单纯地想要得到法线,后续的光照计算才是我们的目的。而选择哪个坐标系意味着我们需要把不同信息转换到相应的坐标系中。例如,如果选择了切线空间,我们需要把从法线纹理中得到的法线方向从切线空间转换到世界空间(或其他空间)中。
总体来说,使用模型空间来存储法线的优点如下。
实现简单,更加直观。我们甚至都不需要模型原始的法线和切线等信息,也就是说,计算更少。生成它也非常简单,而如果要生成切线空间下的法线纹理,由于模型的切线一般是和UV方向相同,因此想要得到效果比较好的法线映射就要求纹理映射也是连续的。
在纹理坐标的缝合处和尖锐的边角部分,可见的突变(缝隙)较少,即可以提供平滑的边界。这是因为模型空间下的法线纹理存储的是同一坐标系下的法线信息,因此在边界处通过插值得到的法线可以平滑变换。而切线空间下的法线纹理中的法线信息是依靠纹理坐标的方向得到的结果,可能会在边缘处或尖锐的部分造成更多可见的缝合迹象。
但使用切线空间有更多优点。
自由度很高。模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它的那个模型,而应用到其他模型上效果就完全错误了。而切线空间下的法线纹理记录的是相对法线信息,这意味着,即便把该纹理应用到一个完全不同的网格上,也可以得到一个合理的结果。
可进行UV动画。比如,我们可以移动一个纹理的UV坐标来实现一个凹凸移动的效果,但使用模型空间下的法线纹理会得到完全错误的结果。原因同上。这种UV动画在水或者火山熔岩这种类型的物体上会经常用到。
可以重用法线纹理。比如,一个砖块,我们仅使用一张法线纹理就可以用到所有的6个面上。原因同上。
可压缩。由于切线空间下的法线纹理中法线的Z方向总是正方向,因此我们可以仅存储XY方向,而推导得到Z方向。而模型空间下的法线纹理由于每个方向都是可能的,因此必须存储3个方向的值,不可压缩。
切线空间下的法线纹理的前两个优点足以让很多人放弃模型空间下的法线纹理而选择它。从上面的优点可以看出,切线空间在很多情况下都优于模型空间,而且可以节省美术人员的工作。