Houdini 20.0 Solaris

Shader Framework

Describes the Solaris shading framework, including shader node translation to USD primitives.

On this page

Overview

Houdini users author USD shaders using shader VOP nodes inside a Material Library LOP.

Users create VOP nodes that represent desired shaders for a particular renderer, set their parameter, and wire them together to form a shader network that culminates in a terminal shader node, usually representing a BRDF or a surface shader. It is possible to group several surface shaders for different renderers into one USD material using a Collect VOP.

The Material Library LOP searches for the terminal shader nodes, as specified in its parameters, and then uses a shader translator script to traverse the VOP node input chains and author corresponding USD shader primitives.

There are actually several shader translator scripts for different render targets and different languages, and they are located in $HH/husdplugins/shadertranslators/. Such setup also allows adding own custom shader translators for own renderers. During a cook, for a given shader VOP node, the Material Library LOP chooses a particular shader translator based on the VOP node’s render mask and hands over the task of translating the VOPs to USD shaders to that shader translator.

The default shader translator accepts any render mask and is able to handle a broad variety of shader VOPs for different renderers. It uses spare parameters on VOP nodes to guide the decisions about how to translate them to a USD primitive, and also uses parameter tags refine the translation of the node parameters into USD attributes.

Creating a New Shader HDA

If you want to add a support for a new renderer in Solaris, one of the steps will be to provide a set of shader VOPs, for users to be able to author shader networks for that renderer in Houdini.

The simplest way to create a shader HDA is to use File ▸ New Asset… from the main menu. Choose VOPs and set Network Type to “USD Surface Shader”. After you accept, you can configure the HDA in the Node tab, to specify the Render Mask that identifies the new renderer or a new shading language, and the VopNet Mask which is used for filtering available shaders in the TAB menu.

Next, in the Parameters tab, you should create the node parameters that correspond to this particular shader. Usually node parameters are named the same way as the shader parameters, although you can configure the parameter translation (including the USD attribute name and type) using parameter tags listed in the default translator section below.

Note, in general, there is no need for node parameters for the shader output parameters, because the shader translator will author them based on the information from the node output. However, if you need to configure the shader outputs (e.g., to specify a particular name or type), you can create a hidden node parameter (e.g., a string), and store the configuration on that parameter.

After that, you can create the connectable inputs on the node in the Input/Output tab. Since you already have node parameters, you can create inputs for them by pressing the Create/Update Inputs from Parameters button. But, you can add arbitrary inputs and outputs using the New Input and New Output menus.

After you apply or accept the above changes, you should be able to create a new shader node inside the Material Library LOP (or any /mat context).

As a final step, you can further customize the translation of the shader HDA to USD primitives using spare parameters listed in the default translator section below. For example, add a Shader Name parameter called shader_name, to control the shader ID authored in the USD primitive.

Scripting HDA Creation

While creating the HDA manually from the main menu is fine for a small number of shader nodes, it becomes impractical for a large number of custom shaders. In such cases, it’s more convenient to use a script to generate HDA definitions.

To facilitate this process, there is a Python module called shaderhda.py that is able to create HDA library files. The basic process is to parse the source information about a 3rd-party shader from some native representation and store it in the data structures defined in the Shader class from that Python module. Then, you can call one of the Shader's utility functions, such as addHDAToFile() to create HDA and save it in a file.

This is how the mtlx2hda.py script generates HDAs for the standard MaterialX shaders that ship with Houdini. You can refer to that script for further tips and recommendations of how to create HDAs programmatically.

Auto-generated Preview Shader

The Material Library LOP has an option to automatically generate a standard USD preview shader targeting the universal render context that each USD-compliant renderer should support. When the Auto-generate Preview Surface Shaders toggle parameter is turned on, the LOP translates the shader VOP into two USD shaders: one targeting the specific renderer, and anther one targeting the universal render context. The specific renderer will render the scene in high quality using the native shader, but any other renderer will also be able to shade the scene reasonably well using the preview shader approximation.

