OpenShadingLanguage icon indicating copy to clipboard operation
OpenShadingLanguage copied to clipboard

Defining a canonical interface to random numbers

Open sfriedmapixar opened this issue 1 year ago • 13 comments

Problem

We currently don't have a standard way for shaders to access random-number-generation facilities build into a renderer. The closest we have is noise functions, but in sample-based numerical integration like path-tracers, well-stratified numbers can make a significant improvement in the visual quality of low-sample-count integrations.

In particular, we'd like to expose

  1. uniformly-distributed random samples
  2. "well-stratified" random samples across different invocations of the shader, for whatever "well-stratified" happens to mean to a particular renderer that is in-charge of the generating the integration samples.
  3. a way of getting multiple samples for a single shader-group evaluation that are "well-stratified" relative to one-another
  4. a way of getting multiple samples for a single shader-group evaluation that are distributed independent of one another within the shader group execution, but relatively well stratified with respect to other invocations of the shader group.

Even though the implementation of this is renderer specific, it would be nice to expose the functionality in a canonical way across renderers to reduce the challenge of writing OSL shaders that take advantage of these facilities.

Some Possible Implementations

A discussion on the ASWF #openshadinglanguage slack channel did some initial exploration of these possibilities:

  1. A new shadeop() with full jit-backend support that has the entire implementation embedded in OSL source.
  2. An agreed upon set of getattribute() queries that renderers are expected to respond to.
  3. A stdosl.h implementation that provides a common set of interface functions, but can be implemented via an agreed upon set of getattribute() queries to be customized by renderers that can fall back to noise() based OSL intrinsic functionality.

The preferred solution seems to be something along the lines of 3, but bringing it here for further discussion/suggestion.

Initial Proposal

The base unit of functionality that a renderer could use to override would be a getattribute call using the "RNG" namespace, for Random-Number-Generator. This proposal attempts to fit what we need within current getattribute API limitations. Within that namespace, there would be the following attributes that could be queried:

  • "uniform" - A uniformly distributed random number.
    • When combined with the "array index" form of getattribute, the array-index becomes a "seed".
  • "stratified" - A random number well-stratified according to the integration technique of the renderer.
    • When combined with the "array index" form of getattribute, the array-index becomes a way to choose independent stratifications (i.e. all calls with index 1 will be stratified with respect to each other within the stratification domain, eg. samples within a pixel in a path-tracer, but a call with index 1 and a call with index 2 will be independent).
  • "stratifiedseq" - Allows access to a stratified sequence of numbers, where numbers in the sequence are stratified with respect to each other.
    • Only available with the "array index" form, which says which number in the sequence to return. If multiple sequences are needed, that can be achieved with just this one index by providing a large "offset" into the sequence.
  • "idealsequenceoffset" - This returns the renderer specific ideal offset to get multiple stratified sequences to be used with the "stratifiedseq" flavor.

Both "int[3]" and "float[3]" return types could be supported, with "int" type returning up to 32 bits of randomness, and float returning 0-1 normalized values with up to 24 bits of randomness. The array of [3] is required to be able to support stratification across up to 3 dimensions.

The stratification strategy of choice is up to the renderer, and may be things like blue noise, QMC sequences, progressive multi-jitter, etc. A built-in default if none of these getattribute calls is supported would simply be a call to hash-noise. All of this would be wrapped up in new random() function calls provided as part of the standard library in stdosl.h to provide a readable and portable interface. The most explicit call would be

int[3] random(string type, int seed, int sequence);

type -- Specifies the "uniform", "stratified", etc type of random number. seed -- An index across the "independent" dimension of random numbers, specifies a seed-like integer, so calls with the same seed for the same execution of the same shader return the same value, and different seeds return statistically independent values. sequence -- An index across the "property" space of random numbers -- specifies an index into the appropriate "sequence" for a given type of random number. For "uniform" it may be equivalent to incrementing the seed. For "stratified", types the results maintain the stratification property across sequence numbers.

Variants with int, and int[2] results would be provided that just return the initial dimensions of the int[3] variant. If a renderer only implements the float[3] version of getattribute(), that would be used as a fallback and would only provide 24 bits of randomness per dimension.

Variants that provide float[3],float[2] and float results would be provided that either directly ask the renderer via getattribute for these types, or fallback to utilizing the int versions and doing a conversion from 32-bit-random int to 24-bit 0-1 random float with OSL code.

Variants that omit the seed and sequence values utilizing a default of 0 for those would be provided in stdosl.h

Because of the restriction of 1 integer arg to the existing getattribute shadeop, we would implement

int result[3] = random("stratifiedseq", 3, 7);

as

int[3] random(string type, int seed, int sequence)
{
    int result[3];

    int offsetStride = 4096; // big default
    getattribute("RNG", "idealsequenceoffset", offsetStride);
    getattribute("RNG", "stratifiedseq", 3 * offsetStride + 7, result);
    return result;
}

sfriedmapixar avatar Jun 14 '24 23:06 sfriedmapixar