1. 纹理映射
对于任何一个三角形,我们在内部填充一个图形作为纹理。纹理 Texture 也就是我们在不同的位置所定义的漫反射系数。
2. 三维物体表面展开
任何一个三维物体的表面都是二维的图形,我们可以将一个三维物体表面映射到一个二维的图像上。图形的映射非常的复杂,这里不做过多解释。我们将纹理建立一个 𝑢 − 𝑣 坐标系。同时 𝑢, 𝑣 ∈ [0, 1]。当然,我们在为墙面,地面加入纹理的时候可以使用边缘连接连续的纹理,称为 tiled。这样子就可以复用纹理拼接大表面。我们可以知道每一个三角形的顶点坐标和对应的纹理坐标,那么我们怎么求出来三角形内部顶点坐标对应的纹理坐标。这就是一个插值问题,我们需要使用重心坐标的方式计算。
3.重心坐标
我们在之前的节中留下了以下疑问,已知三角形三个顶点的颜色,法线或者纹理坐标,如何求出三角形内部某一点对应的插值量?这个时候我们就需要使用重心坐标(Barycentric Coordinate)来计算。
对于 △𝐴𝐵𝐶 内任意一点 (𝑥, 𝑦 ) 可以满足
- (𝑥, 𝑦 ) = 𝛼𝐴 + 𝛽𝐵 + 𝛾 C
- 𝛼 + 𝛽 + 𝛾 = 1,
- 𝛼, 𝛽, 𝛾 >= 0
三角形平面上的任意一点都可以用三角形三个顶点坐标的线性组合表示。三角形内部的点必须满足系数加和为 1 并且系数均为非负数。那么重心坐标就是 (𝛼, 𝛽, 𝛾 ),已知任意两个重心坐标可以直接推断出第三个坐标。重心坐标可以通过面积求出,我们令三角形内一点和三个顶点相连,每一个顶点所对的小三角形的面积为 𝐴𝐴, 𝐴𝐵, 𝐴𝐶,那么重心坐标可以用以下方式计算:
当重心坐标为 (1/3,1/3,1/3) 的时候,这个点是三角形的重心。已知三个顶点的坐标也可以直接推算出重心坐标:
如果三角形三个顶点对应了三个向量(颜色,法线或者纹理坐标),那么内部点对应的向量值是使用重心坐标进行的线性组合。假设三个顶点对应的向量是 𝑉𝐴, 𝑉𝐵, 𝑉𝐶,那么三角形中任意一点的插值后向量是𝑉 = 𝛼𝑉𝐴 + 𝛽𝑉𝐵 + 𝛾 𝑉𝐶。同时,重心坐标在投影后不能保证结果不变,因此我们在空间中需要使用三维坐标计算的重心坐标来进行插值。
4.纹理映射的问题
纹理映射主要分为两步,第一步是把像素点坐标映射到纹理坐标 (𝑥, 𝑦 ) → (𝑢, 𝑣 )。第二部根据纹理坐标得到对应的漫反射系数 (𝑢, 𝑣 ) → 𝑘𝑑,纹理上的像素叫做纹理元素或者纹素(Texel)。但是如果纹理过大或者过小都会出现一些问题。
如果纹理太小的话…
如果纹理本身太小,但是物体像素点比较多,那么就会产生非常多类似于马赛克的像素。这是由于很多像素点会求出浮点数纹理坐标,在取整后会导致马赛克的产生。我们用两种方式解决,一种是双线性插值法,另一种是二次样条插值法。
双线性插值(Bilinear Interpolation)指的是对于任意一个纹理坐标,我们使用其临近的四个纹素值进行两次线性插值得到这个坐标对应的漫反射率。
在一维上的线性插值可以表示为:lerp(𝑥, 𝑣0, 𝑣1) = 𝑣0 + 𝑥(𝑣1 − 𝑣0)。首先我们在水平方向上做两次线性插值:
然后我们在纵向上做一次线性插值:
插值的结果相比于之前变化更加顺畅。
双立方插值(Bicubic Interpolation)使用相邻 16 个点进行计算,效果更好但是计算量相对来说更大。
如果纹理太大的话…
当纹理过大的时候,近处的物体会产生锯齿,远处的物体会产生摩尔纹,也就是说结果会产生走样。主要原因是因为当物体离得越远,每一个像素所代表的纹素的数量会变多。这个时候再使用像素和纹素一一对应的方式是不可靠的。
我们的解决方法是避免采样。通过像素点直接得到对应纹素区域的平均值。这里我们引入 Mipmap 来解决这样一个范围查询的问题。Mipmap 是一个快速,近似并且只用于正方形区域的范围查询方法。主要思想如下:我们从一张纹理生成一系列的纹理。每一个纹理的大小都是之前纹理大小的一半,最后得到的最小的纹理是一个 1 × 1 的纹理,这就是一个图像金字塔。最终存储这些纹理额外的开销是原本纹理的三分之一。
我们需要计算每一个像素对应纹理的方形大小,并使用对应层的纹理。
对于一个点 𝑢(0, 0) 和其相邻的两个点之间在纹理上的距离,可以用微分形式表示,那么这个像素所对应纹理方形的大小是:
那么对应的层数为:𝐷 = log2𝐿。为了保证我们能够取到的层数是一个连续的层数,我们使用三线性插值法得到对应的结果。首先,对于任意一个非整数层数,我们在其上下两层使用双线性插值进行取值。接下来我们在两个层之间使用线性插值就可以得到最后的结果。
Mipmap 也存在局限性。由于所有的纹理都必须对应到一个正方形的区域,但是并不是所有的纹理都可以被一个正方形完美的包住(例如细长的长方形,或者是在对角线上的长方形)。最终远处的纹理会变得非常的模糊,丢失了许多细节。
为了解决这个问题,我们使用各向异性过滤(Anisotropic Filtering)解决。各向异性过滤指的是加入只在水平方向或者竖直方向上缩小的纹理,这样可以应对不同长方形纹理块区域。但是对于对角线上的纹理块依然不好解决。并且存储多余的纹理需要多使用原来纹理三倍的开销。
除此之外我们还可以使用 EWA 过滤得到更好的结果。可以把纹理拆分成不同的圆形块进行多次查询获得最终的结果。但是开销也会比较大