I tried to follow Nathan Reed post Antialiasing: To Splat Or Not to implement splat method. The image I am getting thorugh the Lanczos filter is very different than Nathan. I do not understand where the problem is.
According to Wikipedia page Lanczos filter is defined as either of below formulas:
Nathan implementation seems to have missed a division by pi**2 and a multiplication by filter radius.
elif options.kernel == 'lanczos':
weight =
(np.sin(np.pi * sampleOffsetX) * np.sin(np.pi * sampleOffsetY) *
np.sin(np.pi * sampleOffsetX / options.radius) *
np.sin(np.pi * sampleOffsetY / options.radius) /
(sampleOffsetX**2 * sampleOffsetY**2))
Here is my code which borrowed most of the filter implementation from pbrt code:
private void btn_Render_Click(object sender, EventArgs e)
{
Random rand = new Random();
int samples = 16;
ImageFilm film = new ImageFilmSplat(pictureBox1.Width, pictureBox1.Height);
film.Filter = new SincFilter(tau: 4, width: 4);
for (int i = 0; i < film.Width; i++)
{
for (int j = 0; j < film.Height; j++)
{
EvaluatePixel(i, j, samples, rand, film);
}
}
for (int i = 0; i < film.Width; i++)
{
for (int j = 0; j < film.Height; j++)
{
Pixel px = film.GetPixel(i, j);
if (px.filterWeightSum != 0)
{
px.radiance = px.radiance / px.filterWeightSum;
film.SetPixel(i, j, px);
}
}
}
pictureBox1.Image = film.GetImage();
}
void EvaluatePixel(int i, int j, int nSamples, Random rand, ImageFilm film)
{
Vector halfpixel = new Vector(.5 / film.Width, .5 / film.Height, 0);
//all samples between [0,1]
Sample[] samples = sampler.Random(rand, nSamples);
for (int n = 0; n < nSamples; n++)
{
//set sample coord at centre of the pixel
double cx = ((double)i / film.Width) + halfpixel.X;
double cy = ((double)j / film.Height) + halfpixel.Y;
//jitter the centre
cx += ((samples[n].X * 2) - 1) * halfpixel.X
cy += ((samples[n].Y * 2) - 1) * halfpixel.Y
//sample the function
double f = func(cx, cy);
film.AddToPixel(i, j, new Vector(f));
}
}
//ImageFilm class
public void AddToPixel(int i, int j, Vector radiance)
{
//splat the value to neighbor pixels
//find neighbour pixels based on filter width
int x0 = (int)Math.Max(0, i - Filter.Width);
int x1 = (int)Math.Min(Width - 1, i + Filter.Width);
int y0 = (int)Math.Max(0, j - Filter.Width);
int y1 = (int)Math.Min(Height - 1, j + Filter.Width);
for (int x = x0; x <= x1; x++)
{
for (int y = y0; y <= y1; y++)
{
double weight = this.Filter.eval(x - i, y - j);
Pixel pxl = GetPixel(x, y);
pxl.radiance += radiance * weight;
pxl.filterWeightSum += weight;
SetPixel(x, y, pxl);
}
}
}
public class SincFilter
{
private double tau;
public SincFilter(double tau1, double width)
{
Width = width;
tau = tau1;
}
double Sinc(double x)
{
x = Math.Abs(x);
if (x < 1e-5) return 1;
return Math.Sin(Math.PI * x) / (Math.PI * x);
}
double WindowedSinc(double x, double radius)
{
x = Math.Abs(x);
if (x > radius) return 0;
return Sinc(x) * Sinc(x / tau);
}
public override double eval(double x, double y)
{
return WindowedSinc(x, Width) * WindowedSinc(y, Width);
}
}
double func(double x, double y)
{
double minPeriod = 2e-5;
double maxPeriod = 0.2;
double period = minPeriod + (maxPeriod - minPeriod) * (y * y);
double phase = x / period;
phase -= Math.Floor(phase);
return Math.Round(phase);
}
Here is Nathan image :
And here is mine for lanczos filter width 4, tau 4
My implementation is quite noisy for some reason. It doesn't look any better than box filter.