Chapter 6 – Texturing 贴图

物体表面的贴图就是我们看到的并感受到的——也可以将贴图想象成一幅油画。在计算机图形学中,贴图是一个过程,其读取了物体表面的每一个点并且基于图像,函数或者其他数据改变了每一个点的外观。例如,为了展现一块砖头的外观,我们不可刻意展现其几何上的细节,而是将砖头的图像应用在一个矩形上,这个矩形由两个三角形组成。当我们观察那个矩形时,砖块的图像便会出现在矩形所在的位置。除非我们靠得非常近,否则是无法注意到砖块缺乏几何细节。

但是,除了由于缺乏几何细节,一些贴图后的砖块效果仍然不佳。例如,砖块的表面看上去应该是粗糙的而不是光滑的。为了让物体看上去更为真实,我们会使用第二张图像贴图。这一张贴图并不会改变物体表面的颜色,而是基于表面的位置改变其粗糙度。现在,我们的砖块物体拥有一张表现颜色的贴图和一张表现粗糙度的贴图。

现在砖块看上去是粗糙的,但是我们可能会觉得砖块的表面是简单的平面。而真实世界中砖块的表面应该是高低起伏的。通过使用bump mapping,砖块的着色法线将会各不相同,所以在我们绘制砖块的表面时,它们将不再是一个简单的平面。这一类的贴图将会改变矩形原始的表面法线向量。

从阴影的角度来说,上述bump mapping造成的物体表面凸起的“假象”会被识破。例如,砖块凸起的部分在某些视角下可能会遮住凹下去的部分。至少凸起的部分会在凹陷部分投射出阴影。parallax mapping使用一张贴图改变平面的形状,parallax occlusion mapping则会基于高度贴图进行射线检测让画面的真实性更高。displacement mapping通过改变模型中三角形的高度来改变模型的外观。

上述例子中的这些问题都能通过贴图解决,其相应的算法也会越来越精巧。在本章节中,我们将学习贴图技术的各个细节。首先,我们会展示贴图系统的常见框架。之后,我们将专注于使用图像为物体表面贴图,这也是实时渲染中贴图最常用的功能。最后则是程序化贴图以及一些常见的使用贴图来影响物体表面的方法。

6.1 贴图管线

贴图是一种高效改变模型表面材质与外观的技术。正如我们在之前的章节看到的,在着色计算时我们需要考虑材质的颜色,光照以及其他因素。贴图的原理是改变了着色等式中使用的颜色值。这些颜色值一般会基于物体表面的位置而改变。所以,以砖块为例,物体表面任一点的颜色将被砖块图像中相对应的颜色所替代。而图像贴图中的像素点一般被成为texel纹理,以此区分到底是屏幕上的像素点还是贴图中的像素点。粗糙度贴图则改变了物体表面某一点的粗糙度,bump贴图改变了着色法线的方向,所以这些值都会改变着色等式的结果。

贴图也能由贴图管线表示。空间中的一个点将会是贴图处理过程的开始。这个坐标点可以位于世界坐标系,但一般来说其位于模型的本地空间,所以随着模型的移动,贴图也会移动。之后,这个位于空间中的点将会被投影以得到一组数据,其被称为贴图坐标(texture coordinate),而我们可以使用这个坐标读取贴图。这个过程被称为映射(mapping),也就是所谓的贴图映射(texture mapping)。有时贴图图像也被称为texture map,但严格意义上来说这是错误的。

在我们使用这些新的数据读取贴图之前,我们需要使用多个函数将贴图坐标转换至贴图空间。这些位于贴图空间的坐标点将用于从贴图中读取颜色值,例如,它们类似于数组中的索引,只不过用来读取贴图图像中的坐标。读取的颜色值也可能再一次被转换,最后这些值将改变表面的属性,例如材质或者着色法线。下图展示了这一过程。而贴图管线看起来较为复杂,但是这并不意味着其中的每一步都是必不可少的。

