StratifiedSampler 类
// StratifiedSampler Declarations
class StratifiedSampler : public Sampler {
public:
// StratifiedSampler Public Methods
StratifiedSampler(int xstart, int xend, int ystart, int yend,
int xs, int ys, bool jitter, float sopen, float sclose);
~StratifiedSampler();
int RoundSize(int size) const { return size; }
Sampler *GetSubSampler(int num, int count);
int GetMoreSamples(Sample *sample, RNG &rng);
int MaximumSampleCount() { return xPixelSamples * yPixelSamples; }
private:
// StratifiedSampler Private Data
int xPixelSamples, yPixelSamples;
bool jitterSamples;
int xPos, yPos;
float *sampleBuf;
};
StratifiedSampler *CreateStratifiedSampler(const ParamSet ¶ms, const Film *film,
const Camera *camera);
类的作用:
(这个分层采样的主要思路就是,分层,把采样域细分成不重叠的rectangular,其实个人理解就是,1个Pixel可以分成很多个不重叠的rectangular, 每一个rectanglar里面有一个采样点)
The first sample generator that we will introduce divides the image plane into rectangular
regions and generates a single sample inside each region. These regions are commonly
called strata, and this sampler is called the StratifiedSampler. The key idea behind
stratification is that by subdividing the sampling domain into nonoverlapping regions
and taking a single sample from each one, we are less likely to miss important features
of the image entirely, since the samples are guaranteed not to all be close together. Put
another way, it does us no good if many samples are taken from nearby points in the
sample space, since each new sample doesn’t add much new information about the
behavior of the image function. From a signal processing viewpoint, we are implicitly
defining an overall sampling rate such that the smaller the strata are, the more of them
we have, and thus the higher the sampling rate.
The stratified sampler places each sample at a random point inside each stratum by
jittering the center point of the stratum by a random amount up to half the stratum’s
width and height. The nonuniformity that results from this jittering helps turn aliasing
into noise, as discussed in Section 7.1. The sampler also offers a nonjittered mode, which
gives uniformsampling in the strata; this mode is mostly useful for comparisons between
different sampling techniques rather than for rendering final images.
1. 构造函数
// StratifiedSampler Method Definitions
StratifiedSampler::StratifiedSampler(int xstart, int xend,
int ystart, int yend, int xs, int ys, bool jitter,
float sopen, float sclose)
: Sampler(xstart, xend, ystart, yend, xs * ys, sopen, sclose) {
jitterSamples = jitter;
xPos = xPixelStart;
yPos = yPixelStart;
xPixelSamples = xs;
yPixelSamples = ys;
sampleBuf = new float[5 * xPixelSamples * yPixelSamples];
}
作用:
(Sampler 对于图片的 [xstart,ystart] to [xend-1,yend-1] 的区域进行采样,每一个Pixel 会有 xs,ys 个 strate,每一个strate 代表一个采样点,jitter表示是否在strate 中心进行随机偏移 )
The StratifiedSampler generates samples by looping over the pixels from left to right
and top to bottom, generating all of the samples for the strata in each pixel before
advancing to the next pixel. The constructor takes the range of pixels to generate samples
for—[xstart,ystart] to [xend-1,yend-1], inclusive—the number of strata in x and y
(xs and ys), a Boolean (jitter) that indicates whether the samples should be jittered,
and the range of time values where the camera shutter is open, [sopen, sclose].
The implementation of the GetMoreSamples() method below generates all xs * ys samples
for a pixel at once; scratchpad memory that it uses in this process is allocated in the
constructor and stored in sampleBuf.
2. int RoundSize(int size) const { return size; }
作用:
The StratifiedSampler has no preferred sizes for the number of additional samples
generated for the Integrators.
3. Sampler *GetSubSampler(int num, int count);
Sampler *StratifiedSampler::GetSubSampler(int num, int count) {
int x0, x1, y0, y1;
ComputeSubWindow(num, count, &x0, &x1, &y0, &y1);
if (x0 == x1 || y0 == y1) return NULL;
return new StratifiedSampler(x0, x1, y0, y1, xPixelSamples,
yPixelSamples, jitterSamples, shutterOpen, shutterClose);
}
作用:
(计算 subSampler,可能会返回Null,主要由于Image不能细分的情况)
The GetSubSampler() method uses the ComputeSubwindow() utility routine to compute an
image tile extent for the given subsampler number and then returns a new instance of a
StratifiedSampler that generates samples in just that region. ComputeSubWindow() may
return an empty pixel extent, where one or both of the x and y ranges are degenerate(退化).
(Consider, for example, a 2× 2 image; it can’t be decomposed(分解) into more than four image
tiles if the tiles are no smaller than a pixel’s extent.) In this case, NULL is returned. Calling
code must handle this case where a finer decomposition of the image is requested than
the Sampler is able to support by ignoring any returned NULL pointers.
4. int GetMoreSamples(Sample *sample, RNG &rng);
作用:
和前面的Sampler类一致的,这个方法主要是
遍历图片的 【xPixelStart ,yPixelStart 】->【xPixelEnd,yPixelEnd】区域中的所有的Pixel,每一个Pixel就生成一个或者多个采样点,如果这个区域所有的Pixel 都生成完采样点的话,就直接返回0,如果还没有,就直接返回当前这个Pixel的采样点数量
思路:
(假如1个Pixel 有4个 strate(rect),每一个sample有五维数据(x,y t,u,v),那么,就会对这五维数据进行分层,分成 4个(x,y), 4个 t,4个(u,v),然后随机组合这些数据,组合出来的结果就是Sample的数据)
Figure 7.16 shows the basic idea: we might
want to take just four samples per pixel, but still have the samples be stratified over all
dimensions.We independently generate four 2D stratified image samples, four 1D stratified
time samples, and four 2D stratified lens samples. Then, we randomly associate a
time and lens sample value with each image sample. The result is that each pixel has
samples that together have good coverage of the sample space.
Figure 7.16:We can generate a good sample pattern that reaps(获得) the benefits of stratification without
requiring that all of the sampling dimensions be stratified simultaneously. Here, we have split (x, y)
image position, time t , and (u, v) lens position into independent strata with four regions each. Each
is sampled independently, then a time sample and a lens sample are randomly associated with each
image sample. We retain the benefits of stratification in each of the individual dimensions without
having to exponentially increase the total number of samples.
细节:
int StratifiedSampler::GetMoreSamples(Sample *samples, RNG &rng) {
if (yPos == yPixelEnd) return 0;
int nSamples = xPixelSamples * yPixelSamples;
// Generate stratified camera samples for _(xPos, yPos)_
// Generate initial stratified samples into _sampleBuf_ memory
float *bufp = sampleBuf;
float *imageSamples = bufp; bufp += 2 * nSamples;
float *lensSamples = bufp; bufp += 2 * nSamples;
float *timeSamples = bufp;
StratifiedSample2D(imageSamples, xPixelSamples, yPixelSamples, rng,
jitterSamples);
StratifiedSample2D(lensSamples, xPixelSamples, yPixelSamples, rng,
jitterSamples);
StratifiedSample1D(timeSamples, xPixelSamples * yPixelSamples, rng,
jitterSamples);
// Shift stratified image samples to pixel coordinates
for (int o = 0; o < 2 * xPixelSamples * yPixelSamples; o += 2) {
imageSamples[o] += xPos;
imageSamples[o+1] += yPos;
}
// Decorrelate sample dimensions
Shuffle(lensSamples, xPixelSamples*yPixelSamples, 2, rng);
Shuffle(timeSamples, xPixelSamples*yPixelSamples, 1, rng);
// Initialize stratified _samples_ with sample values
for (int i = 0; i < nSamples; ++i) {
samples[i].imageX = imageSamples[2*i];
samples[i].imageY = imageSamples[2*i+1];
samples[i].lensU = lensSamples[2*i];
samples[i].lensV = lensSamples[2*i+1];
samples[i].time = Lerp(timeSamples[i], shutterOpen, shutterClose);
// Generate stratified samples for integrators
for (uint32_t j = 0; j < samples[i].n1D.size(); ++j)
LatinHypercube(samples[i].oneD[j], samples[i].n1D[j], 1, rng);
for (uint32_t j = 0; j < samples[i].n2D.size(); ++j)
LatinHypercube(samples[i].twoD[j], samples[i].n2D[j], 2, rng);
}
// Advance to next pixel for stratified sampling
if (++xPos == xPixelEnd) {
xPos = xPixelStart;
++yPos;
}
return nSamples;
}
a.
(先检查是否完成遍历所有的Pixel,如果已经完成了,就直接返回0,否则的话,就会生成当前Pixel的Samples,并直接返回给 samples 参数中)
It starts by checking whether it is done generating samples for
its region—because it generates samples starting from (xPixelStart, yPixelStart) and
then first scans across x and then down y, it is done when yPos equals yPixelEnd. If it
is done, a return value of zero indicates to the caller that the sampler has completed its
work. Otherwise, the method generates a pixel’s worth of samples, returning them in the
samples array.
b.
int nSamples = xPixelSamples * yPixelSamples;
// Generate initial stratified samples into _sampleBuf_ memory
float *bufp = sampleBuf;
float *imageSamples = bufp; bufp += 2 * nSamples;
float *lensSamples = bufp; bufp += 2 * nSamples;
float *timeSamples = bufp;
StratifiedSample2D(imageSamples, xPixelSamples, yPixelSamples, rng,
jitterSamples);
StratifiedSample2D(lensSamples, xPixelSamples, yPixelSamples, rng,
jitterSamples);
StratifiedSample1D(timeSamples, xPixelSamples * yPixelSamples, rng,
jitterSamples);
作用:
(生成 CameraSample 采样点,sampleBuf 在构造函数的时候就已经申请了内存,在这里就明白了为什么会有 5, 主要是因为 有(x,y,t,u,v)
1个Pixel有 xPixelSamples * yPixelSamples 个采样点,imageSamples,lensSamples,timeSamples指向各自的内存,
)
First, stratified sampling utility routines are used to generate two 2D and one 1D sampling
patterns for the camera samples. These sample values are stored in the sampleBuf
memory for now.
c.
void StratifiedSample1D(float *samp, int nSamples, RNG &rng,
bool jitter) {
float invTot = 1.f / nSamples;
for (int i = 0; i < nSamples; ++i) {
float delta = jitter ? rng.RandomFloat() : 0.5f;
*samp++ = min((i + delta) * invTot, OneMinusEpsilon);
}
}
void StratifiedSample2D(float *samp, int nx, int ny, RNG &rng,
bool jitter) {
float dx = 1.f / nx, dy = 1.f / ny;
for (int y = 0; y < ny; ++y)
for (int x = 0; x < nx; ++x) {
float jx = jitter ? rng.RandomFloat() : 0.5f;
float jy = jitter ? rng.RandomFloat() : 0.5f;
*samp++ = min((x + jx) * dx, OneMinusEpsilon);
*samp++ = min((y + jy) * dy, OneMinusEpsilon);
}
}
作用:
( 用 StratifiedSample2D 生成的采样点,都是在【0,1】之间,但是不同的采样点坐标类似:【0.1, 0.1】,【0.2, 0.2】.....[1,1], 也就是把一个Pixel的 所有的 rect 的采样值 都限定在 【0, 1】之间)
The 1D and 2D stratified sampling routines are implemented as utility functions, since
they will be useful elsewhere in pbrt. Both of them loop over the given number of strata
over the [0, 1]domain and place a sample value in each one.
d.
// Shift stratified image samples to pixel coordinates
for (int o = 0; o < 2 * xPixelSamples * yPixelSamples; o += 2) {
imageSamples[o] += xPos;
imageSamples[o+1] += yPos;
}
作用:
(CameraSample 采样点的(imageX, imageY)需要表示为 与当前的 pixel 坐标相关,所以,需要 相加 当前像素坐标位置)
The StratifiedSample2D() utility function generates samples in the range [0, 1]2, but
image samples need to be expressed in terms of continuous pixel coordinates. Therefore,
GetMoreSamples() next loops over all of the new stratified samples and adds the current
(x, y) pixel number, so that the samples for the discrete pixel (x, y) range over the
continuous coordinates [x, x + 1) × [y, y + 1), following the convention for continuous
pixel coordinates described in Section 7.1.7. Figure 7.18 reviews the relationship between
discrete and continuous coordinates as it relates to samples for a pixel.
Figure 7.18: To generate stratified samples for a discrete pixel (1, 2), all that needs to be done is to
add (1, 2) to the elements of the samples generated over [0, 1]2. This gives samples with continuous
pixel coordinates from (1, 2) to (2, 3), which end up surrounding the discrete pixel (1, 2).
e.
Shuffle(lensSamples, xPixelSamples*yPixelSamples, 2, rng);
Shuffle(timeSamples, xPixelSamples*yPixelSamples, 1, rng);
template <typename T>
void Shuffle(T *samp, uint32_t count, uint32_t dims, RNG &rng) {
for (uint32_t i = 0; i < count; ++i) {
uint32_t other = i + (rng.RandomUInt() % (count - i));
for (uint32_t j = 0; j < dims; ++j)
swap(samp[dims*i + j], samp[dims*other + j]);
}
}
作用:
(扰乱 lensSamples 和 timeSamples 数组)
In order to randomly associate a time and lens sample with each image sample, this
method next shuffles(混乱) the order of the time and lens sample arrays. Thus, when it later
initializes a Sample with the ith precomputed sample value for this pixel, it can just return
the ith time and lens sample.
The Shuffle() utility function randomly permutes(交换) a sample pattern of count samples in
dims dimensions.
f.
// Initialize stratified _samples_ with sample values
for (int i = 0; i < nSamples; ++i) {
samples[i].imageX = imageSamples[2*i];
samples[i].imageY = imageSamples[2*i+1];
samples[i].lensU = lensSamples[2*i];
samples[i].lensV = lensSamples[2*i+1];
samples[i].time = Lerp(timeSamples[i], shutterOpen, shutterClose);
// Generate stratified samples for integrators
for (uint32_t j = 0; j < samples[i].n1D.size(); ++j)
LatinHypercube(samples[i].oneD[j], samples[i].n1D[j], 1, rng);
for (uint32_t j = 0; j < samples[i].n2D.size(); ++j)
LatinHypercube(samples[i].twoD[j], samples[i].n2D[j], 2, rng);
}
作用:
(这里是直接 初始化 采样点的(x,y,t,lenU,lenV) ,接下来就是采样点的 oneD,twoD)
Now that the camera samples have been offset and the lens and time samples have been
shuffled, the sample values can be copied out of the temporary buffer and stored in the
array of samples passed to GetMoreSamples(). As this step is performed, sample values for
the samples requested by the integrators are generated for each of the returned samples
as well.
g.
LatinHypercube(samples[i].oneD[j], samples[i].n1D[j], 1, rng);
LatinHypercube(samples[i].twoD[j], samples[i].n2D[j], 2, rng);
作用:
if an integrator asks for a set of 64 two-dimensional sample values
for each image sample, the sampler has two different goals to try to fulfill:
We would like each image sample’s 64 integrator samples to themselves be well distributed
in 2D (i.e., with an 8 × 8 stratified grid). Stratification here will improve
the quality of the integrator’s results for each individual sample.
(如果一个 Integrator 为每一个Sample 额外请求 64 个 2D 的积分器需要采样值(integrator samples),在分层采样器中,只是确保这64个integrator samples 有很好的分布,那每一个Sample 的 integrator samples 各自well distributed)
A second integrator-related complication comes from the fact that they may ask for
an arbitrary number of samples per image sample, so stratification may not be easily
applied. (For example, how do we generate a stratified 2D pattern of seven samples?)
We could just generate an n × 1 or 1× n stratified pattern, but this only gives us the benefit of stratification in one dimension, and no guarantee of a good pattern in the
other dimension. The StratifiedSampler::RoundSize() method could round requests
up to the next number that’s the square of integers, but instead we will use an approach
called Latin hypercube sampling (LHS), which can generate any number of samples in
any number of dimensions with reasonably good distribution.
(因为 Integrator 可能会为每一个Sample请求额外的 任意数量的 Integrator Sample,但是 分层采样器 对于 任意数目的采样点不好处理得不够好,所以就引入 Latin hypercube sampling (LHS),这个LHS 可以生成 分布良好 的任意数目,任意维数采样点)
Latin hypercube sampling (sometimes called n-rooks sampling) chooses samples such
that only a single sample is present in each row and each column of a grid. This can be done
by generating random samples in the cells along the diagonal and then randomly permuting their
coordinates. One advantage of LHS is that it can generate any number of samples with a good
distribution, not just m × n samples, as with stratified patterns.
(LHS思路:采样点只会出现在对角线,并且会随机交换它们的坐标)
In spite of(尽管) addressing(解决) the clumping(聚丛) problem, LHS isn’t necessarily(不是必要的) an improvement to
stratified sampling; it’s easy to construct cases where the sample positions are essentially
colinear(共线) and large areas of [0, 1] have no samples near them (e.g., when the permutation(交换)
of the original samples is the identity, leaving them all where they started). In particular,
as n increases, Latin hypercube patterns are less and less effective compared to stratified
patterns.
(分层采样有一个最坏的情况就是会在一条线上聚集采样点,LHS虽然解决了一个聚集采样点的问题,但是也容易造成 很大的区域没有采样点)
void LatinHypercube(float *samples, uint32_t nSamples, uint32_t nDim,
RNG &rng) {
// Generate LHS samples along diagonal
float delta = 1.f / nSamples;
for (uint32_t i = 0; i < nSamples; ++i)
for (uint32_t j = 0; j < nDim; ++j)
samples[nDim * i + j] = min((i + (rng.RandomFloat())) * delta,
OneMinusEpsilon);
// Permute LHS samples in each dimension
for (uint32_t i = 0; i < nDim; ++i) {
for (uint32_t j = 0; j < nSamples; ++j) {
uint32_t other = j + (rng.RandomUInt() % (nSamples - j));
swap(samples[nDim * j + i], samples[nDim * other + i]);
}
}
}
代码细节:
The general-purpose LatinHypercube() function generates an arbitrary number of LHS
samples in an arbitrary dimension. The number of elements in the samples array should
thus be nSamples*nDim.
float delta = 1.f / nSamples;
for (uint32_t i = 0; i < nSamples; ++i)
for (uint32_t j = 0; j < nDim; ++j)
samples[nDim * i + j] = min((i + (rng.RandomFloat())) * delta,
OneMinusEpsilon);
这里是生成对角线的 采样点 数据,假如 samples 有 10 个2D的数据,那么 其实就是 有 10 * 10 个2D的rect,但是只会取 对角线出现 采样点。
for (uint32_t i = 0; i < nDim; ++i) {
for (uint32_t j = 0; j < nSamples; ++j) {
uint32_t other = j + (rng.RandomUInt() % (nSamples - j));
swap(samples[nDim * j + i], samples[nDim * other + i]);
}
}
作用:
(每次只交换 1个维度的数据,并不是整体交换,例如,有一个3D的数据,那么就会交换这个3D数据的x,或者y,或者z)
To do the permutation(交换), this function loops over the samples, randomly permuting the
sample points in one dimension at a time. Note that this is a different permutation
than the earlier Shuffle() routine: that routine does one permutation, keeping all nDim
sample points in each sample together, while here nDim separate permutations of a single
dimension at a time are done (Figure 7.21).
Figure 7.21: (a) The permutation done by the Shuffle() routine moves entire blocks of nDims
elements around. (b) The permutation for Latin hypercube sampling permutes each dimension’s
samples independently. Here, the shuffling of the second dimension’s samples from a four-element
pattern of three dimensions is shown.
h.
if (++xPos == xPixelEnd) {
xPos = xPixelStart;
++yPos;
}
作用:
After a pixel’s worth of samples have been generated, the StratifiedSampler advances
the pixel location to the next pixel. It first tries to move one pixel over in the x direction.
If doing so takes it beyond the end of the sampling range, it resets the x position to
the first pixel in the next x row of pixels and advances the y position. Because the y
pixel location yPos is advanced only when the end of a row of pixels in the x direction
is reached, once the y position counter has advanced past the bottom of the sampling
range, sample generation is complete.
5. int MaximumSampleCount() { return xPixelSamples * yPixelSamples; }
作用:
the MaximumSampleCount() method can be implemented; recall that each call to
GetMoreSamples() returns xPixelSamples * yPixelSamples until sampling is done.