HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
USD Hydra: Customizing For Houdini

USD provides the Hydra Framework (Hd) that enables communication between multiple scene graphs and multiple renderers. The Solaris viewport in Houdini uses Hydra to communicate with render delegates and allows some additional customization. Once you get your render delegate to successfully appear in the Solaris viewport, you may want to read this section.

USD Hydra: Render Settings

Houdini communicates to the delegate using two methods.

When the delegate is created, the plugin is called with the HdRenderSettingsMap initialized with default settings when calling HdxRendererPlugin::CreateRenderDelegate(). This can be used by the delegate to initialize state based on Houdini's current state.

Houdini also makes use of the HdRenderDelegate::SetRenderSetting() method to pass updates to the delegate. Unlike the rest of Hydra, these events are not passed using Sync() methods with dirty bits. So the delegate may receive a larger number of parameter updates than is strictly necessary. Delegates should be careful to only perform updates if values have actually changed.

One common oversight when writing a delegate is to implement the plugin create method that doesn't take the HdRenderSettingsMap. When rendering with husk, the initial render settings are sent en mass instead of one at a time.

USD Hydra: Viewport Controls

Houdini looks for several custom dialog script files (.ds). These files are found in the HOUDINI_PATH under the soho/parameters sub-directory. You should be able to see the example file HdEmbreeRendererPlugin_Viewport.ds in that location.

The path for the files is determined by using the delegate name in the following fashion:

viewport = sprintf("%s_Viewport.ds", renderer_name);
rendersettings = sprintf("%s_Global.ds", renderer_name);
renderproduct = sprintf("%s_Product.ds", renderer_name);
rendervar = sprintf("%s_Aov.ds", renderer_name);
geometry = sprintf("%s_Geometry.ds", renderer_name);
light = sprintf("%s_Light.ds", renderer_name);
camera = sprintf("%s_Camera.ds", renderer_name);

The viewport file is parsed to create custom UI decorations around the Solaris viewport. The example .ds file for Embree is fairly well documented. There are two special keywords the can be used to control where the UI widget appears.

  • uiscope
    The uiscope tag's value is a string which, at the current time, can be a combination of "viewport" and "toolbar" tokens (that is, both tokens can exist in the string value if desired). The "viewport" token will cause the UI to appear in the "Display Options" for the viewport. The "toolbar" option will cause the UI to appear on the side-bar beside the viewport (with the drawing modes).
  • uiicon
    The value for the tag is a string specifying an icon for the toolbar UI. The name of the icon can be an existing icon found in $HH/config/Icons (including icons in the SVGIcon.cache), or it can be a full path to an icon file.

Please note that parameter names are encoded using the hou.text.encodeParm() method of the hou Python module. This is required if parameter names are fully qualified or have non-standard naming.

The other files are parsed to add custom UI to the standard LOP nodes for creating specific USD primitive types. Render Settings LOPs use the rendersettings file. Render Product LOPs use the renderproduct file. Render Var LOPs use the rendervar file. Light LOPs (including Dome Light and other nodes for specific light subclasses) use the light file. Camera LOPs use the camera file. And the Render Geometry Settings LOP (for adding renderer-specific primvars to geometry primitives) uses the geometry file.

These files are loaded by the creation script for each of these LOPs, and invokes the addRendererParmFolders in the loputils.py Python code (found in $HH/scripts/python* ) If this file exists, custom UI should automatically show up when new LOP noes of these types are created in a /stage network.

USD Hydra: Debugging Viewport Scripts

If you're having difficulty having your scripts show up, you can set HOUDINI_SCRIPT_DEBUG to 2 or greater to have Houdini generate output in the console. For light UI, you may have to put debug statements in the loputils.py code.

USD Hydra: Custom Settings

Houdini sends several custom settings through to the render delegate. These can safely be ignored, but they can also be used to enhance the user experience with the delegate.

USD Hydra: houdini:interactive

Houdini and husk both send the current render frame through as a render setting with a key of houdini:frame. The value should be a double. This is required by some delegates to resolve texture maps or other reasons.

For the first frame, husk sends this information in the inital render settings in the delegate constructor.

USD Hydra: houdini:interactive

Husk sends the timeCodesPerSecond() defined on the stage as the houdini:fps render setting (a double value). This can be used by delegates when computing velocity motion blur.

