Shader Networks and Layered Shaders

SiTex Graphics Air version 11 (press release) introduces the ability to use multiple shaders to compute the shading for surfaces, displacements, imagers, and environments.

This new capability provides a simple solution to several common shading tasks that would otherwise require complex custom shaders:

  • Decals:  apply an arbitrary number of texture maps to an object’s color and feed the result into any surface shader.
  • Extra output variables:  add an arbitrary number of additional shaders to an object to produce extra output values without affecting the normal shading of the object.
  • Assign different shaders to specific regions:  simple shader compositing allows existing surface shaders to be selectively applied to sections of a surface.
  • Replicate application shader networks:  create arbitrary shader networks by connecting pre-compiled components.

Further, the implementation of networked and layered shaders does not require existing shaders to be re-written to take advantage of these new features.

This article describes how to use multiple shaders to address each of these tasks.  Sample shaders and rib files are included in the AIR distribution in:

$AIRHOME/examples/layers

Simple Color Layers

Here’s a simple RIB fragment that assigns multiple surface shaders to an object:

Surface "ColorMap" "string ColorMapName" "grid.tx"
Surface "+VMarble"

Prepending the shader name with “+” tells Air to append the shader to the list of shaders assigned to the object. The shaders are executed in the order in which they are assigned. Subsequent shaders inherit the current shading state (including the values of global variables) from shaders previously executed. Here’s a ColorMap shader that uses a texture map to set the object’s color value:

generic ColorMap(
  string ColorMapName = "";
  float SetColor = 1;
  output varying color __Color = 0;
)
{
  if (ColorMapName!="") {
    __Color = color texture(ColorMapName,s,t);
    if (SetColor!=0) Cs = __Color;
  }
}

In the RIB fragment given above, the VMarble shader will use the color map result as one of the input colors for the marble pattern. No modification of the VMarble shader is required.

The new generic shader type is compatible with any other shader type.  Generic shaders can be used to construct re-usable components for layered and networked shaders.

Sample scene: colormap.rib

Decals

The layers example directory includes a LayerDecal shader that applies a masked color to a surface. Simple sequential shader assignment allows an arbitrary number of decals to be applied to a surface without constructing a complex custom surface shader:

Surface "LayerDecal"
  "ColorMapName" "sitex.tx"
  "OriginXY" [.3 .67]
  "SizeXY" [-.3 -.3]
  "DecalColor" [1 1 1]
Surface "+LayerDecal"
  "ColorMapName" "sitex.tx"
  "OriginXY" [.25 .6]
  "SizeXY" [-.25 -0.25]
  "DecalColor" [1 0 0]
Surface "+plastic"

Sample scene: decals.rib

Extra Passes

Another task made easy with multiple shaders is adding extra output values to a rendering pass.   Air 11 includes a new generic shader that adds an occlusion output value to any surface (stored in a __occlusion output variable).   Sample usage:

 Display "extrapass.tif" "framebuffer" "rgba,color __occlusion"
...
Surface "VMarble"
Surface "+genOcclusion" "setshaderoutput" 0

Sample scene: extrapass.rib

Shader Compositing

Air’s multi-shader support includes an option to composite one shader on top of another. Here’s an example that adds a plastic decal to a metallic surface:

Surface "VMetal"
Surface "*VPlastic" "OpacityMapName" "sitex.tx" "ColorMapName" "square.tx"

The * before the shader name tells Air to blend the output color from the VPlastic shader with the output color of the VMetal shader using the output opacity from the VPlastic shader. Any output variables shared by the two shaders will also be composited using the output opacity.

Sample scene: compshaders1.rib

The above example shows how to composite a shader with controls for its output opacity. For surface shaders without opacity controls, an auxiliary shader can be used to control the opacity. Here is a simple generic shader that sets the global input opacity value:

generic OpacityMap(
  string OpacityMapName = "";
  output varying color __Opacity = 1;
)
{
  if (OpacityMapName!="") {
    __Opacity = color texture(OpacityMapName,s,t,"fill",-1);
    if (comp(__Opacity,1)==-1) __Opacity=comp(__Opacity,0);
    Os = __Opacity;
  }
}

This shader allows any surface shader that modulates its output color by the input opacity to be composited over another shader.  Most surface shaders modulate their output color in this way.

Here is how the OpacityMap shader might be used to add a marble decal to a 2D grid:

Surface "VGrid2D"
Surface "+OpacityMap" "OpacityMapName" "sitex.tx"
Surface "*VMarble"

To optimize the evaluation of composited shaders, Air will skip any shader layer whose input opacity value is 0.

Sample scene: compshaders2.rib

Shader Networks

Multiple shaders can be used to re-construct a general shader network by defining connections among the shaders assigned to an object. Connections are defined by including additional parameters in the shader declaration. First, each shader declaration is assigned a name using:

“string layername” “componentname

Naturally layername does not need to be an actual parameter of the shader.

Each connection is defined with an additional string parameter:

“string connect:toparametername” “fromlayer:fromparametername

The connection transfers the value of fromparametername in layer fromlayer to the target parameter in the current shader.  The source parameter must be an output variable of the source shader or a global shader variable.  The target parameter is normally one of the shader’s input parameters, but it can also be the global input color (Cs) or input opacity (Os).  The source and target parameters must have the same type.

Sample usage:

Surface "ColorMap"
  "string ColorMapName" "grid.tx"
  "float SetColor" [0]
  "layername" "gridlayer"
Surface "+VMarble"
  "connect:VeinColor" "gridlayer:__Color"

