Traditionally, objects attributes are set directly with values which can be animated, textured or driven by environment variables. This classic approach of driving attributes with direct values can lead to situations where the number of nodes in a project can explode. For example, if each geometry has a material and each material has about 20 texture nodes (including dedicated texture maps) attached to it, then 100K different geometries ultimately leads to define about 2 millions texture nodes!
Shading variables have been designed to avoid this type of situation by giving means to make materials and their texture networks more manageable and reusable across different geometries. The whole idea behind Shading Variables is to think in templates by allowing users to specialize/drive their materials at the geometry level. Instead of putting explicit values to attributes such as filename defining a path to a texture, variables are bound to attributes. These variables called Shading Variables contextually drive attribute values during shading. This way materials and their texture network can be factorized and shared across multiple geometries thus avoiding node explosion.
Shading variables are not meant to construct full-fledged expressions and never will. There are a dynamic set of custom variables for which their values are defined contextually during rendering. Their evaluation is and must be very fast. Their usage doesn't produce any direct overhead on rendering time.
In Clarisse, a Shading Variable is defined by a name and a value. Variables and their values can be declared at different places. They can be declared:
- on scene objects via their Shading Variables attributes.
- on shading groups through the material linker.
- on hierarchy of scene objects until individual shading groups via shading layers.
Once variables are defined, they can be bound to attributes of textures and materials. When a \((x)\) icon is displayed next to its attribute value, it means it can be bound to a shading variable.
When an attribute is bound to a Shading Variable, the value of the attribute is then contextual to the value of the variable. For example, let's say we have two scene objects A and B sharing one material which binds the attribute Filename of a Texture Map File to a variable
DIFF. In the scene object A Shading Variable attribute we have set:
Whereas in the scene object B we have set Shading Variables to:
During rendering, when the material is evaluated on object A the value of the variable
DIFF will take what has been defined in object A:
"/path/to/object_A/diffuse/texture". This behave as if the Filename attribute of the Texture Map File was explicitly set to
"/path/to/object_A/diffuse/texture". In the same way, when the material is evaluated on object B, the Filename implicitly becomes
"/path/to/object_B/diffuse/texture". In the end, a same material can be applied to multiple geometries while implicitly specializing parts of the texture graph at the scene object level.
Declaring shading variables is very easy. The simplest way of declaring them is to declare Shading Variables directly on scene objects. Scene objects, in Clarisse, provide a dedicated attribute named Shading Variables. This attribute of type text expects as input a string which must follow a rather permissive syntax.
While Shading Variable syntax is pretty straightforward and permissive, there are nevertheless a few constraints regarding their syntax:
- names are case sensitive
- names can't start with a digit. For example, it is not valid to declare a shading variable such as:
Typical declaration matches the following scheme:
variable_0_name variable_0_value variable_1_name variable_1_value ... variable_N_name variable_N_value
Even if it's not necessary, the scheme can be extended using assignment operators:
variable_0_name = variable_0_value variable_1_name = variable_1_value ... variable_N_name = variable_N_value
Shading variables are weakly typed. They can declare any numerical value, lists or string.
variable_double 0.5 variable_color 0.8 1 0.2 1 ... variable_path "path_to_my_file"
Note that in case of value list, the syntax can also be embellished this way:
variable_color = (0.8, 1, 0.2, 1)
Moreover, it is possible to redirect the value of environment variables so they can be used as shading variables:
variable_double = "$T" variable_color = ("$MY_CUSTOM_DOUBLE", 1, 0.2, 1) variable_color_2 = "$MY_CUSTOM_COLOR" ... variable_path = "$PDIR/path/to/my/file"
In the same way object variables (variable declared on items) can be also be used using the following syntax :
variable_double = local_var("$MY_OBJECT_DOUBLE") variable_double_2 = local_var("$MY_OBJECT_COLOR") variable_color = local_var("$MY_OBJECT_COLOR") variable_color_2 = (local_var("$MY_OBJECT_COLOR"), 1, 0.2, 1) ... variable_path = local_var("$MY_OBJECT_PATH")
Variables that are within object variables are still evaluated. For example, a object variable
MY_OBJECT_PATH can be set to
Binding/Unbinding Shading Variables#
Shading Variables are meant to be evaluated during rendering. Their value is directly transmitted from the objects to the materials and textures. Even then, not all attributes of textures and materials support Shading Variables.
Attributes which support shading variable bindings can be easily spotted as they have an \((x)\) icon displayed next to their attribute value in the Attribute Editor.
Binding a Shading Variable is achieved by clicking on the \((x)\) icon. When pressed the attribute field displays an empty text input field where you are supposed to type the variable.
Next, we will bind the Color attribute to the Shading Variable
When the attribute is bound to the variable, the value of the attribute is set contextually according to the value of the variable that is declared on the object which is rendered. For example, if we have for object A:
COLOR 0.5 0.5 0.5
And for object B:
COLOR 0.1 0.1 0.1
The value of the Color attribute will here match accordingly to the value of
COLOR variable as defined in each object.
Clicking another time on the \((x)\) icon deactivates the binding of the Shading Variable. The binding is still here but it is disabled. In this state, the attribute acts as if there was no shading variable bound to the attribute.
By clicking multiple times on the \((x)\) icon, it toggles the activation and deactivation of the shading variable.
To unbind a shading variable you must Right Button on the attribute name and choose Unbind Shading Variable from the pop-up menu.
To avoid type mismatch hassle, type conversions are automatically performed when Shading Variables are bound to an attribute. In the same way, if a shading variable of type array provides too many values, only the useful one gets actually used by the attribute. Typically, setting a color for a double typed attribute, the attribute will end up using the first value (red channel) of the provided color. Variables that are not declared, thus undefined are considered as null/empty. In the case of string, the value which is returned in the empty string '', if it is a number then it returns 0.
Overriding and Evaluation Order#
Shading Variables can be defined at different levels:
- at the shading group level using the Material Linker.
- at the scene object level via the Shading Variables attribute.
- at the shading layer level.
Depending on where Shading Variables are declared it is possible to declare a new set of variables overriding entirely the ones already defined. The overriding priority is very similar to the material one. The weakest slot to declared shading variables is at the shading group level using the Material Linker.
The strongest overrides are for variables that are set via the shading layer. One thing that must be taken into consideration about overrides is that they are complete. If you define shading variables in a shading layer, variables that you defined at the object or the shading group level are replaced completely.
For example, let's say you defined the following variables in an object:
DIFF "path/to/my/diffuse/texture" SPEC "path/to/my/spec/texture"
Now let's say you override the variables of the same object using a shading layer like this:
SPEC is lost.
It is possible to use variables that are not yet declared. In that case, the value of an undefined variable is null. For a string this value is the empty string whereas for numerical values it is 0 or a vector of 0.
It is possible to drive final attribute value using mathematical operators that operates on both the attribute value and the shading variable value.
When the attribute is textured the value of the attribute is implicitly the result of the texture. This means the operators is applied using both the texture and the shading variable values.
Perform the addition between the attribute and shading variable values.
||attribute_value + shading_variable_value|
||shading_variable_value + attribute_value|
Perform the subtraction between the attribute and shading variable values.
||attribute_value - shading_variable_value|
||shading_variable_value - attribute_value|
Perform the multiplication between the attribute and shading variable values.
||attribute_value * shading_variable_value|
||shading_variable_value * attribute_value|
Perform the division between the attribute and shading variable values.
||attribute_value / shading_variable_value|
||shading_variable_value / attribute_value|
Finds the remainder after division of.
||attribute_value % shading_variable_value|
||shading_variable_value % attribute_value|
Elevate the attribute to the power of the shading variable value or the other way around.
Clarisse handles a set of conditional operators acting on shading variables, in order to alter values priorities between shading variables and attributes values.
Conditional Operators can be combined with any of mathematical operators.
Ignore the value of the shading variable if the attribute is textured.
Ignore the shading variable binding if it is not defined in the current shading context. This typically happens when the current geometry that is rendered doesn't define any or a specific shading variable.