Layering Shaders

The Layering Shaders

This diagram shows how one can/should simply connect the layering shaders. Basic layering network layout.

These shaders have great layout flexibility, and use NVIDIA ARCS's MDL as inspiration for its components. What you can see from this diagram:

This diagram shows how to insert another combining shader into the network. Two-level layering network layout.

Extra output passes can be specified using Light Path Expression (LPE) terms. A subset of these LPEs are supported by the layering shaders and can be automatically written to user framebuffers, just by specifying them by their names in the camera. One can also use the global string options to specify which LPE can go to an arbitrarily named user framebuffer. Most typical shading subset outputs (e.g. "diffuse") can be represented, and all separated outputs should linearly add up to the beauty "result" output. Therefore, these additive passes are compositing-ready. The current passes supported include those detailed here: Multiple LPE Passes

Global settings

The shaders accept several global settings as string options, some for testing, some for refining the functionality.

Global string options for the layering shaders
"light relative scale" The shader listen to the "light relative scale" with shading matching that of the BSDF's, i.e. it adjusts it's interpretation of lights based on the "light relative scale". To match mia_material shading, "light relative scale" must be 1/PI
"mila ray cutoff" This is the cutoff importance at which point rays starts to be ignored. It is similar to refr_cutoff in mia_material, except it's here a global setting. The default is 0.01 (i.e. rays contributing less than 1% begins being rejected.
"mila clamp output" On/off. Enables/disables an output value clamp in mila_material. The default is off.
"mila clamp level" If the above clamp output is enabled, use this level value to clamp to, roughly as luminance. The default is 1.0. If the output is allowed to go over 1, but needs dynamic range reduction for optimization, try values between 1 and 5; as these depend on scene and pipeline requirements, as well.
"mila quality" A quality multiplier for all other quality settings in the layering library that follow. This defaults to 1.0.
"mila glossy quality" Controls the number of samples for glossy reflection or glossy transmission. This defaults to 1.0. That uses a nominal number of samples, that is also controlled by trace depth and importance, as well as roughness in the glossy shader.
"mila scatter quality" Controls the number of samples for scatter sampling around the hit point. This defaults to 1.0. Nominally about 64 samples if all other factors regarding importance, trace depth, etc. are high.
"mila diffuse quality" Controls the number of samples for indirect diffuse reflection when indirect diffuse detail is used. This defaults to 1.0.
"mila diffuse detail" On/off. Enables indirect diffuse detail mode, similar to ao color bleed/fg force for just the mila_diffuse_reflection shading.
"mila diffuse detail distance" Distance used for detail. Beyond this distance regular indirect diffuse control (GI/FG) is used.
"LPE: XXX" Defines which user framebuffer the Light Path Expression XXX will be written to. Currently supported pass details are here. The string option must begin with "LPE: ", ie a single space after the colon, before the LPE is specified. String option examples based on typical pass names include:
  • "LPE: L<RD>E" "diffuse"
  • "LPE: L.+<RD>E" "indirect"
The default user framebuffer names are the descriptive names from the table detailed in the link above.
"mila share lights" Can be used for debugging and performance comparison. When off, the light sharing is disabled, and every layer (component) shader will run it's own light loop. The default is on.
"mila propagate importance" Can be used for debugging and performance comparison. When off, the importance propagation to subshaders and subrays is disabled, so the impact of this optimization can be judged. The default is on.
"mila separate interactions" Used for debugging and performance comparison. Determines whether after the first hit, secondary rays are traced based on probability of a component, ie, its calculated final weight. The default is on.

The root layering shader mila_material

