Page tree

Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

In order to look up in one of the pre-generated sample tables one needs to specify which table to use and which sample number is requested.  RenderMan provides a convenient abstraction class and API for this, called RixRNG.

The API is defined in the include/RixRNG.h header file (and the implementation of PMJ table lookups are in the include/RixRNGProgressive.h file).

The SampleCtx struct

A sample context (SampleCtx) consists of two unsigned integers: patternid and sampleid.  Patternid is a 32-bit pattern that gets hashed mapped to one of the built-in PMJ  tables, and sampleid determines which of the samples in the table to use.

...

There are six different functions to generate samples: GenerateSample1D(), GenerateSample2D(), GenerateSample3D(), DrawSample1D(), DrawSample2D(),  and DrawSample3D().  The Generate functions increment the sample context sampleid; Draw functions do not.

Multipoint functions: There are also six multipoint functions that generate samples for all points in a RixRNG: GenerateSamples1D(), GenerateSamples2D(), GenerateSamples3D(), DrawSamples1D(), DrawSamples2D(), and DrawSamples3D().

...

The 'newdomain' patterns can be any bit pattern, but it is important that the bit pattern to generate different new domains are different.  For example, if an Integrator is generating new domains for bxdf and light source sampling, those should use different 'newdomain' bit patterns.  Otherwise there will be visible correlation between bxdf and light source sampling, leading to visible artifacts and poor convergence – or even no convergence at all!

Tips for using samples in practice

Mapping from unit square to other domains ...  Example: uniform sampling a disk.  

RtPoint2  RtPoint2 sample = DrawSample2D(...);  radius  float radius = sqrt(sample.x);  float phi = 2 * M_PI * sample.y; point.x = radius * cos(phi); point.y = radius * sin(sample.x);

...

Non-stratified samples similar to e.g. drand48(): ... RepeatableHashToRandom() computes a repeatable random float between 0 and 1 given two unsigned inputs.  Repeatable, and no multi-threaded contention (drand48() has notoriously bad locking, hampering multi-treaded performance).

Advanced topic: PMJ table lookup implementation

Above In the introduction above we wrote that patternid is used to choose the PMJ table, and sampleid is used to choose the sample in that table.  If we had 2^32 pre-generated PMJ tables it really would be as simple as that.  However, in practice we have only a few hundred 384 different PMJ tables, so we have to be a bit inventive to make it look like we have many, many different tables even though we don't.  The trick – implemented in include/RixRNGProgressive.h – is to further "randomize" the samples in the tables.  This randomization is done with scrambling of the sample values and shuffling of the sample order. . scrambling and shuffling First the patternid is mapped to one of the 384 PMJ tables with a repeatable hash.  Then the sampleid is shuffled a bit by carefully swapping neighbor samples and groups of four samples.  (Such local shuffling does not affect the convergence properties of each sample sequence, but decorrelates sample sequences such that they can be safely combined with each other.)  Finally the floating-point bits of the sample values are scrambled using the patternid.

Another practical detail is that even though PMJ tables theoretically could be generated with infinitely many samples, in practice we limit each table to 4096 samples.  This kicks in when more than 4096 samples per pixel are required.  When a sampleid higher than 4096 is requested, we look up from the beginning of another table.  So the samples beyond 4096 are still stratified, just not quite as well stratified with respect to the first 4096 samples as if we had had larger tables.  If more than 196608 samples per pixel are used, ie. we get to sampleids higher than 196608, then the samples revert to uniform random samples generated with the HashToRandom() function described below.

Advanced topic: Overriding the RixRNG implementation

It is possible to override the default PMJ samples if other sample sequences are desired.  This is scary stuff, but can be useful for experimenting with e.g. primary-space Metropolis rendering algorithms.  The Generator class ... When a custom Generator has been specified, it automatically gets called when e.g. a Bxdf of Integrator calls one of the GenerateSample or DrawSample functions.

...