(转)纹理映射过滤

转自:http://blog.csdn.net/i_dovelemon/article/details/27839279 算法

过滤器(Filters)

          在3D空间中,纹理图的大小每每并不老是和定义的三角形一样的大小。也就是说,咱们须要对纹理进行放大和缩小,也就是进行缩放操做。那么咱们如何对纹理进行操做,才可以让纹理可以放大和缩小以后,不会变的混乱不堪了?app

         在这里,使用的就是各类采样方法,对纹理图进行采样,为了更好的效果可能还须要再使用滤波器进行过滤。在DirectX中,支持三种不一样种类的采样方法。下面一一介绍他们。测试

        首先给出这三种采样方式的名称,他们分别是:优化

 

  •        点采样
  •        双线性采样
  •         三线性纹理滤波采样

        咱们知道,在上面定义了几个顶点的纹理坐标,可是对于一个三角形来讲,它是一个平面,它须要将纹理整个的映射到这个平面上来。也就是说,如何经过这三个顶点的纹理坐标,来铺满整个平面?       读者,可能注意到这个问题,和咱们之前讨论的如何经过顶点的颜色,而后将整个三角形进行着色的问题很类似。对头,这里,咱们也是使用插值的方式来获取每个像素点的纹理坐标,而后经过这个纹理坐标,采用不一样的采样方式来获取纹理图中的像素值,使用这个采集的值来填充这个像素。spa

        咱们知道了如何进行纹理的插值(插值方法和前面介绍的颜色插值一致,这里再也不赘述)以后,就要肯定到底使用哪一种采样方法来进行采样。下面将一一介绍不一样的采样方法。.net

点采样

       点采样,故名示意,就是使用咱们进行插值后的纹理坐标来将它扩展到与纹理图相应的尺寸大小(还记的前面说过的,纹理坐标其实是归一化的坐标),而后将这个变换后的纹理坐标进行取整,也就是截取小数部分,只保留整数部分,而后就使用这个整数的坐标来获取纹理图中对应的纹素值。htm

      好比下面的数据:blog

      咱们纹理图的尺寸是128*128 ;游戏

      咱们通过插值计算后的某个像素点的纹理坐标为(0,70,0.55)ip

      那么咱们将这个纹理坐标进行变化,使得纹理坐标的尺寸和纹理图的尺寸一致,即:

     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

 

纹理映射的双线性插值滤波


  当你作纹理映射的时候,是否常常会注意到屏幕上显示出的那些明显锯齿,并且你用的纹理像素化得太明显了?如今,咱们将谈论如何来解决这个问题,而咱们使用的方法就是对你的纹理进行滤波。下面咱们将介绍几种经常使用的滤波方法,最后再详细介绍双线性插值滤波的具体实现。

Bi-linear Interpolation

  双线性插值是经过对纹理中的相邻像素进行处理来平滑掉屏幕输出像素间的锯齿的。使用双线性插值会使屏幕输出的图像显得更平滑。下面先来看看它的基本计算公式。
double texture[N][M];  // x = [0, N), y = [0, M)
double xReal;          // xReal = [0, N - 1]
double yReal;          // yReal = [0, M - 1]

int x0 = int(xReal), y0 = int(yReal);
double dx = xReal - x0, dy = yReal - y0,
omdx = 1 - dx, omdy = 1 - dy;

double bilinear = omdx * omdy * texture[x0][y0] +
omdx * dy * texture[x0][y0+1] +
dx * omdy * texture[x0+1][y0] +
dx * dy * texture[x0+1][y0+1];
  观察这段公式,你会看出,咱们颇有效地使用了纹理座标的小数部分来对四个纹理中的相邻像素进行插值。咱们按对应像素的距离来决定各个像素所占的权重。也就是说,当纹理的U座标的小数部分增长时,左边相邻像素的权重就会减小,减小出来的权重会增长右边的相邻像素上去。对垂直方向的V座标的状况也同此相似。

  在实际应用中,直接按这段公式来计算显然会很慢,你能够用定点整数和查表法来取消浮点和整型的混合运算以及去掉乘法。(提示:针对A、B两种颜色的混合创建 x*A+(1-x)*B 的结果表)

