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.
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.
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
It is technically possible for there to be other types of ROPs that use references, but in practice, it’s only
variableReference (for now).
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.
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.
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.
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.