Computer Graphics · Texturing
Bilinear Weighting and the Tent Reconstruction Filter
How four texel taps and two linear interpolants reconstruct a continuous signal, why the resulting kernel is a tent, and what that kernel does to frequencies above half the texel rate.
Nearest-neighbor sampling treats each texel as a constant over its cell, which makes the reconstructed function a staircase. The staircase is piecewise constant, so it contains frequency content at every multiple of the texel rate no matter how smooth the underlying image was. Bilinear filtering replaces the staircase with a piecewise-linear surface that passes through the texel values and connects them with straight lines. It is the cheapest possible improvement and it is built from four memory reads and three interpolants, yet the shape of the kernel it implies determines what the filter can and cannot remove.
Deriving the kernel from two linear interpolants
Bilinear filtering is separable. The sampler computes the fractional position in texel space, takes the two texels bracketing it on each axis, blends along one axis, then blends the two results along the other:
/* Bilinear tap on a single-channel float image, repeat addressing.
x0 and y0 come from texel-grid addressing and may be -1 or w/h. */
static inline float lerp(float a, float b, float t) {
return a + t * (b - a);
}
static inline float texel(const float *img, int w, int h, int x, int y) {
x = ((x % w) + w) % w; /* repeat: fold any sign into [0, w) */
y = ((y % h) + h) % h;
return img[y * w + x];
}
float bilinear(const float *img, int w, int h, int x0, int y0,
float ax, float ay)
{
float t00 = texel(img, w, h, x0, y0 );
float t10 = texel(img, w, h, x0 + 1, y0 );
float t01 = texel(img, w, h, x0, y0 + 1);
float t11 = texel(img, w, h, x0 + 1, y0 + 1);
float top = lerp(t00, t10, ax);
float bot = lerp(t01, t11, ax);
return lerp(top, bot, ay);
}
The interpolation weights for the four taps are $(1-a_x)(1-a_y)$, $a_x(1-a_y)$, $(1-a_x)a_y$, $a_x a_y$. They are non-negative and sum to one for any fractional position, so a bilinear tap is an affine combination: constant regions are reproduced exactly, and no tap can push a channel outside the range spanned by its four inputs. That last property is why bilinear filtering never rings or overshoots, and why it is the default in every hardware sampler.
The order of the two blends does not matter mathematically, but it does matter for the interpolator. The form above evaluates one interpolant, then the other, which is what fixed-function hardware does with a four-input bilinear unit. On hardware that interpolates in reduced precision, lerping along the shorter axis first keeps the intermediate result closer to the inputs. The a + t*(b-a) form is preferred over (1-t)*a + t*b because it is exact at $t = 0$ and it uses one multiply instead of two; the (1-t)*a + t*b form is exact at $t = 1$ instead. Neither is exact at both ends in floating point, which is why the fractional part is computed so that $a_x = 1$ never occurs.
The kernel is a triangle, and its spectrum is a sinc squared
The reconstruction kernel is the weight the sampler assigns to a texel as a function of distance from that texel’s center. For bilinear in one dimension that weight is $1 - |d|$ for $|d| \le 1$ texel and zero beyond, a triangle of unit height and unit half-width. In two dimensions the kernel is the separable product of two triangles, a pyramid-shaped tent whose support is a 2×2 texel square.
Multiplying the tent by the texel lattice and integrating gives a reconstruction whose spectrum is the texel spectrum multiplied by the squared sinc: the Fourier transform of a triangle is $\mathrm{sinc}^2$. Squaring is the important part. A box filter (nearest neighbor) has a transform of $\mathrm{sinc}$, whose first sidelobe is only about $-13$ dB below the passband and whose zeros are narrow. The tent’s transform decays as $1/f^2$, so its first sidelobe sits near $-26$ dB and its stopband is far cleaner. Bilinear filtering therefore attenuates, but does not eliminate, the spectral replicas that the texel lattice creates.
The passband consequence is a real cost. $\mathrm{sinc}^2$ is not flat to the Nyquist limit; it rolls off gradually and is already several decibels down at half of Nyquist. A texture sampled with bilinear filtering and no magnification compensation looks slightly soft compared with the original continuous image, and the softness increases with the number of texels per screen pixel. Anisotropic magnification filters and higher-order reconstruction kernels exist to flatten that passband, at the cost of more taps.
Two-dimensional separability also means the filter is not isotropic. The tent’s footprint in frequency space is the product of two 1-D responses, so diagonal directions are attenuated differently from axis-aligned ones. A checkerboard pattern rotated 45° therefore survives bilinear reconstruction differently from one aligned to the axes, which is observable as a preferred orientation in fine detail. Rotated-grid texture layouts address this at the storage level; the filtering consequence is inherent to the separable kernel.
What bilinear does not fix
Bilinear filtering removes the staircase but not the aliasing. The reason is that minification inverts the sampling problem: when many texels map to one pixel, the correct operation is to prefilter the texture, averaging all the texels in the pixel’s footprint, before sampling. Bilinear only averages the four texels nearest the sample point, which is a footprint of one texel, far smaller than the pixel’s footprint in any minified region. Frequencies above the screen’s Nyquist limit pass through the filter and fold down to lower frequencies, producing moiré, shimmer, and crawl on moving surfaces.
Magnification is the case where bilinear is close to correct, because the pixel footprint is smaller than a texel and the reconstruction kernel is doing the job it was designed for. Minification is the case that requires a prefiltered representation, and the prefiltered representation is the mip chain, described in building a mip chain by box reduction. Bilinear alone, applied at level 0 in a minified region, is a point sample with a slightly wider aperture.
There is a second, more local problem: the four taps are chosen from the fractional position, and the fractional position is derived from a coordinate interpolated across the triangle. Perspective-correct interpolation divides by $w$, and the division happens per fragment at limited precision. On a triangle receding sharply into the distance, the reconstructed coordinate near the horizon has very few significant bits left, so the fractional part is effectively quantized. The result is that the four taps collapse to a single texel over patches of several pixels, and bilinear filtering degenerates to nearest-neighbor exactly where detail is densest. Multisampling the coordinate or using higher-precision interpolation at the primitive setup stage helps; no filtering knob inside the sampler does.
Cost, precision, and the hardware unit
The arithmetic is three interpolants, each one subtract, one multiply, and one add, assuming the four taps have already been fetched. Address generation is the larger cost: four independent texel addresses, each requiring a coordinate conversion, a wrap-mode application, and an offset into the level’s storage. Hardware texture units therefore fetch the 2×2 block as a unit when the layout permits, and the memory system’s behaviour decides the real cost. If the four texels share a cache line, the fetch is one transaction; if the layout is row-major and the two rows are $W \cdot b$ bytes apart, it is two. The swizzled layouts discussed in swizzled texel layouts and cache behaviour exist to make the first case common.
Precision inside the filter is a design choice with visible consequences. Fixed-point interpolators with 8 fractional bits produce blending weights quantized to $1/256$; the resulting banding is usually invisible on 8-bit colour but becomes visible when bilinear filtering is used for magnification of a gradient in a higher-precision format, or when a filtered value is fed into a computation rather than displayed. Filtering in sRGB-encoded space is a separate error of a similar size: averaging two sRGB-encoded values computes a weighted average of the encoding, not of the light, so a mid-grey blend comes out too dark. Hardware with sRGB texture formats converts to linear before filtering for exactly this reason, and the cost is a per-tap conversion.
Finally, bilinear has no notion of a footprint larger than one texel, so it cannot antialias a minified texture no matter how many taps are added. The family of filters that use a wider footprint is examined in anisotropic filtering and footprint-shaped kernels, which generalizes the tent to an ellipse aligned with the pixel’s projection onto the surface.
References
- Fundamentals of Texture Mapping and Image Warping — Paul Heckbert, MSc thesis, UC Berkeley, 1989
- Pyramidal Parametrics — Lance Williams, SIGGRAPH 1983
- The OpenGL Graphics System: A Specification, Chapter 8: Textures and Samplers — Khronos Group