For the first frame, husk sends this information in the inital render settings in the delegate constructor.

USD Hydra: houdini:interactive

The houdini:interactive keyword will be passed a VtValue holding a string (possibly a std::string, TfToken or UT_StringHolder). Code to get the value could be something like:

const char *
valueAsString(const VtValue &v)
{
return v.UncheckedGet<std::string>().c_str();
if (v.IsHolding<TfToken>())
return v.UncheckedGet<TfToken>().GetText();
return v.UncheckedGet<UT_StringHolder>().c_str();
return "";
}

The value of the string reflects the purpose for the render that has been triggered. The value will be one of:

  • normal
    This is a normal render
  • flipbooking
    The render is being performed to generate an image for a flipbook.
  • playing
    The user has hit the play-bar. This render is to render a frame while the play-bar is advancing to the next frame.
  • scrubbing The user is scrubbing the playbar.
  • editing The user is changing a value of a parameter in the network.
  • tumbling The user is tumbling the viewport
  • husk:mplay This setting is sent when husk is sending data to mplay.

While this setting can be ignored, it can also be used to give a better experience to the user. For example, when playing or flipbooking, you might want to let the renderer create a more complete frame. When tumbling or scrubbing, you might want to have a more coarse rendering to give faster feedback to the user.

stageMetersPerUnit

This settings passes down the value of UsdGeomGetStageMetersPerUnit() for the rendering stage.

This metric may be useful for many things, including DOF computations. Hydra specifies that lens metrics are given in 10ths of a scene unit, so, DOF computations should be consistent regardless of the meters per unit, but this option is still provided in case there are other requirements.

You can test this by adding a Configure Layer LOP to the scene and changing the Meters per Unit value.

USD Hydra: USD File Information

husk sends down information about the USD file that's being rendered. This information may be useful to invalidate checkpoint files or for various other purposes. These settings will only be passed when the plugin is created (in the HdRenderSettingsMap).

  • string usdFilename
    The filename passed on the command line
  • int64 usdFileTimeStamp
    The time stamp (modified time) of the file (if the ArResolver resolves to a path on disk).

It should be noted that this information will not necessarily capture all changes to the USD scene since no information about any files referenced by the main USD file is considered.

USD Hydra: renderCameraPath

The viewport passes down an SdfPath to the rendering camera before the path is available via the HdRenderPassStateSharedPtr. This value is passed down before any Sync calls are made, so it allows you to capture information such as shutter open/close times from the camera that the user is looking through in the view port. Note also that cameras (and other sprims) will always be Sync'ed before any rprims.

USD Hydra: houdini:render_pause

The viewport down a bool value indicating whether rendering should be paused or resumed.

viewerMouseClick

Some Houdini viewers may pass user mouse clicks through to the delegate. This is done by calling SetRenderSetting() during the render with the token viewerMouseClick and ether a GfVec2i or GfRect2i value for the mouse coordinates. When the value is a GfVec2i, this specifies a region of interest (instead of a single point). The delegate is free to do what they want with this information. For example, the delegate my decide to focus rendering on the region around that pixel.

To clear the focus, clients may pass an invalid GfRect2i (or a zero area rectangle). Alternately, the client may pass down a mouse location of std::numeric_limits<int32>::max().

void
Delegate::SetRenderSetting(const TfToken &key, const VtValue &value)
{
static const TfToken viewerMouseClick("viewerMouseClick", TfToken::Immortal);
if (key == viewerMouseClick)
{
if (value.IsHolding<GfVec2i>())
{
GfVec2i mouse = value.Get<GfVec2i>();
if (mouse[0] == std::numeric_limits<int>::max())
myRenderer->clearFocus();
else
myRenderer->setFocus(mouse[0], mouse[1]);
}
else if (value.IsHolding<GfRect2i>())
{
GfRect2i region = value.Get<GfRect2i>();
if (region.IsNull())
myRenderer->clearFocus();
else
{
GfVec2i center = region.GetCenter();
myRenderer->setFocus(center[0], center[1]);
}
}
}
}

huskCheckpoint

When SIGUSR1 is sent to , this will cause husk to save a snapshot of the current rendered image. When husk receives this signal, it will also send a message to the delegate by calling SetRenderSetting with a "husk:snapshot" message. Renderers might use this opportunity to save out checkpointing information.

