Sampling with mi_sample

mi_sample
    miBoolean mi_sample(
        double          *sample,
        int             *instance,
        miState         *state,
        const miUshort  dimension,
        const miUint    *n)

This is mental ray's primary generic sampling function. It can be used to sample arbitrary functions for evaluating integrals by deterministic sample points, which converge faster than their random counterparts and thus reduce rendering time. The mi_sample function is a high-level easy-to-use interface hiding all mathematical details of advanced quasi-Monte Carlo integration from the shader writer. sample is an array with dimension members that the new sample point will be stored into. Sample results are uniformly distributed in the range [0,1)dim. instance is the current sample number; it must point to an integer that is initialized to 0 before the first loop iteration. If there is no loop, a null pointer may be passed. state is the current shader state. dimension is the dimension of the sample, such as 3 for XYZ vectors. n must point to an integer that specifies the number of samples to take. If only a single sample is taken, n may be a null pointer. The body of the mi_sample loop may not write to *sample or *n or vary dimension between any subsequent calls in the same loop because mi_sample computes the next sample incrementally from the previous.

mi_sample has three main applications:

  1. generating single samples, which can be used in photon and other shaders for deciding on the kind of physical interaction like glossy, specular, or diffuse scattering, and then selecting a new quasi-random direction. This is the classical case of implementing random walk simulations with the von-Neumann-Ulam scheme.
  2. controlling an adaptive sampling loop, where the number of samples is not known in advance and the sampling loop will be terminated adaptively by the caller. This is the only mode where the caller may and must decide when to break from the loop.
  3. controlling a finite sampling loop. This is the classical case of quasi-Monte Carlo integration, where n samples are drawn and averaged. This can be used for sampling area light sources or depth of field shaders. The advantage of knowing the number of samples n in advance is an increased convergence speed.

Note that the returned sample point sample must be used as one point. This means that several one dimensional samples must not be assembled to yield a multidimensional sample, and that a multidimensional sample must not be used in order to simulate consecutive events. If, for example, 3D sampling vectors are computed with mi_sample, a single loop with dimension 3 must be used, rather than a higher dimension to obtain other sampling values at the same time, or fewer to compute the X, Y, and Z direction separately. Samples from mi_sample must be used consecutively. The sequence should not be terminated prematurely, nor should samples be dropped, since rejection sampling will result in biased approximations.

A call to mi_sample in single sample mode looks like:

    void a_single_sample(..., miState *state, ...)  
    {  
        double sample[2];  
             
        mi_sample(sample, 0, state, 2, 0);  
        /* use sample[0] and sample[1] */  
    }  

The sampling dimension in this example is 2. Since only a single sample is required, no sample counting is indicated by passing a null pointer as *instance counter and to the required number of samples n. After the call to mi_sample, the array x holds a two-dimensional sample point of [0,1)2. The function call always returns miFALSE in single sample mode.

In the adaptive sampling mode, the number of samples to be taken is not known in advance, and the code must decide when to break from the loop (which it may not in the other two modes, where mi_sample returns miFALSE). mi_sample is then used the following way:

    void adaptive_sampling(..., miState *state, ...)
    {
        double sample[5];
        int sample_number = 0;
             
        while (mi_sample(sample, &sample_number, state, 5, 0)) {
            /* use sample[0], ..., sample[4] */
            if (enough samples taken)
                break;
        }
    }

Adaptive sampling is indicated to mi_sample by passing a null pointer as the number of samples n. The while loop is then controlled by the mi_sample call, which always returns miTRUE in adaptive sampling mode. The current sample number starting with 1 for the first sample is kept in sample_number. This variable must be initialized to zero in order to force initialization in mi_sample. Note that in deterministic sampling, adaptive sampling (here, the break condition) cannot be controlled by the calculation of variance as in Monte Carlo context. This will result in biased approximations. Adaptive sampling here could be based on contrast calculations, for example. sample holds a sample point in the five-dimensional unit cube [0,1)5.

In the classical case, where the number of samples is known in advance, the deterministic sampling controlled by mi_sample very much resembles a Monte Carlo quadrature:

    void finite_sampling(..., miState *state, ...)
    {
        const miUint samples = 16;
        double sample[2];
        int sample_number = 0;

        while (mi_sample(sample, &sample_number, state, 2, &samples)) {
            /* use sample[0], sample[1] */
        }
    }

Here, all arguments of mi_sample are used. Unlike the previous adaptive sampling case, the number of samples is passed explicitly to mi_sample. Again the integer variable sample_number is initialized to zero and passed by reference to instance. *state is the current state of the shader. mi_sample returns miTRUE for samples iterations of the loop, until finally miFALSE is returned to terminate the loop. The loop should not be terminated with a break or return statement, because then the internal state of state does not guarantee convergence of rendering any longer (that is, state→qmc_instance and state→qmc_component are wrong until the shader terminates). In the loop, *instance provides the current sample number, starting at 1, until samples is reached. The loop controls a two-dimensional integration, where sample is an array of doubles with two elements. The vector sample is the sample point in the two-dimensional unit cube [0,1)2. The finite sampling mode exposes a faster convergence than the adaptive sampling mode.

Exceptional convergence can be achieved if the number of samples is

dimension−1
samples = pi ki
i = 1

where pi are the prime numbers, i.e., p1 = 2, p2 = 3, etc., and ki is a (positive) natural number. In two dimensions, i.e., dimension = 2, samples should be a power of 2. In the special case of dimension = 1, any number of samples can be used in order to achieve optimal convergence.

Note that if mi_sample controls the loop, it may take fewer samples than expected. For example, consider a sun, modeled as a disk area light source, with sampling set to request 8 samples. Assuming the sun is setting and only half of it is visible over the horizon, sampling the lower half cannot contribute any light. In this case, a light sampling function such as mi_sample_light (which is a specialization of mi_sample) may return miTRUE four times, and then abort the loop by returning miFALSE for the fifth call. The shader should work for such early aborts, even if the very first call returns miFALSE. The shader should also not assume any valid result parameters, such as dot_nl, for a call to mi_sample that has returned miFALSE.

Copyright © 1986-2009 by mental images GmbH