This shader MUST be the root shader of the shade tree (otherwise, transparency and shadows will not work at all). It can also optionally write to framebuffers.

 declare shader "mila_material" (
         # Shader to call
         shader "shader",
         boolean "thin_walled",
         shader  "backface_shader",
         scalar "visibility" default 1.0,
         # Debug/test tool: Show a given fb as beauty
         #   1 = ALL, 2 = transparency, 3 = absorb, 4 = LrDE, 5 = LrGE etc.
         integer "show_framebuffer" default 0,
         # overall bump vector for whole material
         vector  "bump",
         # Name of framebuffers to output to
         # If empty, nothing is written

         array struct "extra_color" {
             color   "color",
             string  "color_fb",
             boolean "color_comp"
         array struct "extra_vector" {
             vector  "vector",
             string  "vector_fb",
             boolean "vector_comp"
         array struct "extra_scalar" {
             scalar  "scalar",
             string  "scalar_fb",
             boolean "scalar_comp"
      apply material, shadow, photon
      version 4
 end declare

The mila_material root shader requires a single mila_layer or mila_mix as input to the "shader" slot, and the shade tree grows from there.

It also has the following additional inputs

defines if the geometry should be interpreted as the entry- and exit surface of a solid object, or as an infinitely thin shell of the material. This affects primarily how indices of refraction are used and interpreted by the shader. For example, to make a glass sphere, one would use thin_walled off, but to make a soap-bubble using the same geometry, thin_walled would be on. When on, all sides are considered outsides for Fresnel computation in any mila_layer connected into this shader network.
Optionally, the mila_material can also take a single mila_layer or mila_mix as input to the "backface_shader" slot. For inside ray intersections, if such as shader is plugged in here, it will use it, otherwise, it will use the surface shader from the "shader" slot. This is handy for thin-walled materials that have different layers of shading on either side, for example decals on one side but not the other.
when on, causes the shader to write to the given framebuffers.
is a debugging tool. Used for testing. When set to 0, does nothing. When set to 1, copies the first framebuffer (indirect) to the beauty output so one will see it directly in the render. When set to 2 the diffuse framebuffer, etc.
is a vector that, if nonzero, will be the normal used as an overall bump for the whole material, ie, it is used by all the elemental components. It can be used for normal mapping as well as bump mapping, depending on the bump shaders functionality.
is an array of additional colors to output. Each array item has the input color for the color to write, color_fb for the name of the framebuffer to write it to, and color_comp to define if transparency will composite in the value behind it or not. The extra_color inputs can be useful for specifying a matte for the object using this material.
works the same as extra_color but for vector values. This allows any other additional values to be written to framebuffers, for custom purposes.
works the same as extra_color but for scalar values. This allows any other additional values to be written to framebuffers, for custom purposes.

The combining shaders mila_layer and mila_mix

The mila_layer and mila_mix shaders do all the "hard lifting" of layering and mixing. They should only be used either

The shaders will not function properly outside the layering shader system, and may even crash if you attempt to use them elsewhere.

These shaders return a single color output representing the combined shader components input as layers. The mila_layer shader interface is an initial parameter, followed by an array of structs (shown below), one for each layer. Note that when used in some DCC applications, parameter of type "array-of-structs" are not supported in the user interface. To solve this, Phenomena will be provided that expose a fixed sets of layers.

declare shader 
        color "mila_layer" (

        array struct "layers" {
            shader  "shader",
            boolean "on"                     default on,
            scalar  "weight"                 default 1.0,     #: min 0 softmax 1
            color   "weight_tint"            default 1 1 1 1,
            boolean "use_directional_weight" default off,
            integer "directional_weight_mode" default 0,       #: enum "fresnel:custom"
            scalar  "ior"                    default 1.2,
            scalar  "normal_reflectivity"    default 0.05,
            scalar  "grazing_reflectivity"   default 1.0,
            scalar  "exponent"               default 5.0,
            vector  "bump"
     version 5
end declare

The mila_layer shader uses weight similar to an "over" operation in a compositing system, where the weight represents a percentage of the top layer layered over the top of whatever is below. In other words, the first (topmost) component is used at exactly the weight specified, and anything that comes below is weighted by (1 - weight). This is applied recursively down through the layers in the order they are specified. This behavior is similar to the built in energy conservation of mia_material. The input parameters are as follows:

is an array of structures with the following contents. Array items are ordered logically from the top to bottom in the layering. The struct members are:
parameter is the shader of that layer. This must be one of the mila_* shaders or another mila_layer. Trying any other shaders will most certainly crash!
is a simple boolean on/off switch, similar to setting the layer's weight to zero, but easier to use when testing in a UI.
is a layer weight as a color. It will be combined with the weight below.
is the scalar weight of the layer. The weight_tint color above is weighted by this value. Note, for mila_layer this may not be the final weight, because that may depend on the directional dependency parameters (below).
if on multiplies weight by a directionaly dependent weight factor, as controlled by the mode below
defines how to calculate the directional weight dependency.
means "Fresnel". The weight is multiplied by the directionally-dependent Fresnel equation using the supplied ior below.
means "Custom Curve" where the parameters normal_reflectivity (0-degree), grazing_reflectivity (90-degree) and exponent (curve-shape) define the directionally-dependent weighting behavior, just like the reflectivity of mia_material.
is a vector that, if nonzero, will be the normal used for shading this layer.
declare shader 
     color "mila_mix" (
        boolean "clamp"                     default off,

        array struct "components" {
            shader  "shader",
            boolean "on"                    default on,
            scalar  "weight"                default 1.0,     #: min 0 softmax 1
            color   "weight_tint"           default 1 1 1 1,
            vector  "bump"
     apply material, texture, shadow, photon
     version 2
end declare

The mila_mix shader mixes its components, like two paints are mixed together in a bucket. For mila_mix, the component inputs are mostly similar, but without the directional weight dependency. Weight is not interpreted as a percentage like a layer would behave. So, for energy conservation, one other parameter is used to mix with the specified weights as follows:

Defines the mix behavior of the shader, interpreting weights. If:
means "normalized mixing". Each layer is used at the exact weight specified in the weight parameters. Only if the sum of all weights exceed 1.0, are all the weights reduced proportionally to make sure the sum is exactly 1.0
means "clamped mixing". As above, each layer is used at the exact weight specified, starting from the first layer, then the second etc. If the weight of the next layer would push the total sum over 1.0, the weight of only that layer is reduced to keep the sum under 1.0, and the weight of any following layers are forced to zero.