In ray tracing / path tracing, one of the simplest way to anti-alias the image is to supersample the pixel values and average the results. IE. instead of shooting every sample through the center of the pixel, you offset the samples by some amount.
In searching around the internet, I've found two somewhat different methods to do this:
- Generate samples however you want and weigh the result with a filter
- One example is PBRT
- Generate the samples with a distribution equal to the shape of a filter
- Two examples are smallpt and Benedikt Bitterli's Tungsten Renderer
Generate and Weigh
The basic process is:
- Create samples however you want (randomly, stratified, low-discrepancy sequences, etc.)
- Offset the camera ray using two samples (x and y)
- Render the scene with the ray
- Calculate a weight using a filter function and the distance of the sample in reference to the pixel center. For example, Box Filter, Tent Filter, Gaussian Filter, etc.)
- Apply the weight to the color from the render
Generate in the shape of a filter
The basic premise is to use Inverse Transform Sampling to create samples that are distributed according to the shape of a filter. For example a histogram of a samples distributed in the shape of a Gaussian would be:
This can either be done exactly, or by binning the function into a discrete pdf/cdf. smallpt uses the exact inverse cdf of a tent filter. Examples of binning method can be found here
Questions
What are the pros and cons of each method? And why would you use one over the other? I can think of a few things:
Generate and Weigh seems to be the most robust, allowing any combination of any sampling method with any filter. However, it requires you to track the weights in the ImageBuffer and then do a final resolve.
Generate in the Shape of a Filter can only support positive filter shapes (ie. no Mitchell, Catmull Rom, or Lanczos), since you can not have a negative pdf. But, as mentioned above, it's easier to implement, since you don't need to track any weights.
Though, in the end, I guess you can think of method 2 as a simplification of method 1, since it's essentially using an implicit Box Filter weight.