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 header file).  The purpose of the abstraction is that authors of Bxdfs and Integrators do not have to worry about explicit indexing, sample counting, table lookups, etc.

The SampleCtx struct

A sample context (SampleCtx) consists of two unsigned integers: patternid and sampleid.  Patternid is a 32-bit pattern that gets mapped to one of the built-in PMJ sample tables, and sampleid determines which of the samples in the table to use.  Camera rays are initialized such that samples in a given pixel will have the same patternid; typically, the first sample in each pixel will have sampleid 0, the next sample in the pixel will have sampleid 1, etc.

The RixRNG class

The "RNG" part of RixRNG stands for "Random Number Generator" even though the samples are not random at all.  The RixRNG class is basically just a wrapper around an array of per-shading-point sample contexts (SampleCtxArray) and an integer (numPts) specifying how many sample contexts there are in the array.  There is typically one SampleCtx for each point in a ray shading batch.  The RixRNG wrapper class makes it convenient to generate sample points (or new sample domains) for an entire ray shading batch with just a single function call.

Generating samples

There are six different functions to generate samples: 

...

Code Block
for (int i = 0; i < numPts; i++)
    sampleCtxArray[i].sampleid++;


Generating new sample domains (new patternids)

We need different sample sequences for each combination of pixel, ray depth, and domain (bxdf lobes, area lights, indirect illumination, etc.).  (If the same sample sequence was used for everything, there would be correlation between samples and the image would not converge.)  RenderMan internally selects sample sequences for pixel position (for anti-aliasing), lens position (for depth-of-field), and time (for motion blur), and sets up an initial patternid (bit pattern) and sampleid for camera ray hit points.  All sample sequences used by Bxdfs and light source sampling need to be set up in the Integrators calling them: separate domains for bxdf, light sources, and indirect illumination derived from the parent domains – and then new domains again at the next bounce (derived from those at the first bounce), etc.  This is accomplished by calling one of the NewDomain*() functions.  The NewDomain*() functions create a new patternid based on a hash of the parent RixRNG's patternid and a scramble bit-pattern.

...

Here the scramble bit-pattern is 0x8732f9a1 and the splitting factor is 4.

Tips for using samples in practice

This section contains a few practical tips for how to use the samples returned by the RixRNG DrawSample* and GenerateSample* API functions.

Mapping samples

The samples returned by the DrawSample2D() and GenerateSamples2D() functions are in the unit square.  It is often necessary to map from the unit square to other domains.  Example: uniform sampling a disk with a given radius can be done by picking a radius proportional to the square root of a sample between 0 and 1, and an angle between 0 and 2pi, and then computing the xy position corresponding to that radius and angle:

...

Other common mappings map samples to the surface of a sphere, hemisphere, or cylinder, map samples to directions proportional to a glossy bxdf lobe, and so on.

Shirley remapping

We often need to combine a selection between sub-domains and generation of positions (or directions) on one of those sub-domains.  For example: select a light source and generate a sample position on that light source, or select a bxdf lobe and generate a sample direction proportional to that lobe.  

...

This method of using 2D samples to both choose between sample sub-domains and at the same time stretching the samples from the chosen sub-domain to provide stratified 2D sample points is implemented in the RixChooseAndRemap() function in the RixShadingUtils.h include file.

Path splitting vs distribution ray tracing

Path splitting and distribution ray tracing were mentioned above (in the description of the various NewDomain*() functions).  But when is it appropriate to use which?  A couple examples should provide some guidance:

...

Resort to distribution ray tracing if there is no fixed branching factor.  For example, if some specular ray hit points spawn 16 new sample directions but other specular ray hit points (perhaps with a lower throughput) only spawn 4 or even 1 new sample directions.  In this case there is no simple way to compute the offset into a combined sample sequence.  Instead the 16 (or 4) sample directions are stratified with respect to each other, but not with respect to any other samples for that same pixel.  (If only 1 new sample direction is spawned then there is no stratification at all.)

Uniform random samples

Non-stratified samples similar to e.g. drand48() can be obtained by calling the HashToRandom() function.  It computes a repeatable random float between 0 and 1 given two unsigned int inputs, for example patternid and sampleid.  Using the same patternid and continuously incrementing sampleid gives a sequence of samples with good statistical variation – similar to drand48().  The HashToRandom() function is repeatable (i.e. the same two inputs always give the same output), and has no multi-threaded contention (whereas drand48() has notoriously bad cache line contention, hampering multi-threaded performance).  HashToRandom() is located in the RixRNGInline.h include file.

Advanced topic: Details of PMJ table lookup implementation

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 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.  First the patternid is mapped to one of the 384 PMJ tables with a repeatable hash function.  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 mantissa bits of the floating-point sample values are scrambled using the patternid – this scrambling changes the sample values while preserving their stratification.

...

Code Block
class RandomSampler : public RixRNG::Generator
{
    ...


    // Generate a uniformly distributed pseudorandom sample in [0,1)
    virtual float Sample1D(const RixRNG::SampleCtx &sc, unsigned i) const   // both params are ignored
    {
        float x = drand48();
        if (x >= 1.0f) x -= 1.0f;   // this can happen due to rounding double to float
        return x;
    }


    ... similar Sample2D() and Sample3D() functions ...
    
    // Fill in float array 'xis' with pseudorandom samples in [0,1)
    virtual void MultiSample1D(unsigned n, const RixRNG::SampleCtx &sc, float *xis) const
    {
        for (int i = 0; i < n; i++)
        {
            float x = drand48();
            if (x >= 1.0f) x -= 1.0f;   // this can happen due to rounding double to float
            xis[i] = x;
        }
    }


    ... similar MultiSample2D() and MultiSample3D() functions ...
    
}

More information

For more (very nerdy!) details about the properties of PMJ samples please refer to the following paper:

...