To be able to generate a preview shader for a given render-specific shader node, its parameters need to have appropriate OGL tag set to indicate their mapping to the preview parameters. For example, ogl_diff tag indicates that the parameter represents a diffuse color and ogl_diff_rough marks the parameter as a diffuse roughness coefficient. You can also consult the Principled Shader VOP for other examples of parameter tags.

The the basic tagging process is described here and the full list of recognized shader parameter tags is listed on this page. But the easiest way to tag a shader HDA parameter is to go to the Operator Type Properties, click on it in the Parameters tab, and then press the Built-in Tags… button in the lower right corner. It will bring up the tag browser that lists the OGL tags under the “OpenGL” tree branch. You can then click on a particular tag and hit “Accept” to add the tag to the parameter.

Note

The preview shader is authored using a script in $HH/husdplugins/shadertranslators/ and it is possible to write a custom preview translator, but in practive tagging parameter is sufficient to obtain a good shader approximation.

Default Shader Translator

As mentioned above, the Material Library lop utilizes a default shader translator script in $HH/husdplugins/shadertranslators/ to author USD shader primitives based on the VOP shader nodes it points to. The script basically looks a the node, authors a USD shader primitive given node’s properties, then iterates the node’s parameters and authors input attributes for them, and then repeats the process for each input node.

Spare Parameters

To guide the authoring of the USD shader primitive, the default translator uses simple heuristics based on spare parameters, if available.

For example, Shader Primitive Name (shader_shaderprimname) allows the users to specify the explicit name of the USD shader primitive, rather than having the translator use the node name. Another useful pair of properties are Shader Name (shader_name) and Shader Name Kind (shader_namekind), which specify the USD primitive Asset or ID attribute value which identifies a shader. Note that the term “kind” is not related to USD concept of geometry kind; it is merely the indicator of how to interpret the name parameter: as an ID or as an asset attribute.

One final pair of spare properties worth mentioning are Reference Type (shader_referencetype) and Base Primitive Path (shader_baseprimpath), which can be used to configure a node to author a USD primitive reference to an exiting primitive, etc.

You can add spare parameters in the parameter editor’s Node Properties tab (in the parameter editor), which has a tree properties branch called VOP Properties ▸ Shader. You can consult spare properties page for a complete list of available spare properties.

Parameter Tags

To further fine-tune the USD shader primitive attributes, the default shader translator looks at the parameter tags to determine the name, type, and other aspects of the attribute.

You can add a tag to the parameter using the parameter editor dialog, which has Tag table in the lower right corner of the parameter properties pane. There is also Built-in Tags… action button that brings up tag browser that contains shader parameter tags with preset values.

Here is a complete list of the available parameter tags recognized by the default shader translator.

sidefx::shader_forceparmval

If set to 1, forces the authoring of the shader input attribute, even if the node parameter is at default value. The default value is 0.

Note

There is a shader_skipdefvalparms spare parameter that can be added to the shader node to control this without setting tags on individual parameters.

sidefx::shader_isparm

If set to 0, indicates that the node parameter does not represent a shader parameter and therefore the translator should not author any attribute for it. The default value is 1.

Note

There is a shader_useallparms spare parameter that can be added to the shader node to control the default behavior.

sidefx::shader_isparmuniform

If set to 1, indicates that the corresponding shader parameter is uniform (rather than varying). This can be used for shader languages that support such type modifiers, and to set the correct variability in the translated representation. The default value is 0.

sidefx::shader_parmname

Specifies USD attribute (i.e, shader parameter) name that corresponds to the node parameter. The default value is "".

sidefx::shader_parmtype

Specifies the type name of the USD attribute that corresponds to the node parameter. The default value is "".

sidefx::shader_parmrendertype

Specifies the render type name of the USD shader input attribute that corresponds to the node parameter. The shader translator authors the render type as a string metadata on the shader’s input attribute. The default value is "".

sidefx::connector_kind

Applicable to the Generic Shader VOP, determines whether the parameter has a corresponding input connector, a corresponding output connector, both, or none. The value can be one of: "none"

