(转)纹理映射过滤
转自:http://blog.csdn.net/i_dovelemon/article/details/27839279
过滤器(Filters)
在3D空间中,纹理图的大小往往并不总是和定义的三角形同样的大小。也就是说,我们需要对纹理进行放大和缩小,也就是进行缩放操作。那么我们如何对纹理进行操作,才能够让纹理能够放大和缩小之后,不会变的混乱不堪了?
在这里,使用的就是各种采样方法,对纹理图进行采样,为了更好的效果可能还需要再使用滤波器进行过滤。在DirectX中,支持三种不同种类的采样方法。下面一一介绍他们。
首先给出这三种采样方式的名称,他们分别是:
- 点采样
- 双线性采样
- 三线性纹理滤波采样
我们知道,在上面定义了几个顶点的纹理坐标,但是对于一个三角形来说,它是一个平面,它需要将纹理整个的映射到这个平面上来。也就是说,如何通过这三个顶点的纹理坐标,来铺满整个平面? 读者,可能注意到这个问题,和我们以前讨论的如何通过顶点的颜色,然后将整个三角形进行着色的问题很相似。对头,这里,我们也是使用插值的方式来获取每一个像素点的纹理坐标,然后通过这个纹理坐标,采用不同的采样方式来获取纹理图中的像素值,使用这个采集的值来填充这个像素。
我们知道了如何进行纹理的插值(插值方法和前面介绍的颜色插值一致,这里不再赘述)之后,就要确定到底使用哪种采样方法来进行采样。下面将一一介绍不同的采样方法。
点采样
点采样,故名示意,就是使用我们进行插值后的纹理坐标来将它扩展到与纹理图相应的尺寸大小(还记的前面说过的,纹理坐标实际上是归一化的坐标),然后将这个变换后的纹理坐标进行取整,也就是截取小数部分,只保留整数部分,然后就使用这个整数的坐标来获取纹理图中对应的纹素值。
比如下面的数据:
我们纹理图的尺寸是128*128 ;
我们经过插值计算后的某个像素点的纹理坐标为(0,70,0.55)
那么我们将这个纹理坐标进行变化,使得纹理坐标的尺寸和纹理图的尺寸一致,即:
0.70 * 128 = 89.6, 0.55 * 128 = 70.4
再进行取整操作得到最后的纹理图上的坐标为(89, 70)
然后,我们就使用这样的坐标,来获取纹理图中第89列,第70行的那个像素的值,用这个值来填充我们计算的那个像素点的颜色。
读者可以看出,由于我们截取了小数部分,所以失去的部分的信息,这样的采样方法效果肯定是很不理想的。一种稍微改进点的方法就是保留小数部分,而将采取纹理图中相邻的两个像素的值,使用小数部分作为权值来进行采样。
拿上面的例子来说吧,我们计算后的保留小数的纹理图坐标为(89.6, 70.4),而取整之后的数据为(89,70)。
那么我们可以发现,这个像素实际上占用的空间是89列和90列这两个像素的位置,也就是说它有0.6的(89,70)位置像素值,有0.4的(90,70)的像素值,所以最后的像素值应该为:
0.6 * Texel(89,70) + 0.4 * Texel(90, 70)
通过这样的方式,我们能稍微的改进点采样方法的效果。
点采样方法效果很差,但是由于操作简单,所以效率会很高。
双线性采样
读者可能发现,我们上面讨论改进版的点采样方法时,故意没有考虑v坐标的跨度关系。也就是说,我只考虑了u坐标,在相邻两个坐标上的权值关系。所以,如果将v坐标上的权值关系也考虑进去,效果是否更加的逼真了呢?
的确,这就是所谓的双线性采样理论。通过在u和v两种维度上,都考虑权值关系,来获取最后的像素值。
还是拿上面的关系举例,很明显,这个纹理坐标牵涉到了四个像素,他们的坐标分别是(89,70), (90,70), (89,71)和(90,71)。
我们知道了它是和这四个像素点相关的,那么只要获取每一个像素点上的权值,我们自然就可以使用权值平均的方法来获取最后的像素值了。为了明确该纹理坐标,在这四个像素上所占有的权值,我们使用图示的方式来阐释:
我们就可以通过下面的公式来计算各个像素的权值,这个公式可以很容易的从上图中推导来:
(89,70) : (89.6 - 89) * (70.4 - 70) = 0.6 * 0.4 = 0.24 ;
(90,70) : (90 - 86.6) * (70.4 - 70) = 0.4 * 0.4 = 0.16 ;
(89,71) : (89.6 - 89) * (71 - 70.4) = 0.6 * 0.6 = 0.36 ;
(90,71) : (90 - 89.6) * (71 - 70.4) = 0.4 * 0.6 = 0.24 ;
我们将上面计算出来的权值相加,即0.24 + 0.16 + 0.36 + 0.24 = 1.0 ,也就是说完整的表述了这个像素值。
然后我们用上面的权值分别乘以每一个像素的值,来获取最后的像素:
0.24 * Texel(89, 70) + 0.16 * Texel(90, 70) + 0.36 * Texel(89, 71) + 0.24 * Texel(90, 71)
好了,通过上面的方法,我们就能够得到最后的像素值了,而且这个方法能够基本上完全保留纹理坐标的信息,所以效果十分的不错(之所以说基本上保留,是因为在进行插值计算的时候,使用浮点数,总是会存在一点误差,所以会损失一点信息)。很多游戏,都是采用这样的方法来进行纹理的缩放的。
Mipmap链
在讲解三线性纹理滤波采样之前,先来讲解下什么是Mipmap,以及使用Mipmap来做什么用途。
我们知道,在3D空间中,纹理图总是要被缩放的,而我们在原本纹理图上进行采样,并不总是那么可靠。比如说,纹理图的大小实际上是128*128的尺寸。而我们在3D程序中,我们仅仅需要一个4*4的纹理图就可以了。如果,我们在这个大图上,获取这个4*4的小图的话,操作复杂,而且效果不理想。所以,如果,我们能够预先使用这个大图,来创建一些尺寸较小的图,那么在进行采样的时候,我们可以选取,与需要的尺寸最接近的纹理图来进行采样。通过这样的方式,不仅能够提高效率,也能某种程度上改善效果。
Mipmap的作用就是这样的。在DirectX中,你加载纹理的时候,它总是为你创建了Mipmap链。如果你加载的是一个128*128的纹理图,那么它会为你创建一个64*64, 32*32, 16*16, 8*8, 4*4 , 2*2, 1*1的纹理图。
创建这些纹理图的方法,就是需要进行采样,同样的,它是使用前面介绍的双线性采样方法进行采样的。
当在3D空间中,某一个三角形需要一个纹理图的时候,我们先来判断,它最接近的纹理图是哪一个。比如说,它需要的实际上是50*50的纹理图,那么我们就会发现,使用64*64的纹理图,来进行采样,效果会更好。实际上,选择哪一个Mip等级,有很多不同的实现方法,我并不知道DirectX是使用哪种方式的,但是,它的原理无外乎就是选取最接近该纹理的纹理等级。
三次线性滤波采样
好了,在讲述完了上面的Mipmap之后,就可以来讲解如何实现三次线性滤波采样了。一般来说,三次线性滤波,已经是纹理滤波的极限了,没有办法做的比它更好了。实际上,这个滤波方式,就是结合了前面介绍的Mipmap和双线性采样理论来共同实现。
我们首先通过某种方法来获取最终的Mipmap等级,可能是通过面积计算,也可能是通过其他的方式来获取,而最终获取到的Mipmap等级值,会是像4.3这样的带有小数的值。这个小数的意思就是,我们将要使用Mipmap等级为4和5的这两个纹理来进行纹理采样,采样的方法就是使用双线性纹理滤波采样来进行。通过这样的方法,我们能够得到更加平滑的效果。
实现方式,将不会以代码的形式来提供给大家,如果读者感兴趣的话,可以自己写个软引擎,然后测试一下这个算法。但是不要期望,在软件引擎中大量的使用此种算法,这样的算法消耗将是非常巨大的。只能够少量的使用。
转自:http://dev.gameres.com/Program/Visual/3D/Bilinear.htm
|
||||
|