Variable Handling

What Are Variables?

Many ROPs support what are known as “variables”. These are values which a ROP makes available to upstream operators (anything connected to one of its inputs or to one of those inputs’ inputs, and so on).

Variables provide context-specific information that can be used to base fields on how and where they are being used.

For example, torusSdf provides a variable for the angle around its center (normalized to a 0..1 range). This could be referenced by a waveField controlling the thickness of the torus. No matter where the torus is placed, or what axis its on, that waveField would always be using the correct value.

Referencing Variables

The variableReference ROP takes a reference to a provider ROP and the name of a variable, and returns the value when called.

It does this by specifying a table of references (though there’s only one item in the table), which gets included in the ROP’s definition and passed to the shaderBuilder.

It is technically possible for there to be other types of ROPs that use references, but in practice, it’s only variableReference (for now).

Connecting Variables and References

After pulling together all the ROPs in the scene, the shaderBuilder ends up with two tables: variables and references. It matches references to variables, and strips out any variables that aren’t referenced.

Then it takes the results and generates code for them:

  • Preprocessor macros, which indicate which variables should be provided, what their types are, and helpers for converting them to the relevant supported return type.
  • Global variables, which hold the values of the variables.

Providing Variables

A ROP can provide a table that lists out the variables that it can provide, with names and data types.

Within the code for a ROP that provides a variable, there will be a conditional block that calculates the value and puts it into the global variable.

#ifdef THIS_EXPOSE_normangle
THIS_normangle = atan(p.x, p.z) / TAU + .5;
#endif

This allows operators to skip calculations that might not be needed, for variables that aren’t referenced.

Where Variables Can Be Used

The rules around where variables can be used all come down to this: a variable has to be populated before it can be used.

This means that (with certain exceptions) all paths from a variableReference to a render have to pass through the operator that provides the variable for that reference.

In many cases, a ROP will have several inputs, but only some of them will be able to use certain variables.

For example, reflect provides a side variables indicating which side the current position is on. However, the input that determines where the reflection plane is can’t use that variable, since it can’t be determined until after the reflection plane is chosen. But the primary input, if it was an SDF for example, would be able to use the side since it would have already been determined by the time the reflect calls the SDF.