7.7 Sobol’ Sampler
The last Sampler in this chapter is based on a series of generator matrices due to Sobol. The samples from the sequence that these matrices generate are distinguished by both being very efficient to implement—thanks to being entirely based on base-2 computations—while also being extremely well distributed over all dimensions of the sample vector. Figure 7.34 shows the first few Sobol generator matrices.
Figure 7.35 compares Sobol samples to stratified and Halton points with the depth of field test scene.
The weakness of the Sobol points is that they are prone to structural grid artifacts before convergence; a sense of this issue can be seen in the image sample points shown in Figure 7.36.
This structure is visible in the images in Figure 7.37. In exchange for this weakness, Sobol sequences are extremely well distributed over all dimensions of the sample vector.
The SobolSampler uniformly scales the first two dimensions by the smallest power of 2 that causes the sample domain to cover the image area to be sampled. As with the HaltonSampler, this specific scaling scheme is chosen in order to make it easier to compute the reverse mapping from pixel coordinates to the sample indices that land in each pixel.
The SobolIntervalToIndex() function returns the index of the sampleNumth sample in the pixel p, if the sampling domain has been scaled by to cover the pixel sampling area.
The general approach used to derive the algorithm it implements is similar to that used by the Halton sampler in its GetIndexForSample() method. Here, scaling by a power of two means that the base-2 logarithm of the scale gives the number of digits of the product that form the scaled sample’s integer component. To find the values of that give a particular integer value after scaling, we can compute the inverse of : given
We won’t include the implementation of this method here.
Computing the sample value for a given sample index and dimension is straightforward given the SobolSample() function.
The code for computing Sobol sample values takes different paths for 32- and 64-bit floating-point values. Different generator matrices are used for these two cases, giving more bits of precision for 64-bit doubles.
The implementation of the SobolSampleFloat() function is quite similar to that of MultiplyGenerator(), with the differences that it takes a 64-bit index and that the matrices it uses have size . These larger matrices allow it to generate distinct sample values up to , rather than , as with the matrices used previously.
The SobolSampleDouble() function is similar, except that it uses 64-bit Sobol matrices. It is not included in the text here.
Because the SobolSampler is a GlobalSampler, the values returned for the first two dimensions need to be adjusted so that they are offsets from the current pixel. Here, the sample value is scaled up by the power-of-two scale computed in the constructor and then offset by the lower corner of the sample bounds to find the corresponding raster sample location. The current integer pixel coordinate is subtracted to get a result in .