Mip-Mapping

  我第一次看到Mip-mapping技术是在游戏QUAKE里,而如今这种技术早已经是随处可见了。这种技术是由Williams在1983年发明的,“Mip”这个名称起源于“multum in parvo”,大概就是在一小块地方有不少东西的意思。

  具体说来,Mip-Mapping的思想就是构建一套纹理,总共须要大约1.3倍的内存。其中,每块子纹理是经过对父纹理过滤而获得,它的长和宽都是其父纹理的1/2,其面积为父纹理的1/4。接下来,在应用的时候,你根据距离选取最合适的一块来进行映射,实践证实,这种技术虽然简单,但对提升纹理映射的质量确实很是有效。

  经过Mip-Mapping,能够为较小的多边形映射上面积较小的纹理,这对减小纹理的扰动大有好处。举个例子,你有一块256x256大小的纹理,当它开始向远离观察者的方向开始移动时,你会看到它开始闪烁和颤动。这种现象的出现是由于咱们把一大块纹理映射到一个很小的区域而引发的。你可能在上一帧时,画的是纹理中(50,20)处的像素,到了下一帧,却画的是纹理中(60,30)处的像素。若是这两个像素相差很大,你就会观察到前面所说的现象了。总的来讲,这种剧烈的纹理座标的变化,会损害图像的品质,而且影响CACHE的效率,而Mip-Mapping无疑是解决这个问题的好办法。

Tri-linear Interpolation

  在介绍了双线性插值和Mip-Mapping之后,该来说讲三线性插值了。其实三线性插值也很简单,它就是前两种技术的结合。它在对Mip-Mapping的每块纹理作双线性插值的同时,还要对Mip-Mapping中相邻的两块纹理按距离再作一次插值。既算出较大的一块纹理上的某点双线性插值像素值和较小的一块纹理上的某点双线性插值像素值,再按目标同两块纹理的距离作一次相似的插值。

  使用三线性插值,能够消除Mip-Mapping里纹理切换(既上一帧时用的是某个大小的一块纹理,而下一帧时又换了一块的状况)时的忽然变化,从而能够提供很平畅的高质图像输出。

  同前两种技术相比,三线性插值的运算量很是大,目前只能依靠硬件来实现。

双线性插值纹理映射的实现

  下面,咱们经过一段描述性代码来简单看看双线性插值纹理映射是如何实现的。
 此处略去各类初始化代码,直接观察咱们最关心的部分 其中:U和V是16.16格式的定点整数 du和dv是浮点数 du = (U & 0xFFFF) / 65536.0 dv = (V & 0xFFFF) / 65536.0 invdu = 1.0 - du invdv = 1.0 - dv // 根据到相邻四个像素的距离计算各自的权重 Weight1 = invdu*invdv Weight2 = invdu*dv Weight3 = du*invdv Weight4 = du*dv // 求得各个像素的RGB颜色份量 r00 = Texture[V >> 16][U >> 16].Red g00 = Texture[V >> 16][U >> 16].Green b00 = Texture[V >> 16][U >> 16].Blue r01 = Texture[(V >> 16) + 1][U >> 16].Red g01 = Texture[(V >> 16) + 1][U >> 16].Green b01 = Texture[(V >> 16) + 1][U >> 16].Blue r10 = Texture[V >> 16][(U >> 16) + 1].Red g10 = Texture[V >> 16][(U >> 16) + 1].Green b10 = Texture[V >> 16][(U >> 16) + 1].Blue r11 = Texture[(V >> 16) + 1][(U >> 16) + 1].Red g11 = Texture[(V >> 16) + 1][(U >> 16) + 1].Green b11 = Texture[(V >> 16) + 1][(U >> 16) + 1].Blue // 按权重混合RGB颜色份量 Red = Weight1*r00 + Weight2*r01 + Weight3*r10 + Weight4*r11 Green = Weight1*g00 + Weight2*g01 + Weight3*g10 + Weight4*g11 Blue = Weight1*b00 + Weight2*b01 + Weight3*b10 + Weight4*b11 // 按最后求得的RGB颜色份量画点 PutPixel(X, Y, Pack(Red, Green, Blue))
  这段代码显然未经优化(起码不要去用那个PutPixel),若是你程序功力不够,可能会没法达到理想的优化目标,这时你能够直接使用硬件去实现(新的3D硬件都能支援这些功能)。但我相信你在理解了双线性插值滤波的思想之后,必定能触类旁通,利用它为你的游戏图像更添魅力。
相关文章
相关标签/搜索