The parameter does not have any corresponding ports, neither input nor output.

"in"

The parameter has a corresponding input but not an output port.

"out"

The parameter has a corresponding output but not an input port.

"inout"

The parameter has both a corresponding input and an output port.

sidefx::connector_type

Applicable to the Generic Shader VOP, specifies the VOP type of the corresponding node connector port. Default value is "".

Writing a Custom Shader Translator

While the default shader translator covers most use cases, there may be a situation where you may want to consider writing a custom shader translator for a particular renderer or shading language. For example, a VOP node may need some special treatment and custom handling to property translate it to USD shader primitive.

In such cases, you can write a python code to handle the translation. You can put the python file in a husdplugins/shadertranslators/ subdirectory of a Houdini search path (or HOUDINI_HUSDPLUGINS_PATH if it’s set), and the shader translation framework should pick it up and register it as a translator, providing it contains the API function usdShaderTranslator() that returns the translator object.

The returned translator object should be interchangeable with ShaderTranslator python class defined in husd/shadertranslator.py. That is, it should implement all the methods with the same signatures that the ShaderTranslator does. The ShaderTranslator class contains a handful of methods that LOPs invoke to perform the shader translation. For example, matchesRenderMask() method tests if the translator handles shader that have the given render mask. The shader translator needs to also provide createMaterialShader() method that does the actual authoring of USD shader primitives, given the shader node and other information.One way to write a custom shader translator is to derive the custom class from ShaderTranslator class, and then override select few methods that have custom behavior.

For technical details about Python shader translators, please refer to the code documentation in $HH/husdplugins/shadertranslators/default.py and husd/shadertranslator.py.

Note

It is also possible to write a custom shader translator in C++ using HDK. Please refer to HUSD_ShaderTranslatorRegistry and HUSD_ShaderTranslator classes in HUSD library.

Visualizing Shader Outputs

The default shader translator chose to use the Visualize VOP for debugging shader networks. Using the hotkey x or manually, users can attach that node to outputs of pattern shader nodes, and the translator will hook up the USD material to render that output. This is consistent with the use of the Visualize VOP in an Attribute VOP SOP.

The exact translation of a Visualize VOP depends on the shading language, because each language has own standard (or other) surface shader that can be configured to emit light, etc. I.e., the usual practice is to have the pattern shader output drive an input of a standard shader, which is then made as a temporary terminal shader for the USD material. For example, for MaterialX, the shader translator uses Uniform EDF and Surface shaders.

The default shader translator offers an API call, createUsdVisualizerShader(), to create and connect the USD language-dependent shader primitive(s) that will perform the visualization. If you want your own translator to offer shader visualization for debugging, this is the function you should override. To see an example of this, please refer to husdplugins/shadertranslators/mtlx.py, which creates and sets up an ND_UsdPreviewSurface_surfaceshader USD shader.

Solaris

USD

Geometry

  • SOP Geometry I/O

    Details of how Houdini converts SOP geometry to USD, and how you can control the process.

  • Component Builder

    The Component Builder tool puts down a network snippet for creating a USD model from SOPs, with support for materials, variants, payloads, and layering.

Layout

  • Edit node

    Interactively transforms prims in the viewer. Can use physics collisions to position props realistically.

  • Layout node

    Provides tools for populating a scene with instanced USD assets. You can place individual components, paint/scatter components in different ways using customizable brushes, and edit existing instances.

  • Custom Layout Brushes

    How to create layout brush digital assets you can use to customize the behavior of the Layout LOP.

Look Development

  • MaterialX

    Houdini has VOP node equivalents of the MaterialX shader nodes. You can build a shader network using these nodes, or import an existing MaterialX-based shader, and use them with Karma (Houdini’s USD renderer).

  • UDIM Paths

    You can encode different tiles of a texture space into different texture files, each with its own resolution. You can then specify a texture filename such as kaiju.exr, and Houdini will replace the token with the specific tile address at load time.

  • Shader Translation Framework

    Describes the Solaris shading framework, including shader node translation to USD primitives.

Karma rendering

Tutorials