当一个三角形拥有砖块贴图,其表面上的某一点的采样将会基于上述管线生成(如下图所示)。物体表面一点的本地坐标(x,y,z)为(-2.3,7.1,88.2)。之后该点将被投影。 正如世界地图将三位空间转换至二维空间,投影函数把向量(x,y,z)转换为只包含两个成员的向量(u,v)。这个例子中使用的投影函数是一个正交投影。而转换后的坐标点范围是[0,1]。而例子中的坐标则转换为(0.32,0.29)。我们将使用这个贴图坐标来找到这个位置所对应的图像中的颜色。砖块贴图的分辨率为256×256,所以相对应的函数用256乘以(u,v),得到(81.92,74.24)。舍弃小数部分,其对应砖块图像中的像素点(81.74),其颜色为(0.9,0.8,0.7)。贴图的颜色值位于sRGB颜色空间,所以如果需要在着色等式中使用这个颜色值,我们需要将其转换为线性值,(0.787,0.604,0.448)。(请参考5.6小节

6.1.1 投影函数

贴图处理的第一步是拿到表面坐标点位置,之后将其投影至贴图坐标空间,通常我们使用二维坐标(u,v)表示。通常来说,三维模型软件允许美术为每一个顶点定义一个(u,v)坐标。这些值可能通过投影函数初始化得到,也可能通过网格模型的解压算法得到。投影函数一般会将三维空间内的点转换至贴图坐标。模型软件通常使用球形投影,圆柱形投影和平面投影。

投影函数也可以使用其他的输入条件。例如,我们可以基于表面的法线来选择平面投影的方向。模型的面与面之间的接缝处可能会有贴图匹配的问题;Geiss展示了一种融合技术。Tarini提出了polycube maps,模型将进行立方体投影。

其他的一些投影函数则更像表面的创建与tessellation。例如,参数化的弧度表面拥有一组(u,v)。如下图所示。贴图坐标也可以基于各种不同的参数生成,例如,视野方向,表面的温度,或者其他因素。而投影函数的目的就是生成贴图坐标。而获取位置信息只是其中的一种。

上图展示了不同的贴图投影。从左至右分别为,球型投影(spherical),圆柱体投影(cylindrical),平面投影(planar)和自然投影(natural)。第二行的三个物体分别代表三种不同投影(其并非自然投影)。

不可交互的渲染器通常将投影函数作为渲染过程的一部分。一种投影函数可能就能满足所有模型的需求,但是美术通常会使用工具将模型进一步细分并运用不同的投影函数。

在实时渲染中,投影函数的运用通常会发生在模型阶段,而投影的结果将被存储在顶点中。但也并不完全是这样;有时在顶点着色器或者像素着色器中使用投影函数会带来一些好处,例如增加精确度,实现不同的效果,包括动画(我们将在之后的小节学习)。一些渲染方法,例如environment mapping(环境映射)则有着特殊的投影函数,且基于每一个像素。

上图所示的球型投影,将点投射到一个以某一点为中心的球体上。这一类投影与Blinn and Newell的环境映射相同(我们将在第十章中学习)。圆柱体投影中的贴图坐标u的计算方式和球型投影相同,贴图坐标v则是其沿着圆柱体轴向的距离。这种投影适用于拥有轴向的物体。当表面与圆柱体的轴向几乎垂直时会产生贴图变形。

平面投影类似于x射线,其沿着平行线的方向进行投影并将贴图运用于物体所有的表面。其使用正交投影。这一类的投影一般运用于decal(贴花,我们将在第二十章中学习)。

如果物体表面是投影方向的侧立面,那么会产生严重的变形,美术通常需要手动将模型分割为近乎平面的各个部分。也有工具通过拆解模型来减少变形,或者创建一组最佳的平面投影等等。而最终目的是让每一个多边形都能够分享到一部分贴图,同时也要保证网格模型的连续性。

贴图坐标空间并不一定是二维平面;有时也会是三位空间。这种情况下,贴图坐标将变成一个三维向量,(u,v,w),w表示在投影方向上的深度。其他系统最多会使用四维空间,例如(s,t,r,q);q类似于齐次坐标中第四个值。其类似于电影或者幻灯片投影仪,随着距离增加,投影贴图的尺寸也会变大。

而另一种重要的贴图坐标,其表示方向,也就是说,空间中的每一个点都是通过我们输入的方向来读取。其原理类似于一个单位球体,球体表面的每一个点都有着不同的法线向量,而我们会使用这个法线向量作为读取像素点的坐标。最为常见的例子就是cube map

还需要注意的是,我们有时会使用一维贴图图像与其函数。例如,一个地形模型的颜色可能由海拔决定,例如,低海拔地区为绿色;山区的顶部则是白色。线段也能拥有贴图;我们可以用一张半透明的贴图来绘制雨滴。

由于多张贴图可以运用于一个表面,因此我们可能会定义多组贴图坐标。但是,坐标值的原理是相同的:这些贴图坐标将在物体表面进行插值,之后来读取贴图中的颜色值。但是,在进行插值之前,这些贴图坐标会通过映射函数进行转换。

6.1.2 映射函数(Corresponder Function)

映射函数会将贴图坐标转换为贴图空间内的坐标点。有了这些函数,我们能更方便的将贴图运用在物体表面上。映射函数的一个例子是使用API来决定贴图的哪一部分能够被显示。

映射的另一种类型就是矩阵转换,其运用于顶点着色器或者像素着色器中。它让我们能够位移,旋转,缩放,修剪或者投影一张贴图。正如我们之前学习的,转换的顺序会影响最终的外观表现。我们需要注意的是,贴图的转换顺序正好与物体的转换顺序相反。这是因为,贴图的转换会影响贴图空间。而贴图自身并没有被转换;而是定义贴图坐标点的坐标系发生了变化。

映射函数的另一个作用是控制图像显示的方式。我们都知道,图像会在表面(u,v)范围在[0,1]之内显示。但是,如果超过了这个范围呢?映射函数将决定这种情况下的显示方式。在OpenGL中,这一类的映射函数被成为“wrapping mode”;在DX中,其被称为“texture addressing mode”。该类型下几个常用的映射函数为:

  • wrap(DX),repeat(OpenGL)或者tile——图像会在物体表面不断重复。如果我们需要材质在物体表面不断重复,那么我们会使用这个函数,而通常这个函数是默认选项。
  • mirror——图像会在表面重复显示,但是会依次反转。例如,贴图坐标在范围[0,1]是正常显示的,范围[1,2]是翻转的,而范围[2,3]又是正常的,以此类推。
  • clamp(DX),clamp to edge(OpenGL)——贴图坐标超过[0,1]的部分将被clamp至该范围。其造成的结果就是,贴图边缘的图像被不断重复。这个函数能够避免在双向线性插值时突然读取到贴图对面边界的图像。
  • border(DX),clamp to border(OpenGL)——贴图坐标超过[0,1]的部分将以我们预定义的边界颜色进行绘制。这个函数能够在单色的表面绘制decal。

上述几个函数的效果如下图所示,从左至右分别为wrap,mirror,clamp和border。

我们可以为贴图的不同轴向设置不同的映射函数,例如,在u轴方向,贴图可以是repeat/wrap的,而在v轴方向是clamp的。在DX中,还有mirror once的模式,其只会在贴图坐标为0的点对贴图进行反转,对超过范围[0,1]的部分则实行clamp,其常用于对称decal。

对贴图进行重复tiling始终增加场景细节但是开销又不那么大的方式。但是,如果我们将贴图重复3次以上,其效果就不会那么好,因为我们的眼睛能够识别这一模式。而对于这一“周期性”问题的解决办法是,将贴图与另一个non-tiled(未被tiling)的贴图进行组合。在商用的地形渲染系统中,多张贴图将基于地形的类型,海拔,坡度和其他因素进行组合。场景中的几何模型的贴图也会进行tiling,例如,草丛,石头等等。

另一种避免周期性问题的方法是使用着色器程序来实现特殊的映射函数,其会随机重组贴图模式。Wang tiles就是这一类方法中的一种。其是一组正方形的tile,且边缘与边缘之间互相吻合。在处理贴图时,我们会随机选择tile。

最后的一种映射函数则是基于图像的尺寸。一般来说,贴图的贴图坐标u和v的范围为[0,1]。在我们举的砖块例子中,我们将图像的分辨率乘以贴图坐标以求出像素的位置。将贴图坐标(u,v)的范围定义为[0,1]的好处是,即使我们的模型使用了不同分辨率的贴图,只要贴图坐标的范围不变,我们不需要改变顶点中所存储的贴图坐标。

6.1.3 贴图值

在我们使用映射函数生成贴图空间坐标之后,坐标值将用于读取贴图值。对于图像贴图,我们需要读取图像中的纹理(texel)信息。我们将在下一小节,详细地学习这一过程。在实时渲染领域,我们使用的贴图大多数为图像贴图,但是也会使用一些程序化函数。在使用程序化贴图(procedural texturing)时,从贴图空间位置读取贴图值的过程不再是内存查询的过程(例如,256×256的贴图中,我们读取(25,25)所对应的纹理信息),而是一种函数运算。我们将会在之后的小节学习程序化贴图。

最为直接的贴图值就是RGB,我们使用这个值来替代或者修改物体表面某一点的颜色。而另一种数据类型则是RGBα。α值通常表示颜色的不透明度。也就是说贴图可以存储任何数据,例如表面的粗糙度等等。

贴图所返回的值在使用之前,我们可以对其进行转换。这些转换都在着色器程序中实现。一个常见的例子就是将范围(0.0,1.0)的值重新映射到范围(-1.0,1.0)。

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据