USD Hydra: Render Statistics

Every delegate can choose to implement the GetRenderStats() method. This should return a VtDictionary of render stats.

Every renderer likely has its own representation of render stats. Houdini can use the stats provided by GetRenderStats() in various ways.

For example, a renderer might provide render stats in a VtDictionary that looks like this (using JSON):

{
"camera_near_far":[0.001,10000],
"percent_complete":53.4,
"ray_counts":{
"indirect":69367,
"light_geometry":53922,
"occlusion":94361,
"primary":9216,
"probe":0,
"total":226866
},
"ray_depths":{
"camera":[9216],
"indirect":[62438,6760,162,7],
"light_geometry":[40737,10898,2211,73,3],
"occlusion":[74291,16418,3541,105,6],
"total":[186682,34076,5914,185,9]
},
"renderProgressAnnotation":"128 x 72",
"renderStatsAnnotation":"",
"render_stage_label":"rendering",
"system_memory":[1150980096,1150980096],
"system_time":[2.658,0.835363,9.132031,2.706184],
"ttfp":0.05394,
"world_to_camera":[0.866,-0.203,-0.457,0,0,0.914,-0.407,0,-0.5,-0.35,-0.79,0,0,-42,155,1],
"world_to_ndc":[0.359,-0.47,-0.45,-0.46,-0.2,0.9,-0.407,-0.407,-0.735,-0.82,-0.79,-0.79,77.5,26.801,155,155],
"world_to_raster":[46,1.23,-0.457,-0.457,-26,-94,-0.4,-0.4,-94,2.13,-0.8,-0.8,9920,9230,155,155],
"world_to_screen":[1.17,-0.5,-0.46,-0.46,0,2.2,-0.4,-0.4,-0.7,-0.85,-0.8,-0.8,0,-101,155,155]
}

Viewport: Render Stats