In this example, the marble vein color is taken from the ColorMap shader’s __Color output variable.

Applications

The Air Stream plug-in for Maya uses the new shader network capability in Air 11 to translate Maya’s shader networks for rendering with Air.  This approach also easily allows:

  • Air shader parameters to be driven by a Maya shading network
  • Air shaders and Maya shaders to be mixed in a single network
  • Air custom shading language functions to be incorporated in a Maya shading network using generic shaders
Posted in Shaders | Tagged , | Comments Off

A dictionary function for the shading language

For the Air 11 release we’ve added new shaders that compute a physical sky environment and sun position based on geographical location and date/time.  We thought it would be nice to be able to enter a location using a simple city name instead of having to look up and remember a set of figures for latitude, longitude, and time zone every time.

We need a function that allows us to look up a set of values based on a name, like looking up a word’s definition in a dictionary.   We can add such a function to Air’s shading language by implementing a dynamic shadeop, or DSO for short, which will be loaded by Air automatically and used to evaluate our new function.

The dictionary DSO adds the following new functions to the shading language:

float dictionary(string dictname, string keyname, float fvalue);
float dictionary(string dictname, string keyname, color cvalue);
float dictionary(string dictname, string keyname, string svalue);

The dictionary function looks for a text file named dictname and tries to load a list of token value pairs, one per line.  The token values should look like

Seattle 47.45 -122.3 -8

The functions return 0 if they succeed.  If an error occurs, the functions return one of the following error codes:

-1:  unable to find or read dictionary file
-2:  key name not found
-3:  unable to convert value to the requested type

Since Air 11 has not yet been released, here are a couple other uses for the dictionary function that you can try with the current Air release.

The first example uses the dictionary function to allow the base surface color to be specified using a name instead of r,g,b values.  The Air distribution includes a list of named colors in $AIRHOME/vizools/colors.txt (used by the Air Space tool).  Here’s the source code for a simple NamedColor shader:

surface NamedColor(
 float Ambient = 1;
 float Diffuse = 1;
 string ColorName = "";
 string ColorList = "$AIRHOME/viztools/colors.txt"
)
{
  color Ct = Cs;
  if (ColorName!="") dictionary(ColorList,ColorName,Ct);
  normal Nf = faceforward (normalize(N),I);
  Oi = Os;
  Ci = Oi * Ct * (Ambient * ambient() + Diffuse * diffuse(Nf));
}

The dictionary function will expand the reference to the $AIRHOME environment variable in the ColorList value.

In a RIB file, we can then specify a named color as:

Surface "NamedColor"
 "string ColorName" "neon pink"

Another application for the dictionary function is to allow simple text files to be used to define “preset” values for a shader’s parameters.  Here’s an extended version of the standard paintedplastic surface shader with an option to use a file with preset values:

surface paintedplasticpreset(
 float Ka = 1;
 float Kd = .5;
 float Ks = .5;
 float roughness = .1;
 color specularcolor = 1;
 string texturename = "";
 string preset = "";
)
{
 float preKa=Ka;
 float preKd=Kd;
 float preKs=Ks;
 float preroughness = roughness;
 color prespecularcolor=specularcolor;
 string pretexturename=texturename;

 if (preset!="") {
  dictionary(preset,"Ka",preKa);
  dictionary(preset,"Kd",preKd);
  dictionary(preset,"Ks",preKs);
  dictionary(preset,"roughness",preroughness);
  dictionary(preset,"specularcolor",prespecularcolor);
  dictionary(preset,"texturename",pretexturename);
 }
 Ci = Cs;
 if (pretexturename != "")     Ci *= color texture(pretexturename);
 normal Nf = faceforward (normalize(N),I);
 Oi = Os;
 Ci = Oi * Ci * (preKa*ambient() + preKd*diffuse(Nf)) + prespecularcolor * preKs*specular(Nf,-normalize(I),preroughness);
}

A sample preset file might look like:

Kd 0.4
Ks 0.6
roughness 0.2
specularcolor = 1 1 0
texturename = grid.tx

The new dictionary shadeop can found in this archive.  Unzip the archive to the AIRHOME directory, and the new shadop will automatically be added to the shaders directory of your Air installation (where Air can find it).  Examples and source code for the shadeop can be found in $AIRHOME/examples/dictionary.

Posted in Shaders | Tagged , | Comments Off

How to render a normal map

A poster to the Rhino newsgroup recently asked for an easy way to render a normal map based on a model in Rhino.  This simple surface shader can be used to render such a tangent-space normal map with Air:

surface RenderTangentNormalMap(
 float ReverseZ = 1;
)
{
 normal NN=normalize(faceforward(N,I));
 if (ReverseZ!=0) setzcomp(NN,-zcomp(NN));
 NN=0.5+0.5*NN;
 Ci = NN;
 Oi = 1;
}

Here’s an archive with the source code and a pre-compiled binary for Air.  Air users can install the shader by unzipping the archive to the shaders directory of their Air installation.

To generate a normal map, first be sure the output image gamma is set to 1 (RhinoAir users can find this control on the Air Display page in the Rhino Options dialog).  Assign the above shader to your objects, then render an orthographic viewport.  You should see an image with colors similar to those in this sample:

To apply the normal map to an object for rendering, assign the VTangentNormalMap shader as the displacement shader for the object, and set the shader’s TextureName parameter to the file name of the previously generated normal map.  Here’s a simple rendering using the above map:

Posted in Shaders | Tagged , , | Comments Off