Kaiser windowed sinc filter for mip mapping

These older but informative article talks about how a Kaiser windowed sinc filter is a good way to generate mips.

The Inner Product - Mipmapping - Part 1 | Jonathan Blow

The Inner Product - Mipmapping - Part 2 | Jonathan Blow

Unfortunately, the source code it mentions isn't anywhere to be found.

Can anyone explain how a kaiser windowed sinc filter is calculated and/or give the kernel weights to use for convolution?

• I've not used a Kaiser windowed sinc, but I did find with (some) other windowed sincs that you can get some very strange behaviour once you'd normalised all the non-zero sample weights. In particular, if you plot the weight applied to the "centre" sample as the centre of the window moved from being located on the centre weight to being just off it, the magnitude would increase (rather than decrease as you'd expect with a sinc). Mar 12 '18 at 12:04
• One issue with using a sinc filter for mip-mapping has to do with the hardware texture units. Mipmaps are usually built in a way that the next level sample falls 'between' the previous level samples. That is as if the samples are located at half-integer coordinates and the origins of all the levels coincide. Sinc filter downsampling, on the other hand, filters the data and then throws away every other pixel. This results in a visible half pixel shift. If you try to average those pixels instead of subsampling, then you arrive at a 2x2 box filter essentially. May 19 '19 at 5:27

I was curious so I tried it. I used the Kaiser Window from Wikipedia: $\frac{I_0\left(\alpha\sqrt{1-x^2}\right)}{I_0\left(\alpha\right)}$

This page has a slightly different formulation using $\pi\alpha$ instead of $\alpha$. Given the comments in the page you linked about the Kaiser window being very similar to Lanczos and how they even overlap in the graph, I suspect they also used the former formula. You can see how close they are if you plot both on Wolfram.

This left me wondering if that's what Mitchell originally meant as it's not "noticably better". I could not see the difference at all with my test texture (it's there, just not noticeable). However, using the second formula, the difference in the plots is much more striking. And there is a noticeable difference in the resulting images. Whether it's better is a matter of interpretation and depends on the filter size.

With $x \in \left[-1,1\right]$ , I used this C++ windowing function:

#include <boost/math/special_functions/bessel.hpp>
float bessel_i0( float x )
{
return boost::math::cyl_bessel_i( 0, x );
}

float window_kaiser( float x )
{
const float alpha = 4.0f * M_PI;
float k =
bessel_i0( alpha * sqrtf( 1.0f - x * x ) ) /
bessel_i0( alpha );
return k;
}

This is the second version. Remove the M_PI to get the first version which is more like Lanczos.

The modified Bessel function of the first kind seems to be a C++17 / boost thing. If you have neither, this quick hack will do:

float bessel_i0( float x )
{
float r = 1.0f;
float term = 1.0f;
for( int k = 1; true; ++k )
{
float f = x / float(k);
term *= 0.25f * f * f;
float new_r = r + term;
if( new_r == r )
break;
r = new_r;
}
return r;
}

Just don't consider that a general replacement for a proper bessel function. However, it's fine for computing the window as far as I could tell.

As the second wikipedia page I linked shows, that window_kaiser can be used on a symmetric filter with $N$ weights by using $x = \frac{2n}{N-1}-1$, where $n$ is $0, 1, 2 \dots N-1$.

Computing the weights of the sinc filter itself is another thing entirely as it depends on filter size, how much you're resizing, etc. I suggest posting a separate question if you don't know how do to that.