The viewport uses some stats to display render progress or other useful information about the status of the renderer. The statsdatapaths dictionary in the UsdRenderers.json file can describe where this data can be found. The statsdatapaths entry uses the JSONPath format (https://goessner.net/articles/JsonPath/) to specify the location in the VtDictionary where the data where these entries can be found:

  • percentDone - An integer/float representing the render progress (from 0 to 100).
  • peakmemory - The peak memory used by the render
  • renderStage - A string describing the current stage of rendering. For example "Dicing Geometry", "Compiling Shaders"
  • totalClockTime - The total wall-clock time taken to render
  • totalUTime - The total user time (CPU time) used by rendering
  • totalSTime - The total system time (CPU time) used by rendering
  • renderProgressAnnotation - An arbitrary one-line message message displayed directly under the render progress.
  • renderStatsAnnotation - A multi-lined message displayed above the render stats (when render stats display is turned on).

In addition the viewstats entry in the UsdRenderers.json file can be used to add additional information to be displayed. As with the pre-defined tags, the statsdatapath is used to specify the location of the stats to display.

Using the example VtDictionary, the UsdRenderers.json file might look like:

"viewstats" : [
"Camera Rays",
"Light Rays",
"Indirect Rays",
"Shadow Rays"
],
"statsdatapaths" : {
"Camera Rays" : "ray_counts.primary",
"Light Geometry Rays" : "ray_counts.light_geometry",
"Indirect Rays" : "ray_counts.indirect",
"Occlusion Rays" : "ray_counts.occlusion",
"totalClockTime" : "system_time[1]",
"totalUTime" : "system_time[2]",
"totalSTime" : "system_time[3]",
"rendererStage" : "render_stage_label",
"renderProgressAnnotation" : "renderProgressAnnotation",
"renderStatsAnnotation" : "renderStatsAnnotation",
"percentDone" : "percent_complete",
"peakMemory" : "system_memory[1]"
}

Husk: Verbose Output

Many image formats can store arbitrary metadata.

In addition to the metadata saved by the Render Product (and RenderVars), husk uses the VtDictionary of render stats as the source of the metadata. The metadata source is specified in the UsdRenderers.json file's husk.metadata and husk.stats_metadata entries.

Render delegates can pass information back to husk about the rendering using the GetRenderStats() method. It's often useful to save these stats in the metadata for the rendered image.

The husk.stats_metadata key specifies a Houdini string pattern of the dictionary keys which should be converted to image metadata. This defaults to *, meaning that if a delegate doesn't specify the pattern, all of the render stats will be automatically saved as image metadata.

For example, setting "husk.stats_metadata" : "ray_*,img_*" would take all keys in the VtDictionary of render stats that begin with ray_ or img_ and store them directly in the image metadata.

Sometimes, the dictionary returned by GetRenderStats() is extremely large or may not map directly to a useful representation to be stored in image metadata. In this case, delegate authors can use the husk.metadata field to fine tune the data that's written to image metadata.

Tip: If the delegate specifies a husk.metadata field, it may be prudent to set `husk.stats_metadata := "".

Like the viewport render stats, the husk.metadata specifies JSONPath format path specifications for the data source. However, husk provides extra information, storing the delegate's render stats in the render_stats entry of the dictionary:

{
"fps" : 24,
"frame" : 1023,
"frame_index" : 1,
"frame_count" : 3,
"command_line" : "husk -f 1023 -n 3 foo.usd",
"husk_version" : "major.minor.patch",
"usdfile" : "foo.usd",
"product" : "renders/beauty_1023.exr",
"render_stats" : {"camera_near_far":[0.001,10000],"percent_complete":53.4,...}
}

In the above example, husk is rendering 3 frames, starting at frame 1023 (so frame 1023, 1024 and 1025). The frame item represents the Usd frame while the frame_index represents the offset in the sequence of frames being rendered. The delegate's render stats are stored as a dictionary under the render_stats keyword.

The husk.metadata dictionary uses variable expansion of JSONPath entries to determine the value of the metadata. For example:

  • "${render_stats.camera_near_far[0]}" - would expand to "0.001"
  • "Frame: $frame ($frame_index of $frame_count)" would expand to "Frame: 1023 (1 of 3)"

If the JSONPath doesn't specify a leaf node, the object specified by the JSONPath will be expanded as its JSON representation. For example

  • "${render_stats.world_to_camera}" would expand to [0.866,-0.203,-0.457,0,0,0.914,-0.407,0,-0.5,-0.35,-0.79,0,0,-42,155,1]
  • "${render_stats.ray_counts}" would expand to {"indirect":69367,"light_geometry":53922,"occlusion":94361,"primary":9216,"probe":0,"total":226866}

Tip: The code for this expansion can be inspected in HUSD_HuskEngine::addMetadata()

The keys for the metadata items can either be global or namespaced for a particular device. The list of keywords for each format can be found by running iconvert, which shows that the keyword "artist" is recognized by Houdini, TIFF and PNG formats for example. To specify a global tag for artist, you could have:

"husk.metadata" : [
"artist" : "${render_stats.username}"
]

Alternatively, you could namespace the artist for each format explicitly:

"husk.metadata" : [
"Houdini:artist" : "Houdini .pic ${render_stats.username}",
"TIFF:artist" : "TIFF ${render_stats.username}",
"PNG:artist" : "PNG file ${render_stats.username}"
]

When saving multipart OpenEXR images (the default behaviour), metadata can be specified with a richer set of type information. It can be specified as <type> OpenEXR:<key>, where type is one of:

  • bool
  • int8, int16, int32, int64, vec2i, vec3i, vec4i
  • uint8, uint16, uint32, uint64, vec2u, vec3u, vec4u
  • half, vec2h, vec3h, vec4h
  • float, vec2f, vec3f, vec4f, mat3f, mat4f
  • double, vec2d, vec3d, vec4d, mat3d, mat4d
  • string
  • clr3u8, clr4u8, clr3f, clr4f, clr3d, clr4d
  • point3f, point3d
  • normal3f, normal3d
  • vector3f, vector3d
  • timecode, // int32[2] - 4 byte encoding of SMPTE timecode
  • keycode, // int32[7] - 28 byte encoding of SMPTE keycode
  • rational, // int32[2] - val[0]/val[1]
  • box2i, box2f // vec2[2] for 2d bounds (min/max)
  • box3i, box3f // vec3[2] for 3d bounds (min/max)
  • memory // represent integer of bytes used as memory string (e.g. "32.3 MB")
  • time // represent seconds as time string (i.e. "0:13:24.3")

Types which expect multiple values (e.g. vec3f) expect the expanded string to be able to be parsed using JSON format. Since complex (non-leaf) nodes in the stats are expanded as JSON objects, this makes things simple. For example:

"husk.metadata" : {
"mat4f OpenEXR:worldToCamera" : "${render_stats.world_to_camera}",
"mat4d OpenEXR:worldToCamera_d" : "${render_stats.world_to_camera}",
"float OpenEXR:clipNear" : "${render_stats.camera_near_far[0]}",
"float OpenEXR:frame" : "$frame",
"timecode OpenEXR:FramesPerSecond" : "[24000,1001]"
}

When saving multi-part OpenEXR files, the metadata conventions used by OpenImageIO are respected: https://openimageio.readthedocs.io/en/latest/stdmetadata.html

Husk: Verbose Output

When the user specifies verbose output using the -V option on husk, can use two settings in the UsdRenderers.json file to provide a callback. The husk.verbose_callback entry specifies a Python file which husk calls to print out stats. The husk.verbose_interval is used to specify how frequently the Python script is invoked.

The script is passed 3 arguments.

The first argument encodes the verbose settings from husk. This can be decoded using:

verbose_level = ord(verbose[0]) - ord('0')
show_timestamps = (verbose[1] == 't')
show_laptimestamps = (verbose[2] == 'e')
use_color_text = (verbose[3] == 'c')

The second argument is the "mode" that the script is being invoked and will be one of:

  • "start" - Called once per frame before the delegate begins rendering
  • "active" - The render is currently active
  • "end" - The delegate has reported the render is converged (or the render time limit was exceeded).
  • "snapshot" - Husk just saved a snapshot of the render progress.
  • "info" - A single call with render stats (no start, active or end calls)

The third argument stores the husk render stats in JSON form. This is the same JSON dictionary used for meta-data expansion, for example:

{
"fps" : 24,
"frame" : 1023,
"frame_index" : 1,
"frame_count" : 3,
"command_line" : "husk -f 1023 -n 3 foo.usd",
"husk_version" : "major.minor.patch",
"usdfile" : "foo.usd",
"product" : "renders/beauty_1023.exr",
"render_stats" : {"camera_near_far":[0.001,10000],"percent_complete":53.4,...}
}

The karma_stats.py script shipped with Houdini can be used as an example.

Note
husk will handle printing of ALF_PROGRESS (and a few other verbose messages).

USD Hydra: Critical Delegate Errors

There are scenarios where a delegate will hit a critical error and need to terminate rendering early. There's no well defined process in USD/Hydra to pass this failure state to the caller other than render stats. If a delegate hits a critical error, a stand-alone batch renderer (like husk ) should terminate with an error exit status.

At the current time, the only way a delegate can communicate this failure state to the caller is using the Render Stats. husk looks for an integer in the stats called huskErrorStatus. If the value of this stat is non-zero, husk will terminate the render early and exit with the given status.

USD Hydra: Render Delegate Configuration

A variety of configuration options specific to Houdini can be provided by creating a UsdRenderers.json file in the HOUDINI_PATH. This JSON file should contain a dictionary of dictionaries. The top level dictionary maps the render delegate internal name to a dictionary of settings. For example, the settings for the Storm renderer would be:

{
"HdStormRendererPlugin" : {
"valid" : true,
"menulabel" : "Storm",
"menupriority" : 40,
"depthstyle" : "opengl",
"allowbackgroundupdate" : false,
"defaultpurposes" : [ "guide", "proxy" ],
"drawmodesupport" : true,
"aovsupport" : false
}
}

The following settings are supported. The data type of each settings is indicated, along with the default value used if this setting isn't provided. If your render delegate doesn't have settings specified in any UsdRenderers.json file, it uses all default values for its settings.

- @c valid : bool|string (True) @n
    Whether or not this render delegate should be available in Houdini.

    If the value for `valid` is a string, it can be set to a
    simple expression based on the prefix to the string value.
    See the `usdrenderers.py` script for more details.  Currently,
    there are two prefixes handled:
      `platform:REGEX`: Match the os.sys.platform string against the given regex.
      `env:NAME` or `env:-NAME`:  Check the environment variable
        for the named variable (or the non-existence of the named
        variable).
- @c aovsupport : bool (True) @n
    Whether this renderer is able to generate AOV buffers.
- @c drawmodesupport : bool (False) @n
    Whether this renderer wants to support USD draw mode settings.
    Generally this is only supported by OpenGL based renderers. A value of
    False causes the full geometry to always be rendered.
- @c menulabel : string (display_name) @n
    Text to appear in viewport Renderers menu.
- @c menupriority : int (0) @n
    Priority to determine ordering in Renderers menu (higher numbers first).
- @c complexitymultiplier : float (1.0) @n
    Multiplier to use on draw complexity when this renderer is active.
- @c depthstyle : string ('normalized') @n
    Describes the range used when returning depth information. Can be
    normalized (-1, 1), linear (distance to camera), or opengl (0, 1).
- @c defaultpurposes : string array (['proxy']) @n
    Specifies the default list of USD render purposes which will be
    displayed by this render delegate.
- @c needsdepth : bool (False) @n
    Whether this renderer needs the Houdini OpenGL renderer to generate
    depth information.
- @c needsselection : bool (False) @n
    Whether this renderer needs the Houdini OpenGL renderer to draw the
    currently selected primitives in an overlay.
- @c allowbackgroundupdate : bool (True) @n
    Whether this renderer allows updates to occur on a background thread.
- @c restartrendersettings : string array ([]) @n
    A list of render settings that require the renderer to restart if they
    are changed.
- @c restartcamerasettings : string array ([]) @n
    A list of camera settings that require the renderer to restart if they
    are changed.
- @c viewstats : string array ([]) @n
    A list of render statistics that can be printed in the viewer.
- @c preloadlibraries : string array ([]) @n
    A list of dynamically loadable libraries that Houdini should load
    (using dlopen on Linux/MacOS or LoadLibrary on Windows) prior to
    trying to load the plugin for this render delegate. By preloading
    some libraries, delegates may be able to avoid issues involving RPATH
    or RUNPATH settings in the delegate binaries when the Houdini
    installation directory is not known. These paths can contain
    environment variables which will be expanded by Houdini before trying
    to load each library.

Render delegates can also define custom entries, though data related to these custom entries is only to be read and understood by that render delegate. An example of this is that the Houdini GL render delegate supports two additional pieces of data:

- @c lighttypes : string array ([]) @n
    A list of Hydra SPrim types that represent custom light primitive types
    which should be recognized by Houdini GL as lights. This means they
    have guide geometry displayed in the viewport, and appear in the "Look
    Through Light" menu. They will not contribute to the lighting of the
    scene, since Houdini GL has no way to know how the light primitive or
    its parameters should be interpreted.
- @c pointinstancertypes : string array ([]) @n
    A list of USD primitive types that should be treated like point
    instancers for the purpose of picking instances in the viewport. This
    mechanism allows custom prim adapters to create instances that are
    selectable in the viewport even though each instance does not have a
    unique scene graph location.

These two additional pieces of data are explicitly read by the Houdini GL render delegate when it is created. They will not appear in the standard renderer info object.

USD Hydra: Rendering Volumes from SOPs

Solaris has the ability to author USD volume primitives that refer directly to VDB or Houdini volume primitives that are being authored in SOPs. In this situation, the volume and field primitives will appear in USD as normal, but the filePath attribute of the field primitive will begin with "op:", followed by the full path to the SOP from which the volume is being referenced.

These paths can be converted directly into a GT_Primitive pointer using a pair of function defined in a dynamically loadable library, $HH/dso/USD_SopVol.so. This file can be dynamically loaded (using dlopen() or LoadLibrary() on Windows). It exports three functions:

extern "C"
{
void *
SOPgetVDBVolumePrimitive(const char *filepath,
const char *name); // deprecated
void *
SOPgetVDBVolumePrimitiveWithIndex(const char *filepath,
const char *name,
int index);
void *
SOPgetHoudiniVolumePrimitive(const char *filepath,
const char *name,
int index);
}

The returned pointers are void in the function definition, but can both be cast to GT_Primitive pointers. From these GT_Primitive pointers, it is possible to directly access the in-memory representation of the volume data. The pointers will remain valid as long as the USD stage holding the volumes remains valid. Any chaneg to the stage that will invalidate the volume pointers will result in a call to the render delegate to sync or remove the volume primitive.

Note that the hydra primitive type name for Houdini Volumes is houdiniFieldAsset. To support Houdini volumes, this token must be returned in the TfTokenVector returned by the render delegate's GetSupportedBprimTypes() method.

USD Hydra: Integration with husk

The husk renderer is a stand-alone tool shipped with Houdini which can be used for batch rendering with any Hydra delegate, provided the delegate supports rendering to AOVs.

Husk: RenderSettings

There are two virtual methods to create a render delegate in HdxRendererPlugin. Husk will always invoke the method that's passed the HdRenderSettingsMap. This is used to pass information from the USD Settings to the render delegate. When running husk -V4, the application will print out all the settings, render products and render variables which are defined by the settings (-s command line option). These are the settings that your delegate should receive when rendering.

Husk: Command Line Arguments

In the HdRenderSettingsMap that's passed to the delegate, one of the settings will be batchCommandLine which contains the command line and all arguments. It will also pass huskDelegateOptions which contains the string given by the –delegate-options argument.

Husk: Image properties

This section is to clarify exactly how husk interprets the cropWindow, overscan and resolution properties in the Render Settings.

The resolution property specifies the full image resolution. This will be picked up from the settings primitive (-s option) and can be overriden by the user by using the –res command line option. This resolution is passed through to the Render Delegate unchanged. However, the resolution of the AOV buffers may be different than this value.

In terms of the OpenEXR data and display window, the cropWindow property is used by husk to define the display windwow. The overscan property is applied after the display window is computed. This result of this overscan expansion results in the OpenEXR equivalent data window.

When husk allocates AOV buffers, it will use the data window resolution (rather than the image resolution). Often, these vales are identical, but may be different if there's a crop window or overscan. It's up to the delegate to capture the resolution, cropWindow and overscan from the HdRenderSettingsMap and interpret thses to understand the full image representation.

// Example code to convert the resolution, cropWindow and overscan
// to the OpenEXR data and display windows.
GfVec2i resolution = renderSettings[TfToken("resolution")].Get<GfVec2i>();
GfVec4f cropWindow = renderSettings[TfToken("cropWindow")].Get<GfVec4f>();
GfVec4i overscan = renderSettings[TfToken("overscan")].Get<GfVec4i>();
// resolution, cropWindow and overscan are extracted from the
// HdRenderSettingsMap passed into the constructor (or modified by
// the SetRenderSetting() method on the render delegate.
UT_DimRect image_window(0, 0, resolution[0], resolution[1]);
UT_InclusiveRect display_window(
SYSceil(image_window.width() * cropWindow[0]),
SYSceil(image_window.width() * cropWindow[1] - 1),
SYSceil(image_window.height() * cropWindow[2]),
SYSceil(image_window.height() * cropWindow[3] - 1),
UT_InclusiveRect data_window(
display_window.x() - overscan[0],
display_window.y() - overscan[1],
display_window.x2() + overscan[2],
display_window.y2() + overscan[3]);
// AOVs will be allocated with:
// width = data_window.width()
// height = data_window.height()

Husk: Delegate Render Products

Each render product has a type associated with it. This type defaults to raster and husk will save these products from the AOVs that are filled out by the delegate. However, there may be product types that husk doesn't understand (for example deep or photon_map or checkpoint ). Many of these products types may be specific to a single delegate.

Prior to rendering a frame, husk will collect all the products that it doesn't know how to handle and pass these to the delegate as a render setting. Since the delegate shouldn't depend on usdRender, the render products and variables are serialized into an HdAovSettingsMap.

As a note, when husk is determining the AOVs required for rendering, it will normally skip any render vars defined for non-raster products. However, husk will look for a custom render setting bool includeAovs. If the value of this custom render setting is true, then the AOVs will be created by husk. For example, in a USDA file, this might look like:

...
float4 dataWindowNDC = (0, 0, 1, 1)
custom string driver:parameters:artist = ""
custom bool includeAovs = 1
bool instantaneousShutter = 0
rel orderedVars = [
</Render/Products/Vars/C>,
...

Please note that delegates should still continue to render AOVs normally so that husk can save out valid raster products.

Each product will have the following key/values:

  • token productName : The name of the product
  • token productType : The type of the product
  • VtArray<HdAovSettingsMap> orderedVars : the list of ordered render variables. Note that the product may have additional settings passed down.

Each render variable in the orderedVars will have the key/values:

  • token sourceType - The source type (i.e. "lpe")
  • string sourceName - The name (i.e. "lpe:C.*")
  • token dataType - The type of data (i.e. color3f or normal3f)
  • HdFormat aovDescriptor.format - The format of the variable's HdAovDescriptor
  • VtValue aovdescriptor.clearValue - The clear value in the AOV descriptor
  • bool aovdescriptor.multiSampled - The value of the multiSampled field in the AOV descriptor
  • HdAovSettingsMap aovdescriptor.aovSettings - The aovSettings map from the AOV descriptor. This will hold any extra settings for the render variable (e.g. metadata)

Below is code that shows how to interpret the data sent by husk:

void
MyDelegate::SetRenderSetting(const TfToken &key, const VtValue &value)
{
static TfToken delegateRenderProducts("delegateRenderProducts", TfToken::Immortal);
static TfToken orderedVars("orderedVars", TfToken::Immortal);
static TfToken aovSettings("aovDescriptor.aovSettings", TfToken::Immortal);
if (key == delegateRenderProducts)
{
using delegateProduct = HdAovSettingsMap;
using delegateVar = HdAovSettingsMap;
using delegateProductList = VtArray<delegateProduct>;
using delegateVarList = VtArray<delegateVar>;
delegateProductList drp = value.Get<delegateProductList>();
std::cout << drp.size() << " delegate render products\n";
for (const auto &pit : drp)
{
std::cout << "Product\n";
for (const auto &pval : pit)
{
if (pval.first == orderedVars)
{
delegateVarList vars = pval.second.Get<delegateVarList>();
std::cout << " -- " << vars.size() << " render vars --\n";
for (const auto &vit : vars)
{
std::cout << " Render Var\n";
for (const auto &vval : vit)
{
if (vval.first == aovSettings)
{
std::cout << " AOV Settings\n";
HdAovSettingsMap amap = vval.second.Get<HdAovSettingsMap>();
for (const auto &aval : amap)
std::cout << " " << aval.first << " := " << aval.second << std::endl;
}
else
{
std::cout << " " << vval.first << " := " << vval.second << std::endl;
}
}
}
}
else
{
std::cout << " " << pval.first << " := " << pval.second << std::endl;
}
}
}
}
}

Husk: Image Metadata

husk uses the Houdini image libraries to write out images, which means that if need be, you can customize an image device (see the HDK documentation for loading and saving raster images).

Metadata is passed to all image devices in the same fashion, but not all image devices will process metadata in the same way.

When saving an image, husk will process all the attributes on the render product. It looks for settings that begin with driver:parameters: and will pass that data down to the image as a piece of metadata. For example

custom string driver:parameters:OpenEXR:test_string = "Hello world"
custom float driver:parameters:OpenEXR:pi = 3.1415

will add metadata to OpenEXR files named "test_string" and "pi" with the types and values you might expect. Note that OpenEXR doesn't necessarily support all the types of data allowed in USD.

Users are free to add any metadata they want. However, some data is only known to the render delegate (for example the worldToCamera transform matrix). This delegate specific data is provided using the husk.metadata entry in the UsdRenderers.json file.

Husk: Extra Channels

There may be cases where it's useful to have a RenderVar that outputs more than one image plane when written out to disk. For example, a single Cryptomatte layer may require four image planes (or more, depending on its settings), but it's more convenient and user-friendly to have to declare just one RenderVar for it instead of four. This may be accomplished by returning a shared pointer to a UT_HUSDExtraAOVResource from the GetResource method of your HdRenderBuffer subclass.

UT_HUSDExtraAOVResource is a simple struct that should be very portable (no reliance on Houdini or USD classes). It provides the information for an affiliated extra image planes and is currently only used by husk when writing images to disk (or mplay). These affiliated AOVs cannot be displayed in a Hydra viewport. See HUSD_RenderBuffer for further information on using UT_HUSDExtraAOVResource.

Husk: Licensing

Husk will not consume any Houdini licenses when it runs. Husk will check for the existence of some kind of Houdini token, but will not actually consume the Houdini license while rendering.

Note that with Apprentice rendering, the resolution of products may be restricted.