双线性过滤Bilinear filtering)是进行缩放显示的时候进行纹理平滑的一种纹理过滤方法。 在大多数情况下,纹理在屏幕上显示的时候都不会同保存的纹理一模一样,没有任何损失。正因为这样,所以一些像素要使用纹素之间的点进行表示,在这里我们假设纹素都是位于各个单元中心或者左上或者其它位置的点。双线性过滤器利用像素所表示点周围四个最近的点(纹素点)之间进行双线性插值

经过缩放的一小部分位图图像,左图使用了最近邻插值过滤,中间的图用了双线性过滤,右图是用了立方过滤。立方插值过滤类似于双线性插值

公式

编辑

在下面这些方程中,uk 与 vk 是点 k 处的纹理坐标,yk 是点 k 处的颜色值。不带下标的值表示像素点,带有下标 0、1、2、3 的值表示从左上沿顺时针方向到左下包围像素的纹素点,带有下标 a、b 的值分别表示像素点在点0、1的连线与点3、2连线上的投影点。由于双线性插值方程是线性插值方程的一种特殊形式,所以我们从较简单的线性插值方程开始分析。

 
 
 

假设纹理是正方形的位图,并且满足

 
 
 
 
 

我们进一步定义,

 
 

这样就可以将插值方程化简为:

 
 
 

代入方程,得到:

 

或者,

 

这种表示相当简单。但是,如果图像只进行缩放处理,而没有旋转、扭曲、透视或者其它处理,那么使用独立的方程计算并且保存用于后面数据行计算的 yb 或者 ya 速度将更快。

示例代码

编辑

这部分代码假设纹理是常见的正方形,没有Mipmap,并且只有一个通道的数据。只有一个通道的情况非常少见,几乎所有的纹理都是彩色的,都有红色、绿色与蓝色通道,有些还有阿尔法透明通道,所以每个 y 都要进行三到四次的计算

double getBilinearFilteredPixelColor(Texture tex, double u, double v) {
  u *= tex.size;
  v *= tex.size;
  int x = floor(u);
  int y = floor(v);
  double u_ratio = u - x;
  double v_ratio = v - y;
  double u_opposite = 1 - u_ratio;
  double v_opposite = 1 - v_ratio;
  double result = (tex[x][y]   * u_opposite + tex[x+1][y]   * u_ratio) * v_opposite + 
                  (tex[x][y+1] * u_opposite + tex[x+1][y+1] * u_ratio) * v_ratio;
  return result;
}

局限

编辑

在纹理缩减到一半或者放大一倍的范围内,双线性过滤都能够有非常好的精度。这也就是说,如果纹理在每个方向都有 256 个像素,那么将它缩减到 128 以下或者放大到 512 以上的时候,由于会丢掉太多的像素或者进行了过多的平滑处理,纹理看起来就会很差。通常,可以在缩减的过程中使用 Mipmap 来实现较好的性能;但是,在透视图中的纹理上的经过双线性过滤处理的两个不同尺寸的 mipmap 之间的过渡将非常明显。三线性过滤尽管比较复杂,但是可以使得过渡非常平滑。

为了快速说明纹理过滤中如何丢失纹素,这里有一组用来自于 8 纹素宽纹理的数字表示的盒子的中心,它们与蓝色表示的来自于 3 纹素宽的纹理表示的盒子中心的一组数字混杂在一起。红色数字表示计算 3 纹素纹理中根本不需要的纹素。

0.0625, 0.1667, 0.1875, 0.3125, 0.4375, 0.5000, 0.5625, 0.6875, 0.8125, 0.8333, 0.9375

特殊情况

编辑

通常纹理是有限大小的,我们经常会得到坐标位于纹素坐标之外栅格之外的像素。可以用以下几种方法来处理这种情况:

  • 旋绕纹理,这样一行中的最后一个纹素将出现在该行第一个纹素的前面,一列中的最后一个纹素将出现在该列第一个纹素前面。在纹理平铺的时候这种方法可以得到最好的效果。
  • 纹理之外的区域使用一种颜色。这可能并不是一个非常了不起的想法,但是如果纹理要放到固体或者透明背景上,那么就可以使用这种方法。
  • 无限重复边界纹素。如果所设计的纹理不是要重复使用的话,这种方法可以很好地工作。

参见

编辑